Let's say I'm making an application. For the user interface I decide to go with an Model-View-ViewModel pattern. The UI will access a service layer which will use Entity Framework Core as a replacement for the more traditional repository (I know people have mixed feelings about this, but this is not the point of this question). Preferably the DbContext from EFCore will be injected into the service. Something like this:
public void SomeUserInterfaceMethod()
{
using (var context = new MyContext())
{
var service = new MyService(context);
service.PerformSomeAction();
}
}
Now this isn't so bad at all, but I do have an issue with it. The using (var context = new MyContext()) will be in a lot of places in the code, even in a small application. This means trouble if I want to change the context as well as for testing purposes.
Now I could replace the new MyContext() with a factory method (MyFactory.GetMyContext()), which would make it easier to replace. But what if I want to change the context to a testing one (using another database)?
Is there some more clever way to initialize MyContext which allows both for easy replacement, but also for easy testing?
Honestly, I don't see any problems in using factory method for your purposes.
Easy replacement example:
public class ClassWithSomeUserInterfaceMethod
{
private readonly IDataContextsFactory dataContextsFactory;
public ClassWithSomeUserInterfaceMethod(IDataContextsFactory dataContextsFactory)
{
this.dataContextsFactory = dataContextsFactory;
}
public void SomeUserInterfaceMethod()
{
using (var context = dataContextsFactory.GetDataContext())
{
var service = new MyService(context);
service.PerformSomeAction();
}
}
}
You can pass any class that implements IDataContextsFactory interface in dataContextsFactory.
Easy testing example:
public AnotherDatabaseDataContextFactory : IDataContextsFactory
{
public IDataContext GetDataContext()
{
return new AnotherDataContext();
}
}
[Test]
public void SomeTest()
{
var factory = new AnotherDatabaseDataContextFactory();
var classWithSomeUserInterfaceMethod = new ClassWithSomeUserInterfaceMethod(factory);
classWithSomeUserInterfaceMethod.SomeUserInterfaceMethod();
// Assert.That ...
}
Hope it helps.
Related
i'm trying to develop from scratch a WPF app with the use of Simpleinjector as a IOC container.
I'm new on this topic and i have some issue regards lifetime of object and hot use them correctly.
I started the app by following the WPF integration guide on simpleinjector manual.
But i don't understand how to receive a new instance every time a service needed it
As i ask in my previous post i need to receive a new unitOfWork every time a service need it.
as #Steven say on my previous post
Do note that transient means "allways a new instance is resolved when it is requested from the container." If you're not requesting it again, you will be operating on the same instance, which might explain the ObjectDisposedException.
In the other post i found a solutin but i think it's a little bit over-complicated and it's to create a factory and inject this instead of the instance because i want to call the container.getInstance only on the startup method and not on the service by passing the container as a dependency
It's the only way i have to achieve this or there is something that i don't understand on how to develop in DI way?
Example of code:
public class HeaderViewModelFactory : IWpfRadDispenserViewModelFactory<HeaderviewModel>
{
private readonly ProductionService _service;
public HeaderViewModelFactory(ProductionService service)
{
_service = service;
}
public HeaderviewModel CreateViewModel()
{
return new HeaderviewModel(_service);
}
}
public class HeaderviewModel : ViewModelBase
{
private readonly ProductionService _service;
public HeaderviewModel(ProductionService service)
{
_service = service;
CreateData();
}
private void CreateData()
{
_service.CreateTestCycle();
}
}
public class CycleService : GenericDataService<Cycle>
{
private readonly IUnitOfWork<WpfRadDispenserDbContext> _uowContext;
public CycleService(IUnitOfWork<WpfRadDispenserDbContext> uowContext)
: base(uowContext)
{
_uowContext = uowContext;
}
public void CreateTestCycle()
{
var cycleDataService = new GenericDataService<Cycle>(_uowContext);
var vialDataService = new GenericDataService<Vial>(_uowContext);
Cycle c = new Cycle();
c.BatchName = "test";
Vial v = new Vial();
v.Name = "Test Vial";
c.Vials.Add(v);
_uowContext.CreateTransaction(IsolationLevel.ReadCommitted);
try
{
vialDataService.Create(v);
_uowContext.Persist();
var list = vialDataService.GetAll();
cycleDataService.Create(c);
_uowContext.Persist();
_uowContext.Commit();
}
catch (Exception e)
{
Console.WriteLine(e);
_uowContext.RollBack();
throw;
}
finally
{
_uowContext.Dispose();
}
}
}
private static Container Bootstrap()
{
// Create the container as usual.
var container = new Container();
// Register your types:
// Register your windows and view models:
container.Register<WpfRadDispenserDbContextFactory>(Lifestyle.Transient);
container.Register<IUnitOfWork<WpfRadDispenserDbContext>,WpfRadDispenserUOW>();
container.Register(typeof(CycleService));
container.Register<IWpfRadDispenserViewModelFactory<ProductionViewModel>,
ProductionViewModelFactory>(Lifestyle.Transient);
container.Register<IWpfRadDispenserViewModelFactory<AnagraphicViewModel>,
AnagraphicsViewModelFactory>(Lifestyle.Transient);
container.Register<IWpfRadDispenserViewModelFactory<HeaderviewModel>,
HeaderViewModelFactory>(Lifestyle.Transient);
container.Register<IViewModelAbstractFactory,
ViewModelAbstractFactory>(Lifestyle.Transient);
container.Register<INavigator, Navigator>(Lifestyle.Transient);
container.Register<MainWindowViewModel>();
container.Register<MainWindow>();
//container.Options.EnableAutoVerification = false;
//container.Verify();
return container;
}
in this way every time i create a new viewmodel i receive the same service and ovviously the dbcontext it's not present anymore because disposed.
This is not the rela code but only an example that i made to understand how DI works.
Using Abstract Factory pattern is the most common and recommended approach. Using the container in your application directly is widely considered an anti-pattern, like the Service Locator (Service Locator is an Anti-Pattern) for a very good reason.
Abstract factory allows instantiation of objects without introducing a tight coupling to the actual implementation that knows how to create specific instances.
Most IoC frameworks support this pattern natively. Most of the time they provide the generic interface for the factory. You register the instance (the product) with the container and the framework will export a ready-to use factory for you. You add the dependency to this framework interface to your object e.g. constructor. Then you register the generic factory interface. The framework will automatically create the instance of the factory and inject it into the relevant instances e.g., via constructor.
I am not too familiar with Simple Injector, but the framework really keeps things simple. There is no such code generation.
But the pattern is very simple (that's why this is so easy to automate) and in no way complicated.
Example
The interface required to dynamically create the instances of type TInstance:
interface IFactory<TInstance>
{
TInstance Create();
}
The implementation of this factory:
class SaveItemFactory : IFactory<ISaveItem>
{
ISaveItem Create() => new SaveItem();
}
The type that needs to create a dependency dynamically:
interface IItemManager {}
class ItemManager : IItemManager
{
IFactory<ISaveItem> SaveItemFactory { get; }
public ItemManager(IFactory<ISaveItem> itemFactory) => this.SaveItemFactory = itemFactory;
public void SaveData(object data)
{
ISaveItem saveItem = this.SaveItemFactory.Create();
saveItem.SetData(data);
}
}
Configure the container:
public void Run()
{
var container = new SimpleInjector.Container();
container.Register<IFactory<ISaveItem>, SaveItemFactory>(Lifestyle.Singleton);
container.Register<IItemManager, ItemManager>(Lifestyle.Singleton);
IItemManager itemManager = container.GetInstance<IItemManager>();
itemManager.SaveData("Some Data");
}
I'm using EF for the first time, in a WPF application, using MVVM pattern. I read a lot of stuff but I couldn't end up with a solution. My problem is how to integrate EF in my app.
The most common way I found is build your own Repository + UnitOfWork. I don't like it. Basically because I already have DbContext and DbSet that can work as unit of work and repository, so why reinvent the wheel?
So I tried to use DbContext directly from view models like this
public class BookCollectionViewModel : ViewModelBase
{
public void LoadCollection()
{
Books.clear();
var books = new List<Book>();
using(var ctx = new DbContext())
{
books = ctx.DbSet<Book>().ToList();
}
books.Foreach(b => Books.Add(b));
}
ObservableCollection<Book> Books { get; } = new ObservableCollection<Book>();
}
But I don't like to use DbContext directly from view models, so I built a service layer
public class DbServices
{
public TReturn Execute<TEntity>(Func<IDbSet<TEntity>, TReturn> func)
{
TReturn retVal = default(TReturn);
using(var ctx = new DbContext())
{
retVal = func(ctx.DbSet<TEntity>());
}
return retVal;
}
}
public class BookCollectionViewModel : ViewModelBase
{
private DbServices mDbServices = new DbServices();
public void LoadCollection()
{
Books.clear();
var books = mDbServices.Execute<Book>((dbSet) => return dbSet.ToList());
books.Foreach(b => Books.Add(b))
}
ObservableCollection<Book> Books { get; } = new ObservableCollection<Book>();
}
But this way every action is atomic, so when I modify an entity I have to call SaveChanges() every time or loose changes, because DbContext is always disposed. So why not create a class-wide DbContext?
public class DbServices
{
private Lazy<> mContext;
public DbServices()
{
mContext = new Lazy<TContext>(() => {return new DbContext();});
}
public TContext Context { get { return context.Value; } }
public TReturn Execute<TEntity>(Func<IDbSet<TEntity>, TReturn> func)
{
return func(Context.DbSet<TEntity>());
}
}
Unfortunately, this way again doesn't work, because once a dbcontext is created, it is never disposed... So how about explicitly Open/Close the DbContext?
The question is: Where and how should I create/dispose the DbContext? The only thing I'm sure of is that I don't want to rebuild repository and unit of work, since they already exist as DbContext and DbSet...
I'm in the same position. I find that any persistent repository on the client side causes users not to see each others' changes.
Here's a good video explaining why EF is not the same as a repository
https://www.youtube.com/watch?v=rtXpYpZdOzM
Also I found an excellent end-to-end tutorial on WPF,MVVM & EF
http://www.software-architects.com/devblog/2010/09/10/MVVM-Tutorial-from-Start-to-Finish.
In this he exposes the data through a WCF data service and detaches them from the dbcontext straight away.
Hope it helps
I want to make unit tests for my project using a fake context (i'm currently using moq for that).
I have the following classes:
EpisodiosService.cs
public class EpisodiosService : IService<Episodio>
{
private Context _context;
public EpisodiosService(Context context = null)
{
if (context == null)
{
context = new Context();
}
_context = context;
}
...
}
TesteHelper.cs
public class TesteHelper
{
public static List<Episodio> lstEpisodios { get; set; }
public static Mock<Context> mockContext { get; set; }
public static Mock<Context> GerarMassaDeDados()
{
...
var mockSetEpisodio = new Mock<DbSet<Episodio>>();
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.Provider).Returns(lstEpisodios.AsQueryable().Provider);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.Expression).Returns(lstEpisodios.AsQueryable().Expression);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.ElementType).Returns(lstEpisodios.AsQueryable().ElementType);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.GetEnumerator()).Returns(lstEpisodios.AsQueryable().GetEnumerator());
mockContext = new Mock<Context>();
mockContext.Setup(x => x.Episodio).Returns(mockSetEpisodio.Object);
EpisodiosService episodiosService = new EpisodiosService(mockContext.Object);
return mockContext;
}
Episodio.cs
public class Episodio : ModelBase
{
...
public Episodio()
{
nIdEstadoEpisodio = Enums.EstadoEpisodio.Ignorado;
lstIntEpisodios = new List<int>();
lstIntEpisodiosAbsolutos = new List<int>();
}
public bool IdentificarEpisodio()
{
...
EpisodiosService episodiosService = new EpisodiosService();
List<Episodio> lstEpisodios = episodiosService.GetLista(oSerie);
...
}
So, if in the test method i put some code like var service = new EpisodiosService(TesteHelper.GerarMassaDeDados()) and work with this service i would get the mocked content as intended, but there are some methods inside the some entities that consumes the service and i cannot pass the mocked context like at the Episodio.IdentificarEpisodio(), and if i create an instance of Episodio and call IdentificarEpisodio(), it will not use the mocked context because it isn't passed.
Is there a way to make the service use the mocked context without changing its signature (to IdentificarEpisodio(Context context) for exemple)?
I didn't want to change it's signature because there are a lot of methods that have this same problem and that i would have to change, and i don't think it would be nice to change it all...
Thanks in advance.
To my opinion best way to solve that issue will be usage of dependency injection (you can use ninject or any other lib for this). Then you will be able to configure what context to use in any case.
If you using ninject easier solution will be create interface IContext and pass it as parameter in to service constructors like:
public class EpisodiosService : IService<Episodio>
{
private Context _context;
public EpisodiosService(Context context)
{
_context = context;
}
...
}
Next step is to configure injection core, where you can set what implementation to use for each interface in constructor parameters of class, that will be injected.
For development project:
var kernel = new StandardKernel();
kernel.Bind<IContext>().To<Context>();
For unit tests:
var kernel = new StandardKernel();
kernel.Bind<IContext>().ToMethod(e => TesteHelper.GerarMassaDeDados());
Then you can get your services using this core:
var service = kernel.Get<EpisodiosService>();
In this way you will have required context for each case.
Please note that there are much more options to configure injection, for example you could inject in public properties marked with InjectAttribute or create more complex and general binding rules.
As easier solution you can just create some method CreateContext() that will return required type of context depending on some settings and use it in all your methods. For example:
Context CreateContext()
{
if (isTest)
return TesteHelper.GerarMassaDeDados();
return new Context();
}
But this solution is less flexible than dependency injection.
I have been reading Mark Seemann's excellent book on DI and hope to implement it in my next WPF project. However I have a query regarding object lifetime. So far, most examples seem to explain the repository pattern per request for MVC applications. In WPF there isn't really an alternative to this (I think). Seeing as the object graph of the entire application is constructed in the composition root, how can I make sure that my unit-of-work stuff is working properly. For example:
public class ContextFactory : IContextFactory
{
DBContext context;
public ContextFactory()
{
context = new MyDBContext();
}
public DBContext GetContext()
{
return context;
}
}
public class ItemOneRepository() : IItemOneRepository
{
DBContext context;
public ItemOneRepository(IContextFactory contextFactory)
{
this.context = contextFactory.GetContext();
}
public IEnumerable GetItems()
{
return context.ItemOnes;
}
}
public class ItemTwoRepository() : IItemTwoRepository
{
DBContext context;
public ItemTwoRepository(IContextFactory contextFactory)
{
this.context = contextFactory.GetContext();
}
public IEnumerable GetItemsByItemOneID(int itemOneID)
{
return context.ItemTwos.Where(i => i.itemOneID == itemOneID);
}
}
public class ThingService : IThingService
{
IItemOneRepository itemOneRepo;
IItemTwoRepository itemTwoRepo;
public ThingService(
IItemOneRepository itemOneRepository,
IItemTwoRepository itemTwoRepository)
{
itemOneRepo = itemOneRepository;
itemTwoRepo = itemTwoRepository;
}
public IEnumerable Things GetThing()
{
var ItemOnes = itemOneRepo.GetItems();
return ItemOnes.Select(i =>
new Thing(
i.FieldOne,
i.FieldFour,
itemRepoTwo.GetItemsByItemOneID(i.ID)
)
);
}
}
In this case the MyDBContext instance is created through ContextFactory in the composition root. ItemOneRepository and ItemTwoRepository are using the same unit-of-work (MyDBContext), but so is the rest of the application which is plainly wrong. What if I changed the repositories to accept a DBContext instead of ContextFactory and added a ThingServiceFactory class like:
public ThingServiceFactory : IThingServiceFactory
{
IContextFactory contextFactory;
public ThingServiceFactory(IContextFactory factory)
{
contextFactory = factory;
}
public IThingService Create()
{
MyDBContext context = contextFactory.Create();
ItemOneRepository itemOneRepo = new ItemOneRepository(context);
ItemOneRepository itemTwoRepo = new ItemTwoRepository(context);
return new ThingService(itemOneRepo, itemTwoRepo);
}
}
This is better as I can now pass the ThingServiceFactory to my ViewModels instead of an instance of ThingService (complete with DBContext). I can then create a unit-of-work whenever I need one and instantly dispose of it when I’ve finished. However, is this really the correct approach. Do I really need to write a factory for every unit-of-work operation I need? Surely there is a better way...
There's IMO only one good solution to this problem and that is to apply a command-based and query-based application design.
When you define a single ICommandHandler<TCommand> abstraction to define business transactions, you can inject closed versions of that interface into any form that needs this. Say for instance you have a "move customer" 'command' operation:
public class MoveCustomer
{
public Guid CustomerId;
public Address NewAddress;
}
And you can create a class that will be able to execute this command:
public class MoveCustomerHandler : ICommandHandler<MoveCustomer>
{
private readonly DBContext context;
// Here we simply inject the DbContext, not a factory.
public MoveCustomerHandler(DbContext context)
{
this.context = context;
}
public void Handle(MoveCustomer command)
{
// write business transaction here.
}
}
Now your WPF Windows class can depend on ICommandHandler<MoveCustomer> as follows:
public class MoveCustomerWindow : Window
{
private readonly ICommandHandler<MoveCustomer> handler;
public MoveCustomerWindows(ICommandHandler<MoveCustomer> handler)
{
this.handler = handler;
}
public void Button1Click(object sender, EventArgs e)
{
// Here we call the command handler and pass in a newly created command.
this.handler.Handle(new MoveCustomer
{
CustomerId = this.CustomerDropDown.SelectedValue,
NewAddress = this.AddressDropDown.SelectedValue,
});
}
}
Since MoveCustomerWindow lives for quite some time, it will drag on its dependencies for as long as it lives. If those dependencies shouldn't live that long (for instance your DbContext) you will be in trouble and Mark Seemann calls this problem Captive Dependency.
But since we now have a single ICommandHandler<TCommand> abstraction between our presentation layer and our business layer, it becomes very easy to define a single decorator that allows postponing the creation of the real MoveCustomerHandler. For instance:
public class ScopedCommandHandlerProxy<TCommand> : ICommandHandler<TCommand>
{
private readonly Func<ICommandHandler<TCommand>> decorateeFactory;
private readonly Container container;
// We inject a Func<T> that is able to create the command handler decoratee
// when needed.
public ScopedCommandHandlerProxy(
Func<ICommandHandler<TCommand>> decorateeFactory,
Container container)
{
this.decorateeFactory = decorateeFactory;
this.container = container;
}
public void Handle(TCommand command)
{
// Start some sort of 'scope' here that allows you to have a single
// instance of DbContext during that scope. How to do this depends
// on your DI library (if you use any).
using (container.BeginLifetimeScope())
{
// Create a wrapped handler inside the scope. This way it will get
// a fresh DbContext.
ICommandHandler<TCommand> decoratee =this.decorateeFactory.Invoke();
// Pass the command on to this handler.
decoratee.Handle(command);
}
}
}
This sounds a bit complex, but this completely allows you to hide the fact that a new DbContext is needed from the client Window and you hide this complexity as well from your business layer; you can simply inject a DbContext into your handler. Both sides know nothing about this little peace of infrastructure.
Of course you still have to wire this up. Without a DI library you do something like this:
var handler = new ScopedCommandHandlerProxy<MoveCustomerCommand>(
() => new MoveCustomerCommandHandler(new DbContext()),
container);
How to register this in a DI library is completely depending on the library of choice, but with Simple Injector you do it as follows:
// Register all command handler implementation all at once.
container.Register(
typeof(ICommandHandler<>),
typeof(ICommandHandler<>).Assembly);
// Tell Simple Injector to wrap each ICommandHandler<T> implementation with a
// ScopedCommandHandlerProxy<T>. Simple Injector will take care of the rest and
// will inject the Func<ICommandHandler<T>> for you. The proxy can be a
// singleton, since it will create the decoratee on each call to Handle.
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(ScopedCommandHandlerProxy<>),
Lifestyle.Singleton);
This is just one of the many advantages that this type of design gives you. Other advantages is that it makes much easier to apply all sorts of cross-cutting concerns such as audit trailing, logging, security, validation, de-duplication, caching, deadlock-prevention or retry mechanisms, etc, etc. The possibilities are endless.
ItemOneRepository and ItemTwoRepository are using the same
unit-of-work (MyDBContext), but so is the rest of the application
which is plainly wrong.
If your factory is registered with a transient lifecycle, you will get a new instance every time it's injected, which will be a new DBContext each time.
However, I would recommend a more explicit unit of work implementation:
public DBContext GetContext() //I would rename this "Create()"
{
return new MyDBContext();
}
And:
public IEnumerable GetItemsByItemOneID(int itemOneID)
{
using (var context = contextFactory.Create())
{
return context.ItemTwos.Where(i => i.itemOneID == itemOneID);
}
}
This gives you fine-grained control over the unit of work and transaction.
You might also ask yourself if the repositories are gaining you anything vs. just using the context directly via the factory. Depending on the complexity of your application, the repositories may be unnecessary overhead.
I have created a generic repository for my entity types which handles retrieving , adding and deleting data. Each entity type has a corresponding Service class which interacts with the generic repository to handle all the Data access.
However many times i need to retrieve data based on more than one service and i am never sure where to place this code. For example below is some code that returns a list of email addresses ("GetEmailTrackingAddressGroup" function) which is using 3 different service. I have placed this in the "GroupService" but it could also easily go in the "UserService" aswell.
public class GroupService
{
IRepository<Group> groupRepository;
public GroupService(IRepository<Group> groupRepository)
{
this.groupRepository = groupRepository;
}
public Group GetById(int id)
{
return groupRepository.GetSingle(g => g.Id == id);
}
public static List<string> GetEmailTrackingAddressesGroup(int instanceId, int groupId)
{
MyEntities entityContext = new MyEntities();
UserGroupService userGroupService =
new UserGroupService(new BaseRepoistory<UserGroup>(entityContext ));
UserService userService =
new UserService(new BaseRepoistory<User>(entityContext ));
List<string> emails = new List<string>();
Group productGroup = GetById(groupId);
foreach (UserGroup userGroup in userGroupService.GetByGroupId(productGroup.Id))
{
if (userGroup.EmailTracking)
emails.Add(userService.GetByUserId(userGroup.UserId).UserName);
}
return emails;
}
}
My Question is, should you just try and pick the most relevant service and place the code in there and call the other relevant service inside it, or should i create a new class which handles Data access when more than 1 service is involved. For example i have placed code for what this class might look like below.
public class DataFunctions
{
public static List<string> GetEmailTrackingAddressesGroup(int instanceId, int groupId)
{
MyEntities entityContext = new MyEntities();
GroupService userGroupService =
new GroupService(new BaseRepoistory<Group>(entityContext ));
UserGroupService userGroupService =
new UserGroupService(new BaseRepoistory<UserGroup>(entityContext ));
UserService userService =
new UserService(new BaseRepoistory<User>(entityContext ));
List<string> emails = new List<string>();
Group productGroup = GetById(groupId);
foreach (UserGroup userGroup in userGroupService.GetByGroupId(productGroup.Id))
{
if (userGroup.EmailTracking)
emails.Add(userService.GetByUserId(userGroup.UserId).UserName);
}
return emails;
}
}
The second approach seems to make more sense as this means each service will never rely on other services however im not sure if i am going about this the right way. One concern i have about using this separate class is that it will get very big and hard to manage.
Edit - For now i have come up with a third solution, i think it is better than my previous two however i'm still uncertain if i am managing this correctly. I have created a seperate "EmailService" which will handle all data queries which are needed when handing email functionality in my main ASP.Net web project.
Below is the code for this new class
//Functionality realting to data needed when handling emails
public class EmailService
{
MyEntities entityContext;
AspUserService aspUserService;
GroupService groupService;
UserGroupService userGroupService;
public EmailService()
{
entityContext = new MyEntities ();
aspUserService = new AspUserService(new RepositoryBase<aspnet_Users>(entityContext));
groupService = new GroupService(new RepositoryBase<Group>(entityContext));
userGroupService = new UserGroupService(new RepositoryBase<UserGroup>(entityContext));
}
public List<string> GetEmailsForProductGroup(int groupId)
{
List<string> emails = new List<string>();
Group productGroup = groupService.GetById(groupId);
foreach (UserGroup userGroup in userGroupService.GetByGroupId(productGroup.Id))
{
if (userGroup.EmailTracking)
emails.Add(aspUserService.GetByUserId(userGroup.UserId).UserName);
}
return emails;
}
}
If you were to do maintenance on an application you've never seen before, where would you expect it to be? Since it is something that "belongs" to a group, i think that putting it in the group-repo/service would be the right approach. In your example, i would suggest creating a new group-repository, extending the IRepository of group, and create a method for getting the group object, including the connection entities. When scaling your application, this will make a huge difference, since you wont have to query the database for every subobject (n+1 problem).
No offense, but all your approaches suck for the following reasons: tight coupling and messing around responsibilities.
The generic repository is an anti pattern, stay away from it. Your first approach pretty much does the work of a repository and it's a static function (why?!).
The services shouldn't be coupled to concrete repositories. All dependencies should be injected as abstractions via constructor. I get the feeling that aspUsersService,GroupService and UserGroupService (what's the difference????) are actually implemented like a repository and thus, they are useless.
Actually from what I see, all your services are practically repositories. Cut the useless code, have one service/repository that uses directly EF and that's it.
It looks like you're a little confused between the point of repositories and service classes. Your service classes are just repositories. You ended up having to work this way because of the generic repository pattern. This pattern is actually an anti-pattern. It seems nice at first until you get to the point you're at. Instead you should create repositories for each of your entities that handle all CRUD operations for that entity. In these repositories is where you'll place your 'GetEmailTrackingAddressesGroup' type methods. And then your service layer can handle interacting with more that one repository if need be. Your service classes shouldn't have hard coded instances of your repositories. Instead you should be inject repository interfaces into your service's constructor.
Here's an example of how I would set up repositories and a simple service that interacts with 2 repositories.
public interface IUserRepository
{
void Insert(User user);
...
IEnumerable<User> GetByDepartmentId(int deptId);
}
public interface IContactLogRepository
{
void Insert(ContactLog contactLog);
}
public class EmailService
{
private readonly IUserRepository _userRepo;
private readonly IContactLogRepository _contactLogRepo;
public EmailService(IUserRepository userRepo, IContactLogRepository contLogRepo) {
_userRepo = userRepo;
_contactLogRepo = contLogRepo;
}
public void EmailDepartment(int deptId, string message) {
var employees = _userRepo.GetByDepartmentId(deptId);
foreach (var emp in employees) {
Email(emp.Email, message);
_contactLogRepo.Insert(new ContactLog {
EmployeeId = emp.Id,
Message = message
});
}
}
private void Email(string address, string message) {
...
}
}
So our repositories are there to handle CRUD operations for a specific entity - not our service layer. The generic repository pattern forces us to do CRUD (well at least the retrieval) in our services.