简体   繁体   English

EF-管理DbContext的最佳方法?

[英]EF - Best way to manage DbContext?

I know there are alot of articles regarding this topic, but wherever I looked, it was either too complicated or unclear to me. 我知道有很多关于该主题的文章,但是无论我去哪里看,对我来说要么太复杂,要么不清楚。

My team develops web application that uses Entity framework code first. 我的团队开发的Web应用程序首先使用Entity Framework代码。 We also use Autofac for dependency injection. 我们还使用Autofac进行依赖项注入。

Currently, the data access looks as follows: 当前,数据访问如下所示:

The API supplied to projects that do not contain EF: 提供给不包含EF的项目的API:

public class DataService
{
   private IDbContextFactory<MyContext> _factory;
   private IDataServiceExecutor _dataServiceExecutor;

   public DataService(IDbContextFactory<MyContext> factory, IDataServiceExecutor executor)
   {
       _factory = factory;
       _dataServiceExecutor = executor;
   }

   public void AddItem(Product prod)
   {
       using (var context = _factory.Create())
       {
           if (_dataServiceExecutor.AddItem(context, prod))
               context.SaveChanges();
       }
   }
}

My DataServiceExecutor: 我的DataServiceExecutor:

public class DataServiceExecutor
{
    private IRepository<Product> _products;

    public DataService(IRepository<Product> products...more repositories)
    {
       _products = products;
    }

    public bool AddItem(DbContext context, Prouduct prod)
    {
        try
        {
            _products.AddItem(context, prod);

            return true;
        }
        catch(Exception ex)
        {
             Log(..)
             return false;
        }
    }
}

All my repositories inherit from this abstract repository: 我所有的存储库都从该抽象存储库继承:

public abstract class EFRepository<T> : IRepository<T>
{
    public void AddItem<T>(DbContext context, T item)
    {
        context.Set<T>().Add(item);
    }

    .
    .
    .
}

The good thing is that a context is used per transaction this way. 好处是,这样可以在每个事务中使用上下文。

The bad thing is that both my service methods and my repositories methods take context directly. 不好的是,我的服务方法和存储库方法都直接采用上下文。 The application is not so big, so for now the method injection of the context is fine, but it will probably become bigger and so the context injection at it's current state is problematic in my opinion. 应用程序不是那么大,所以现在上下文的方法注入是可以的,但是它可能会变得更大,因此我认为当前状态下的上下文注入是有问题的。 And it looks bad. 而且看起来很糟。

Maybe there are more pros and cons which I'm not aware of.. 也许还有更多我不了解的利弊。

Is there any way I'm not familiar with to make data access better? 有什么我不熟悉的方法可以改善数据访问?

Classes like DataServiceExecutor (essentially a verb) always spell design flaws. 诸如DataServiceExecutor (本质上是动词)之类的类总是会拼写设计缺陷。 It's a method ( Execute... ) disguised as a class. 这是伪装为类的方法( Execute... )。 Responsibilities of such classes aren't clear because their functions inevitably belong to other classes. 这些类的职责尚不明确,因为它们的功能不可避免地属于其他类。

The problem with an in itself great pattern like Inversion of Control is that an IoC container can be used to inject any dependency. 像Inversion of Control这样的本身很棒的模式的问题在于,可以使用IoC容器注入任何依赖项。 So they allow you to create a tangle of dependencies and scattered responsibilities and still do a decent job managing life cycles. 因此,它们使您可以创建各种依赖关系和分散的职责,并且在管理生命周期方面仍然做得不错。 They may obscure life cycle problems you'd have otherwise. 它们可能会掩盖您原本会遇到的生命周期问题。

So let's ignore IoC for a moment and see what your code would look like with simple object creation if you call DataServiceExecutor.AddItem . 因此,让我们暂时忽略IoC,并查看如果调用DataServiceExecutor.AddItem创建简单对象的代码是什么样的。

var product = new Product();
var factory = new DbContextFactory<MyContext>(); // Dependencies unknown
var productRepository = new Repository<Product>(context);
var executor = new DataServiceExecutor(productRepository);
var dataService = new DataService(factory, executor);

Inside dataServiceAddItem method you essentially have: dataServiceAddItem方法内部,您基本上具有:

using (var context = _factory.Create())
{
    executor.AddItem(context, product);
    context.SaveChanges();
}

If DataService would receive the productRepository instead of the executor this would boil down to: 如果DataService将接收productRepository而不是executor则可以归结为:

productRepository.AddItem(context, product);
context.SaveChanges();

The executor can be taken out easily. executor可以很容易地取出来。 Its only role seems to be error logging. 它的唯一作用似乎是错误记录。 But that could be done by DataService just as well. 但这也可以由DataService完成。

But we're not done yet. 但是我们还没有完成。 As you indicated yourself, these methods taking a context as parameter are a bit awkward. 正如您自己指出的那样,这些将上下文作为参数的方法有些尴尬。 But now DataServiceExecutor is out of the picture, it's far more natural to do: 但是现在DataServiceExecutor不在画面中了,它变得更加自然:

var product = new Product();
var factory = new DbContextFactory<MyContext>();
using (var context = _factory.Create())
{
    var productRepository = new Repository<Product>(context);
    productRepository.AddItem(product);

    // And for example
    var orderRepository = new Repository<Order>(context);
    orderRepository.AddItem(order);

    // One SaveChanges call!
    context.SaveChanges();
}

And the DataService is gone too. 而且DataService也消失了。

Now EFRepository stores its context as a member variable, and AddItem looks like this: 现在, EFRepository将其context存储为成员变量, AddItem如下所示:

public void AddItem<T>(T item)
{
    _context.Set<T>().Add(item);
}

And now back to IoC. 现在回到IoC。

In the light of IoC, the main problem of your code is this inside DataService.AddItem : 根据IoC,您的代码的主要问题是在DataService.AddItem内部:

using (var context = _factory.Create())

You create a context that is not managed by the IoC container. 您创建一个不受IoC容器管理的上下文。 It's lifespan is scoped to the AddItem method. 它的寿命仅限于AddItem方法。 Therefore you have to pass it around all over the place to make sure everything within the business transaction uses this one instance. 因此,您必须将其传递到各处,以确保业务交易中的所有内容都使用该一个实例。 By bringing the repository's dependency (to a context) back to constructor injection, it's much easier to let the IoC container manage the context's lifespan. 通过将存储库的依赖关系(返回到上下文)返回到构造函数注入,让IoC容器管理上下文的生命周期变得容易得多。

By the way, you say that DataService is part of "the API supplied to projects that do not contain EF". 顺便说一句,您说DataService是“提供给不包含EF的项目的API”的一部分。 But it does refer to MyContext in its generic parameter. 但是它的确在其通用参数中引用了MyContext Maybe MyContext is an abstraction too, I don't know. 也许MyContext也是一个抽象,我不知道。 Anyhow, you could supply instances of this abstraction by IoC as well. 无论如何,您也可以提供IoC的抽象实例。

My opion is that the solution is not correctly layered. 我的选择是解决方案未正确分层。 I guess the DataService is the top layer that is accessed from the outside world? 我猜DataService是从外部访问的顶层?

In that case i would change to the following: 在这种情况下,我将更改为以下内容:

  • The service gets the repository (or datalayer) injected through the constructor (and then properly disposed). 该服务获取通过构造函数注入的存储库(或数据层)(然后正确处置)。 The service methods then only need relevant parameters. 然后,服务方法仅需要相关参数。
  • What is the datalayer used for? 数据层是用来做什么的? Can it be removed? 可以将其删除吗? I normally have a datalayer below the services that holds all repositories. 我通常在包含所有存储库的服务下面有一个数据层。 In that case the datalayer could be responsible for disposing all repositories when the service disposes the data layer. 在那种情况下,当服务处理数据层时,数据层可能负责处理所有存储库。
  • The repository could be kept as is but should have the context injected through the constructor. 该存储库可以保持原样,但应该通过构造函数注入上下文。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM