与所有存储库/服务类共享我的dbContext吗?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了与所有存储库/服务类共享我的dbContext吗?相关的知识,希望对你有一定的参考价值。

我正在研究经典的.Net Framework Web API解决方案。

我有3层。叫他们

  • MVC-使用POST,GET,UPDATE,DELETE控制器。
  • BIZZ-适用于我的服务等级的企业。我的服务类是具有CREATE,READ,UPDATE,DELETE和特定方法的存储库之王。
  • 数据-具有POCO和数据库上下文的定义。

我不会开发EF层。这是带有POCO的经典Entity Framework项目。这里是Service的示例,并带有BaseService类]

public abstract class Service : IDisposable
{
    protected DbContext dbContext = new DbContext();

    public void Dispose()
    {
        dbContext.Dispose();        
    }
}

然后我有购物车服务和订购服务。它们的结构相似,因此我只写对本示例有用的代码。

    public class CartService : Service
    {

        public Cart Create(Cart cart)
        {
            // Create the cart
        }

        public Cart Read(Guid id)
        {
            // Read
        }

        public Cart Update(Cart cart)
        {
            // I do some check first then
        }

        public void Delete(Cart cart)
        { 
            // Delete
        }

        public void Checkout(Cart cart)
        { 
            // Validation of cart removed in this example
            dbContext.Cart.Attach(cart);
            cart.DateCheckout = DateTime.UtcNow;
            dbContext.Entry(cart).State = EntityState.Modified; // I think this line can be removed
            dbContext.SaveChanges();
           using (var orderService = new OrderService())
           {
                foreach (var order in cart.Orders)
                {
                     order.DateCheckout = cart.DateCheckout;
                     order.Status = OrderStatus.PD; // pending
                     orderService.Update(order); 
                }
            }
        }
    }

    public class OrderService : Service
    {

        public Cart Create(Cart cart)
        {
            // Create the cart
        }

        public Cart Read(Guid id)
        {
            // Read
        }

        public Cart Update(Cart cart)
        {
            dbContext.Entry(order).State = EntityState.Modified;
            dbContext.SaveChanges();
            // More process here...
            return order;
        }

        public void Delete(Cart cart)
        { 
            // Delete
        }
    }

因此,我有一项服务,即购物车服务,该服务称为另一项服务,即订购服务。我必须像这样工作,因为我不能简单地接受购物车及其中的所有订单。保存新订单或更新现有订单时,我必须在其他数据库的某些其他表中创建一条记录。该代码不在我的示例中。所以,我重复说,我有一个调用另一个服务的服务,然后有2个dbContext。最好的情况是在内存中创建2个上下文,最坏的情况下会创建异常。异常,例如您无法将实体附加到2个上下文,或者该实体不在上下文中。

嗯,我希望我所有的服务都使用相同的上下文。我想您会告诉我使用依赖注入。是的,很好,但是我不希望,每次创建新服务时都必须传递上下文。我不想这样做:

    public void Checkout(Cart cart)
    { 
        // ...
       using (var orderService = new OrderService(dbContext))
       {
            // ...
        }
    }

我只想做可能会影响我的基本服务的事情。可能是一个单身汉...此时,我可以看到您的脸。是的,我知道Singleton太糟糕了。是的,但是我正在使用IIS Web API。每个请求都是一个新实例。我不在乎单例的影响。而且我可以通过更改配置文件中的连接字符串来加载数据库,因此DI的好处已经存在。好吧,我也知道DI可能有单身人士。我就是不知道

所以,我该怎么做以确保我与所有服务共享dbContext?

答案

免责声明:此示例并非旨在作为一个“好的”示例,并且肯定没有遵循最佳实践,但是面对现有的旧代码库,而您的示例已经遭受了许多可疑的实践的困扰,这应该会让您过去多个上下文问题。

基本上,如果您尚未使用IoC容器执行依赖项注入,那么您需要引入一个工作单元来管理DbContext的范围,其中基础Service类将提供该工作单元提供的DbContext。 (本质上是DbContext注册表)

对于工作单元,假设使用EF6,我建议使用Mehdime的DbContextScope,它可以作为NuGet包提供。另外,您可以在Github上找到源代码并实现类似的东西而不会带来太多麻烦。我喜欢这种模式,因为它利用CallContext作为DbContextScopeFactory创建的ContextScope(工作单元)和AmbientDbContextScope之间的通信层。这可能需要花费一些时间,但是它可以很好地注入到您想要利用工作单元并且没有依赖项注入的旧应用程序中。

它将是什么样子:

在您的Service类中,您将引入AmbientDbContextLocator来解析您的DbContext:

private readonly IAmbientDbContextLocator _contextLocator = new AmbientDbContextLocator();

protected DbContext DbContext
{
    get { return _contextLocator.Get<DbContext>(); }
}

就是这样。稍后当您重构以适应依赖项注入时,只需注入AmbientDbContextLocator即可,而不是对其进行“新”处理。

然后,在使用服务的Web API控制器中(而不是服务本身),您需要添加DbContextScopeFactory实例。

private readonly IDbContextScopeFactory _contextScopeFactory = new DbContextScopeFactory();

最后,在您的API方法中,当您要调用服务时,只需使用ContextScopeFactory来创建上下文范围。 AmbientDbContextLocator将从此上下文范围中检索DbContext。用工厂创建的上下文范围将在using块中完成,以确保处置上下文。因此,以您的Checkout方法为例,它看起来像:

在您的Web API [HttpPost] Checkout()方法中:

using (var contextScope = _contextScopeFactory.Create())
{
    using(var service = new CartService())
    {
        service.Checkout();
    }
    contextScope.SaveChanges();
}

您的购物车服务Checkout方法将保持相对不变,只是它将访问通过上下文定位器获取上下文的DbContext属性,而不是将dbContext作为变量(new DbContext())进行访问。]

服务可以继续调用DbContext.SaveChanges(),但这不是必需的,并且在调用contextScope.SaveChanges()之前,更改不会提交给DB。每个服务将具有其自己的Context Locator实例,而不是DbContext,并且它们将取决于您定义一个ContextScope来起作用。如果您调用一个试图访问DbContext而不位于using (var contextScope = _contextScopeFactory.Create())块内的Service方法,则会收到错误消息。这样,您所有的服务调用,甚至嵌套的服务调用(CartService调用OrderService)都将与同一DbContext实例进行交互。

即使您只是想读取数据,也可以使用_contextScopeFactory.CreateReadOnly()利用稍快的DbContext,这将有助于防止意外或不允许的SaveChanges()调用。

[使用ASP.NET Core堆栈时,有关将EF与它一起使用的教程,默认情况下是使用DI来提供数据库上下文,而不是不包含服务层。就是说,它实际上是开箱即用的。我将使用在撰写本文时使用的NuGet上的最新版本的ASP.NET Core Web API和EF Core的最新版本,简要介绍此工作所需的最低限度。

另一答案

[使用ASP.NET Core堆栈时,有关将EF与它一起使用的教程,默认情况下是使用DI来提供数据库上下文,而不是不包含服务层。就是说,它实际上是开箱即用的。我将使用在撰写本文时使用的NuGet上的最新版本的ASP.NET Core Web API和EF Core的最新版本,简要介绍此工作所需的最低限度。

以上是关于与所有存储库/服务类共享我的dbContext吗?的主要内容,如果未能解决你的问题,请参考以下文章

将类注入自定义存储库

Blazor 服务器:将 EF Core DbContextFactory 与 DbContext 混合

如何在具有存储库模式的实体框架中伪造 DbContext.Entry 方法

如何从DbContext中清除未插入的POCO? - 实体框架代码优先

ASP.NET MVC,EntityFramework,DBContext,不同项目中的存储库[关闭]

NSSecureCoding 实现类必须在共享框架中才能与 XPC 一起使用吗?