简体   繁体   English

将多个DbContext与通用存储库和工作单元一起使用

[英]Using multiple DbContexts with a generic repository and unit of work

My application is getting larger and so far I have a single MyDbContext which has all the tables I need in my application. 我的应用程序越来越大,到目前为止,我只有一个MyDbContext ,其中包含我的应用程序中需要的所有表。 I wish (for the sake of overview) to split them up into multiple DbContext , like MainDbContext , EstateModuleDbContext , AnotherModuleDbContext and UserDbContext . 我希望(出于概述的目的)将它们分成多个DbContext ,例如MainDbContextEstateModuleDbContextAnotherModuleDbContextUserDbContext

I am unsure how this is done probably as I am right now using dependecy injection (ninject) to place my DbContext on my UnitOfWork class like: 我不确定如何完成此操作,因为我现在正在使用依赖注入(ninject)将DbContext放在UnitOfWork类上,例如:

kernel.Bind(typeof(IUnitOfWork)).To(typeof(UnitOfWork<MyDbContext>));

Should I drop this approach with dependency injection and explicit set the DbContext I wish to use on my services like: 我应该放弃依赖注入这种方法,并显式设置要在服务中使用的DbContext例如:

private readonly EstateService _estateService;

public HomeController()
{
    IUnitOfWork uow = new UnitOfWork<MyDbContext>();
    _estateService = new EstateService(uow);
}

Instead of: 代替:

private readonly EstateService _estateService;

public HomeController(IUnitOfWork uow)
{
    _estateService = new EstateService(uow);
}

Or this there another better approach? 还是这还有另一种更好的方法? Also as a side question, I dont like passing the uow to my service - is there another (better) approach? 还有一个附带的问题,我不喜欢将uow通知我-服务是否有另一种(更好的)方法?

Code

I have this IDbContext and MyDbContext: 我有这个IDbContext和MyDbContext:

public interface IDbContext
{
    DbSet<T> Set<T>() where T : class;

    DbEntityEntry<T> Entry<T>(T entity) where T : class;

    int SaveChanges();

    void Dispose();
}

public class MyDbContext : DbContext, IDbContext
{
    public DbSet<Table1> Table1 { get; set; }
    public DbSet<Table2> Table1 { get; set; }
    public DbSet<Table3> Table1 { get; set; }
    public DbSet<Table4> Table1 { get; set; }
    public DbSet<Table5> Table1 { get; set; }
    /* and so on */

    static MyDbContext()
    {
        Database.SetInitializer<MyDbContext>(new CreateDatabaseIfNotExists<MyDbContext>());
    }

    public MyDbContext()
        : base("MyDbContext")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {

    }
}

Then I have this IRepository and the implementation: 然后我有这个IRepository和实现:

public interface IRepository<T> where T : class
{
    IQueryable<T> GetAll();

    void Add(T entity);

    void Delete(T entity);

    void DeleteAll(IEnumerable<T> entity);

    void Update(T entity);

    bool Any();
}

public class Repository<T> : IRepository<T> where T : class
{
    private readonly IDbContext _context;
    private readonly IDbSet<T> _dbset;

    public Repository(IDbContext context)
    {
        _context = context;
        _dbset = context.Set<T>();
    }

    public virtual IQueryable<T> GetAll()
    {
        return _dbset;
    }

    public virtual void Add(T entity)
    {
        _dbset.Add(entity);
    }

    public virtual void Delete(T entity)
    {
        var entry = _context.Entry(entity);
        entry.State = EntityState.Deleted;
        _dbset.Remove(entity);
    }

    public virtual void DeleteAll(IEnumerable<T> entity)
    {
        foreach (var ent in entity)
        {
            var entry = _context.Entry(ent);
            entry.State = EntityState.Deleted;
            _dbset.Remove(ent);
        }
    }

    public virtual void Update(T entity)
    {
        var entry = _context.Entry(entity);
        _dbset.Attach(entity);
        entry.State = EntityState.Modified;
    }

    public virtual bool Any()
    {
        return _dbset.Any();
    }
}

And the IUnitOfWork and implemention which handles the work done with the DbContext 还有IUnitOfWork和实现,它处理DbContext完成的工作

public interface IUnitOfWork : IDisposable
{
    IRepository<TEntity> GetRepository<TEntity>() where TEntity : class;

    void Save();
}

public class UnitOfWork<TContext> : IUnitOfWork where TContext : IDbContext, new()
{
    private readonly IDbContext _ctx;
    private readonly Dictionary<Type, object> _repositories;
    private bool _disposed;

    public UnitOfWork()
    {
        _ctx = new TContext();
        _repositories = new Dictionary<Type, object>();
        _disposed = false;
    }

    public IRepository<TEntity> GetRepository<TEntity>() where TEntity : class
    {
        // Checks if the Dictionary Key contains the Model class
        if (_repositories.Keys.Contains(typeof(TEntity)))
        {
            // Return the repository for that Model class
            return _repositories[typeof(TEntity)] as IRepository<TEntity>;
        }

        // If the repository for that Model class doesn't exist, create it
        var repository = new Repository<TEntity>(_ctx);

        // Add it to the dictionary
        _repositories.Add(typeof(TEntity), repository);

        return repository;
    }

    public void Save()
    {
        _ctx.SaveChanges();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (this._disposed) return;

        if (disposing)
        {
            _ctx.Dispose();
        }

        this._disposed = true;
    }
} 

Don't split your modular data pieces into multiple DbContext s unless there are logical seams for doing so. 除非有逻辑上的接缝,否则请勿将模块化数据片段拆分为多个DbContext Entities from DbContextA cannot have automatic navigation or collection properties with entities in DbContextB . 从实体DbContextA不能与实体自动导航或集合属性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" (aka keeping your sanity), you can still organize your CLR code and database tables by module. 对于“概述”(也就是保持理智),您仍然可以按模块组织CLR代码和数据库表。 For the POCO's, keep them in different folders under different namespaces. 对于POCO,请将其保存在不同名称空间下的不同文件夹中。 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: (但是,在通过SQL模式进行组织时,您可能还应该考虑安全性。例如,如果有任何数据库用户应限制对某些表的访问,请根据这些规则设计模式。)然后,您可以执行此操作建立模型时:

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 . 只有一个单独去DbContext当它的实体有在你第一次的任何实体没有关系DbContext

I also agree with Wiktor in that I don't like your interface & implementation design. 我也同意Wiktor,因为我不喜欢您的界面和实现设计。 I especially don't like public interface IRepository<T> . 我特别不喜欢public interface IRepository<T> Also, why declare multiple public DbSet<TableN> TableN { get; set; } 另外,为什么还要声明多个public DbSet<TableN> TableN { get; set; } public DbSet<TableN> TableN { get; set; } public DbSet<TableN> TableN { get; set; } in your MyDbContext ? public DbSet<TableN> TableN { get; set; }在您的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: 您可以使用如下所示的EF接口设计极大地简化代码:

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). 如果声明MyDbContext : ICommandEntities ,则只需设置一些方法来实现该接口(通常是单行)。 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. 然后,您可以IQueryEntities 3个接口中的任何一个注入到服务实现中:通常, ICommandEntities用于具有副作用的操作,而IQueryEntities用于没有副作用的操作。 Any services (or service decorators) responsible only for saving state can take a dependency on IUnitOfWork . 任何仅负责保存状态的服务(或服务装饰器)都可以依赖IUnitOfWork I disagree that Controller s should take a dependency on IUnitOfWork though. 我不同意Controller尽管应该依赖IUnitOfWork Using the above design, your services should save changes before returning to the Controller . 使用以上设计,您的服务应在返回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. 如果您的应用中有多个单独的DbContext类有意义,则可以按照Wiktor的建议进行操作 ,并使上述接口通用。 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. 创建宽的每个聚合(甚至每个实体更糟)的存储库接口可以与EF对抗,增加无聊的管道代码,并过度注入构造函数。 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. 诸如.Any()类的方法不属于该接口,您可以在服务方法中调用Query<T>FindMany<T>返回的IQueryable<T>上的扩展。

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. 是的,将您的工作单元注入控制器/服务中是一个好主意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM