将多个DbContext与通用存储库和工作单元一起使用 [英] Using multiple DbContexts with a generic repository and unit of work

查看:132
本文介绍了将多个DbContext与通用存储库和工作单元一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 split them up into multiple DbContext, like MainDbContext, EstateModuleDbContext, AnotherModuleDbContext and UserDbContext.

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

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:

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

我应该放弃这种依赖注入方法,并显式设置 DbContext 我希望在以下服务上使用:

Should I drop this approach with dependency injection and explicit set the DbContext I wish to use on my services like:

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 传递给我的服务-还有另一种(更好的)方法吗?

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?

代码

我有以下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();
    }
}

以及处理以下内容的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除非有合乎逻辑的接缝,否则请勿将模块化数据片段拆分为多个 DbContext DbContextA 中的实体不能具有 DbContextB 中的实体的自动导航或集合属性。如果拆分上下文,则您的代码将必须负责手动执行约束并在上下文之间加载相关数据。

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.

出于概述的考虑(也就是保持理智) ),您仍然可以按模块组织CLR代码和数据库表。对于POCO,请将其保存在不同名称空间下的不同文件夹中。对于表,可以按架构分组。 (但是,在通过SQL模式进行组织时,您可能还应该考虑安全性。例如,如果有任何数据库用户应限制对某些表的访问,请根据这些规则设计模式。)然后,您可以执行此操作建立模型时:

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

仅与单独的 DbContext一起使用当它的实体与您的第一个 DbContext 没有任何关系时。

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

I我也同意Wiktor,因为我不喜欢您的界面&实施设计。我尤其不喜欢公用接口IRepository< T> 。另外,为什么要声明多个 public DbSet< TableN> ;, TableN {get;组; } 在您的 MyDbContext 中?帮我一个忙,阅读这篇文章,然后阅读这一个

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.

您可以使用如下所示的EF接口设计大大简化代码:

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
}

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

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.

如果有多个单独的<$应用中的c $ c> DbContext 类曾经是有意义的,您可以按照Wiktor的建议进行操作并使上述接口通用。然后,您可以像这样将依赖项注入服务中:

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

传递给服务构造函数(甚至更糟的是每个实体)存储库接口可以与EF对抗,增加无聊的管道代码,并过度注入构造函数。相反,给您的服务更大的灵活性。像 .Any()这样的方法不属于该接口,您可以在 IQueryable< T> 由您的服务方法中的 Query< T> FindMany< T> 返回。

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.

这篇关于将多个DbContext与通用存储库和工作单元一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆