且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2023-02-13 15:41:02

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&gt ;, 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.

如果有多个单独的 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.