Using multiple DbContexts with a generic repository and unit of work

前端 未结 2 743
孤城傲影
孤城傲影 2021-01-31 06:59

My application is getting larger and so far I have a single MyDbContext which has all the tables I need in my application. I wish (for the sake of overview) to spli

相关标签:
2条回答
  • 2021-01-31 07:16

    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.

    0 讨论(0)
  • 2021-01-31 07:28

    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.

    0 讨论(0)
提交回复
热议问题