【问题标题】:Using multiple DbContexts with a generic repository and unit of work将多个 DbContext 与通用存储库和工作单元一起使用
【发布时间】:2013-12-12 18:22:36
【问题描述】:

我的应用程序越来越大,到目前为止,我只有一个 MyDbContext,其中包含我应用程序中需要的所有表。我希望(为了概述)将它们分成多个DbContext,例如MainDbContextEstateModuleDbContextAnotherModuleDbContextUserDbContext

我不确定这是如何完成的,因为我现在正在使用依赖注入 (ninject) 将我的 DbContext 放在我的 UnitOfWork 类上,例如:

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

我是否应该放弃这种依赖注入的方法并明确设置我希望在我的服务上使用的DbContext,例如:

private readonly EstateService _estateService;

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

代替:

private readonly EstateService _estateService;

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

或者这还有其他更好的方法吗?另外作为一个附带问题,我不喜欢将uow 传递给我的服务 - 还有另一种(更好的)方法吗?

代码

我有这个 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)
    {

    }
}

然后我有这个 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();
    }
}

以及处理使用 DbContext 完成的工作的 IUnitOfWork 和实现

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;
    }
} 

【问题讨论】:

  • 是否有任何逻辑接缝可以将数据拆分到多个 dbcontexts 上?如果这样做,您将无法拥有跨上下文的导航或集合属性(例如,您无法在 MainDbContext 中的实体与 UsersDbContext 中的另一个实体之间建立 EF 关系)。
  • 无论如何要拆分上下文的原因是什么?此外,如果您拆分它们,则不能使用 DI。我也不喜欢你的工作单元中有存储库(字典的东西)
  • 我将在我的应用程序中拥有多个模块,只是为了不让大量模块 DbSet 污染 MyDbContext。也许我应该忽略它?
  • 嗨,丹,你能提出一个更好的方法吗?我愿意接受建议
  • 我想这样做是因为 'IdentityDbContext' (MVC5 & OWIN) 包含我的 'AppDbContext' 的事务。 UserManager 很实用,但在更改两者之间松散耦合的关系数据时,我想要更多的控制。

标签: c# entity-framework repository-pattern dbcontext unit-of-work


【解决方案1】:

不要将您的模块化数据片段拆分为多个 DbContexts,除非有这样做的逻辑接缝。 DbContextA 中的实体不能与DbContextB 中的实体具有自动导航或集合属性。如果您拆分上下文,您的代码将不得不负责手动执行约束并在上下文之间加载相关数据。

为了“概览”(也就是保持理智),您仍然可以按模块组织 CLR 代码和数据库表。对于 POCO,将它们保存在不同名称空间下的不同文件夹中。对于表,您可以按模式分组。 (但是,在按 SQL 模式组织时,您可能还应该考虑安全因素。例如,如果有任何 db 用户应该限制对某些表的访问,请根据这些规则设计模式。)然后,您可以这样做构建模型时:

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

仅当其实体与您的第一个 DbContext 中的任何实体没有关系时,才使用单独的 DbContext

我也同意 Wiktor 的观点,我不喜欢您的界面和实现设计。我特别不喜欢public interface IRepository&lt;T&gt;。另外,为什么要在您的MyDbContext 中声明多个public DbSet&lt;TableN&gt; TableN { get; set; }?帮我一个忙,阅读this article,然后阅读this one

您可以使用这样的 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
}

如果你声明MyDbContext : ICommandEntities,你只需要设置几个方法来实现接口(通常是单行)。然后,您可以将 3 个接口中的任何一个注入到您的服务实现中:通常ICommandEntities 用于具有副作用的操作,IQueryEntities 用于没有副作用的操作。任何只负责保存状态的服务(或服务装饰器)都可以依赖IUnitOfWork。我不同意Controllers 应该依赖IUnitOfWork。使用上述设计,您的服务应在返回Controller 之前保存更改。

如果您的应用程序中有多个单独的DbContext 类有意义,您可以do as Wiktor suggests 并使上述接口通用。然后,您可以像这样将依赖项注入到服务中:

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

创建广泛的每个聚合(甚至更糟糕的每个实体)存储库接口可能会与 EF 冲突,增加无聊的管道代码,并过度注入您的构造函数。相反,为您的服务提供更大的灵活性。 .Any() 之类的方法不属于该接口,您可以在服务方法中调用 Query&lt;T&gt;FindMany&lt;T&gt; 返回的 IQueryable&lt;T&gt; 的扩展。

【讨论】:

  • 谢谢你,用你上面的代码——我的 DbContext 会是什么样子——它应该只实现 ICommandEntities 吗?我不确定我是否得到它。我的示例(第一篇文章)基于gaui.is/how-to-mock-the-datacontext-entity-framework - 也许这不是最好的方法。
  • “最佳方法”是有争议的。我还没有看到比上述更好的界面模型仅基于我从事的项目类型(大中型)。您实际上实现了DbContext 上的所有 3 个接口,但由于 ICommandEntities 实现了其他 2 个接口,您只需要在类签名中指定它。
  • 请问这些 ICommandEntities 在实践中是如何使用的会不会太过分了?我想我不介意不使用存储库类
【解决方案2】:

您的工作单元接口不是通用的,但实现是通用的。最简单的清理方法是决定并遵循相同的约定。

例如,让你的界面也通用。这样,您可以将三个不同的接口(具有三个不同泛型参数的同一个接口)注册到三个不同的实现:

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

是的,将工作单元注入控制器/服务是一个好主意。

【讨论】:

  • 如何重构 IUnitOfWork 以使其通用?有什么建议吗?
猜你喜欢
  • 2014-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-27
  • 1970-01-01
相关资源
最近更新 更多