在实体框架中创建通用DbContext工厂

时间:2019-02-18 14:04:02

标签: c# asp.net entity-framework asp.net-core-2.1

我正在使用.Net Core 2.1。我正在使用多个DbContext。我为每个上下文创建一个DbContextFactory。但是,我想以一种通用的方式做到这一点。我只想创建一个DbContextFactory。我该如何实现?

MyDbContextFactory.cs

public interface IDbContextFactory<TContext> where TContext : DbContext
{
    DbContext Create();
}

public class MyDbContextFactory : IDbContextFactory<MyDbContext>
{
    public IJwtHelper JwtHelper { get; set; }

    public MyDbContextCreate()
    {
        return new MyDbContext(this.JwtHelper);
    }

    DbContext IDbContextFactory<MyDbContext>.Create()
    {
        throw new NotImplementedException();
    }
}

UnitOfWork.cs

public class UnitOfWork<TContext> : IUnitOfWork<TContext> where TContext : DbContext
{
    public static Func<TContext> CreateDbContextFunction { get; set; }
    protected readonly DbContext DataContext;

    public UnitOfWork()
    {
        DataContext = CreateDbContextFunction();
    }
 }

MyDbContext.cs

public class MyDbContext : DbContext
{
    private readonly IJwtHelper jwtHelper;

    public MyDbContext(IJwtHelper jwtHelper) : base()
    {
        this.jwtHelper = jwtHelper;
    }
}

2 个答案:

答案 0 :(得分:2)

因此,您有一个数据库和一个表示该数据库的类:DbContext,它应该表示表以及数据库中表之间的关系,仅此而已。

您决定将数据库上的操作与数据库本身分开。这是一件好事,因为如果您的数据库的多个用户想要执行同一操作,则他们可以重复使用代码来执行此操作。

例如,如果您要创建“具有多个OrderLine的客户订单,其中包含订购的产品,约定的价格,金额等”,则需要对数据库做几件事:检查客户是否已经存在,请检查所有产品是否已经存在,检查是否有足够的物品,等等。

这些事情通常是您不应在DbContext中实现的,而应在单独的类中实现的。

如果您添加类似CreateOrder的功能,则几个用户可以重新使用此功能。您只需要测试一次,并且如果您决定更改订单模型中的某些内容,则只有一个地方需要更改订单的创建。

分离代表数据库的类的其他优点(DbContext) 从处理该数据的类中得出的结论是,无需更改数据库用户即可更轻松地更改内部结构。您甚至可以决定从Dapper更改为Entity Framework,而不必更改用法。这也使得为测试目的模拟数据库更加容易。

CreateOrder,QueryOrder,UpdateOrder之类的功能已经表明它们不是通用数据库操作,它们是针对Ordering数据库而不是School数据库的。

这可能会导致工作单元可能不是您要在单独的类中使用的功能的专有名称。几年前,工作单元主要是要表示数据库而不是特定数据库上的操作,对此我不太确定,因为我很快就看到一个真正的工作单元类可以不能增强我的DbContext的功能。

您经常会看到以下内容:

  • 代表您的数据库的DbContext类:您创建的数据库,而不是任何一般的数据库概念
  • 一个Repository类,代表将数据存储在某个地方,如何存储的想法,它可以是DbContext,也可以是CSV文件,或者是为测试目的而创建的Dictionary类的集合。此存储库具有一个IQueryable,并具有添加/更新/删除的功能(根据需要
  • 一个代表您问题的类:订购模型:添加订单/更新订单/获取客户订单:此类真正了解有关订单的所有信息,例如,它具有OrderTotal,可能找不到该位置在您的订购数据库中。

在DbContext之外,有时您可能需要SQL,例如,以提高调用效率。在外部存储库中,不应看到您正在使用SQL

考虑将关注点分开:如何保存数据(DbContext),如何对数据进行CRUD(创建,提取,更新等)(存储库),如何使用数据(组合表)

我认为您要在工作单元中执行的操作应在存储库中完成。您的Ordering类应该创建存储库(创建DbContext),查询几个项目以检查其必须添加/更新的数据,执行添加和更新并保存更改。之后,您的订购类应处理存储库,该存储库又将处理DbContext。

Repository类将与DbContext类非常相似。它具有代表表的几组。每个集合都将实现IQueryable<...>,并根据需要添加/更新/删除。

由于功能上的相似性,您可以省略Repository类,而让Ordering类直接使用DbContext。但是,请记住,如果将来您决定不再使用实体框架,而是使用一些较新的概念,或者不再使用Dapper,甚至更低级别,则更改的范围将会更大。 SQL将渗入您的Ordering类

选择什么

我认为您应该自己回答几个问题:

  • 确实只有一个数据库应该由您的DbContext表示,是否可能是在第二个具有相同布局的数据库中使用了相同的DbContext。考虑一下测试数据库或开发数据库。让程序创建要使用的DbContext会更容易/更容易测试/更容易改变吗?
  • 您的DbContext是否确实存在一组用户:每个人都应该可以删除吗?去创造?可能是某些程序只想查询数据(通过电子邮件发送定单的程序),而定单程序需要添加“客户”。也许另一个程序需要添加和更新产品,以及仓库中的产品数量。考虑为它们创建不同的存储库类。每个存储库都具有相同的DbContext,因为它们都访问同一个数据库
  • 类似地:仅一个数据处理类(上述订购类):处理订单的流程是否应该能够更改产品价格并向库存添加商品?

之所以需要工厂,是因为您不想让您的“主”程序决定为当前运行目的应创建哪些项目。如果您自己创建项目,则代码会容易得多:

订购过程的创建顺序:

 IJwtHelper jwtHelper = ...;

 // The product database: all functionality to do everything with Products and Orders
 ProductDbContext dbContext = new ProductDbContext(...)
 {
    JwtHelper = jwtHelper,
    ...
 };

 // The Ordering repository: everything to place Orders,
 // It can't change ProductPrices, nor can it stock the wharehouse
 // So no AddProduct, not AddProductCount,
 // of course it has TakeNrOfProducts, to decrease Stock of ordered Products
 OrderingRepository repository = new OrderingRepository(...) {DbContext = dbContext};

 // The ordering process: Create Order, find Order, ...
 // when an order is created, it checks if items are in stock
 // the prices of the items, if the Customer exists, etc.
 using (OrderingProcess process = new OrderingProcess(...) {Repository = repository})
{
     ... // check if Customer exists, check if all items in stock, create the Order
     process.SaveChanges();
}

处置进程时,处置库也将处置,而处置库又处置DbContext。

通过电子邮件发送订单的过程类似:它不必检查产品或创建客户,它只需要获取数据,并可能更新已通过电子邮件发送过的订单,或-邮寄失败。

 IJwtHelper jwtHelper = ...;

 // The product database: all functionality to do everything with Products and Orders
 ProductDbContext dbContext = new ProductDbContext(...) {JwtHelper = jwtHelper};

 // The E-mail order repository: everything to fetch order data
 // It can't change ProductPrices, nor can it stock the wharehouse
 // It can't add Customers, nor create Orders
 // However it can query a lot: customers, orders, ...
 EmailOrderRepository repository = new EmailOrderRepository(...){DbContext = dbContext};

 // The e-mail order process: fetch non-emailed orders,
 // e-mail them and update the e-mail result
 using (EmailOrderProcess process = new EmailOrderProcess(...){Repository = repository}
 {
     ... // fetch the orders that are not e-mailed yet
         // email the orders
         // warning about orders that can't be emailed
         // update successfully logged orders
     repository.SaveChanges();

查看创建过程的简便程度,创建过程的多功能程度:为DbContext提供不同的JwtHelper,并以不同的方式记录数据,为存储库提供不同的DbContext,并将数据保存在其他数据库中,为流程提供一个不同的存储库,然后您将使用Dapper执行查询。

测试将更容易:创建一个使用列表保存表的存储库,并使用测试数据对您的过程进行测试将很容易

数据库更改将更容易。例如,如果您以后决定将数据库分为一个数据库,分别用于库存和股票价格,一个数据库用于“客户和订单”,则只需更改一个存储库。所有流程都不会注意到此更改。

结论

不要让类决定所需的对象。让该类的创建者说:“嘿,您需要一个DbContext?使用它!”这将省略工厂等的需求

将实际数据库(DbContext)与存储和检索数据(Repository)的概念分开,使用一个单独的类来处理数据,而不知道如何存储或检索该数据(过程类)

创建几个存储库,这些存储库只能访问执行任务所需的数据(+在预期更改后可以预见的数据)。不要创建太多的存储库,但是也不能创建任何可以完成所有工作的存储库。

创建仅执行其预期工作的流程类。不要创建一个包含20个不同任务的流程类。这样只会更加难以描述应执行的操作,更加难以测试和更改任务

答案 1 :(得分:0)

如果要重用现有的实现,EF Core 5现在提供了开箱即用的DbContext Factory: https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#dbcontextfactory

请确保正确处理它,因为它的实例不受应用程序服务提供商的管理。