Using multiple DbContexts with a generic repository and unit of work

非 Y 不嫁゛ 提交于 2019-12-02 17:47:59
danludwig

Don't split your modular data pieces into multiple DbContexts unless there are logical seams for doing so. Entities from DbContextA cannot have automatic navigation or collection properties with entities in DbContextB. If you split the context, your code would have to be responsible for manually enforcing constraints and loading related data between contexts.

For "sake of overview" (a.k.a. keeping your sanity), you can still organize your CLR code and database tables by module. For the POCO's, keep them in different folders under different namespaces. For tables, you can group by schema. (However you probably should also take security considerations into account when organizing by SQL schema. For example, if there are any db users that should have restricted access to certain tables, design the schemas according to those rules.) Then, you can do this when building the model:

ToTable("TableName", "SchemaName"); // put table under SchemaName, not dbo

Only go with a separate DbContext when its entities have no relationships with any entities in your first DbContext.

I also agree with Wiktor in that I don't like your interface & implementation design. I especially don't like public interface IRepository<T>. Also, why declare multiple public DbSet<TableN> TableN { get; set; } in your MyDbContext? Do me a favor, read this article, then read this one.

You can greatly simplify your code with an EF interface design like this:

interface IUnitOfWork
{
    int SaveChanges();
}
interface IQueryEntities
{
    IQueryable<T> Query<T>(); // implementation returns Set<T>().AsNoTracking()
    IQueryable<T> EagerLoad<T>(IQueryable<T> queryable, Expression<Func<T, object>> expression); // implementation returns queryable.Include(expression)
}
interface ICommandEntities : IQueryEntities, IUnitOfWork
{
    T Find<T>(params object[] keyValues);
    IQueryable<T> FindMany<T>(); // implementation returns Set<T>() without .AsNoTracking()
    void Create<T>(T entity); // implementation changes Entry(entity).State
    void Update<T>(T entity); // implementation changes Entry(entity).State
    void Delete<T>(T entity); // implementation changes Entry(entity).State
    void Reload<T>(T entity); // implementation invokes Entry(entity).Reload
}

If you declare MyDbContext : ICommandEntities, you just have to set up a few methods to implement the interface (usually one-liners). You can then inject any of the 3 interfaces into your service implementations: usually ICommandEntities for operations that have side effects, and IQueryEntities for operations that don't. Any services (or service decorators) responsible only for saving state can take a dependency on IUnitOfWork. I disagree that Controllers should take a dependency on IUnitOfWork though. Using the above design, your services should save changes before returning to the Controller.

If having multiple separate DbContext classes in your app ever makes sense, you can do as Wiktor suggests and make the above interfaces generic. You can then dependency inject into services like so:

public SomeServiceClass(IQueryEntities<UserEntities> users,
    ICommandEntities<EstateModuleEntities> estateModule) { ... }

public SomeControllerClass(SomeServiceClass service) { ... }

// Ninject will automatically constructor inject service instance into controller
// you don't need to pass arguments to the service constructor from controller

Creating wide per-aggregate (or even worse per-entity) repository interfaces can fight with EF, multiply boring plumbing code, and over-inject your constructors. Instead, give your services more flexibility. Methods like .Any() don't belong on the interface, you can just call extensions on the IQueryable<T> returned by Query<T> or FindMany<T> from within your service methods.

Your unit of work interface is not generic but the implementation is. The easiest way to clean up this would be to decide and follow the same convention.

For example, make your interface generic also. This way you could register three different interfaces (the same interface with three different generic parameters) to three different implementations:

 container.Bind( typeof<IUnitOfWork<ContextOne>> ).To( typeof<UnitOfWork<ContextOne>> );
 ...

And yes, this is a good idea to inject your unit of works into controllers / services.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!