打开新上下文或将其作为参数发送到子方法是不好的做法?

时间:2015-10-17 10:47:49

标签: entity-framework

一位同事询问了我对以下设置的看法。它基于方法中的上下文声明,然后将其提供给被调用的子方法。原理上看起来就是这样。

public void SuperMethod()
{
  using(Context context = new Context())
  {
    ...
    SetMethod(context, ...);
    ...
    GetMethodDuo(context, ...);
    ...
  }
}

public void SetMethod(Context context, ...) { ... }

public Some GetMethod(Context context, ...) { ... }

我建议他反对它,通过尽可能接近实际操作来打开/关闭对数据库的访问来激励我的回答。但是现在我想起来了,我不确定这在任何情况下都是最好的建议。

问题1:我的建议在一般情况下是否正确,还是应考虑改变?

我还注意到调用子方法的超级方法使用了上下文本身。我的建议是在新的子方法中移动与数据库对话的部分,从而将超级方法从对上下文的任何引用中解放出来。我觉得将super-method作为控制器,同时在worker中执行所有与数据库相关的操作是有意义的。

问题2:有一个控制方法可以调用(可能很大)数量的子方法来进行实际工作吗?

请注意,这两个问题都与使用Entity Framework时的上下文使用有关,而不是一般的类结构。

2 个答案:

答案 0 :(得分:6)

IMO,上下文应该打开并处理每个工作单元(例如,在一个简单的函数中) - 这并不意味着你不应该将你的上下文传递给底层函数。这可能正是您想要的,特别是考虑连接池和上下文输入生命周期。

这有几个非常简单的原因:

  1. 打开新的上下文非常便宜,相对于EF中的主要性能问题几乎没有时间,比如实现值( DataSet 对象,反之亦然)并创建查询 - 这两个必须在已经打开的上下文中完成。

  2. 每次打开和处理上下文的一个主要原因是打开/处理连接(某些DBMS,我特别知道SQL CE,在创建与某些数据库的连接方面存在难以置信的问题 - 而EF < strong>将根据提供的连接字符串创建新连接(无论何时需要)。但是,您可以通过保持连接打开(或者让它在大多数情况下超时也不会太糟糕)来轻松超越这一点,并在创建时使用 DbContext(连接,bool)将其传递给您的上下文重载 ContextOwnsConnection = false

  3. 当您在整个生命周期中保持上下文打开时,您可能无法知道哪些对象已经在更改跟踪器中,已实现或以其他形式存在,哪些不是。对我来说,重写我的项目的BL时这是一个问题。我试图修改一个我之前添加的对象。它是在上下文中(未更改)但不在更改跟踪器中,我无法设置其状态,因为它不在更改跟踪器中。我无法再次附上它,因为它已经在上下文中了。这种行为很难控制。

  4. 另一种形式如下。每当新对象进入上下文时,EF将尝试设置这些关于上下文中其他对象的导航属性。这称为关系修正,是 Include()运行良好的主要原因之一。这意味着在大多数情况下,您的上下文中会有一个巨大的对象树。然后,在添加/删除它(或其他任何操作)时,EF将尝试将其执行到整个树(嗯......有时;)),这会导致很多麻烦,尤其是在尝试添加新条目时与FK已经存在的项目。

  5. 如上所述,数据库上下文基本上是一个对象树,根据其生命周期,它可以是巨大的。在这里,EF必须做一些事情,比如......检查一个项目是否已经存在,因为明显的原因...在最好的情况下复杂度O(n * log(n)+ m),其中m是对象类型的数量和n上下文中此类型的对象的数量。 ...检查自检索以来是否已修改对象 - 您可以想象,因为EF必须在每次调用中为每个对象执行此操作,这可以把事情放慢了很久。

  6. 与上一期相对应的一位。在调用 SaveChanges()时你真正想要的是什么?最有可能的是,您希望能够告诉:&#34; 确定,这些是我所做的操作,因此EF现在应该发出这些以及这些调用db &#34;对吧?嗯......但是,因为EF一直在跟踪实体,也许你修改了一些值,或者其他线程在那里做了什么......你怎么能确定,这些是唯一的东西 SaveChanges()< / em>会吗?你怎么能确定在上下文的整个生命周期中,你的数据库中没有什么可疑的东西(这将取消交易,这可能相当大)?

  7. 但是,是的,当然,有一些问题,你需要保持上下文开放(好吧,你不需要 - 你可以通过它)。对我来说,这主要是在少数情况下FK校正难以维护(但仍然在一个函数内,有时在一个函数中我只需要为了简单而处理和重新创建上下文)以及每当你调用时来自代码中多个位置的子函数 - 我遇到的问题是我在调用函数中打开了一个上下文,调用了另一个函数,它仍然需要上下文。通常,这不是问题,但我的连接处理有点......先进。这导致了性能损失,我通过将已经打开的上下文通过可选的附加上下文参数传递给子函数来解决这个问题 - 就像你已经提到过的那样,但它不应该是真的有必要。

    有关其他参考,以下是一些可能对此有所帮助的链接。一个直接来自MSDN,另一个来自a blog

答案 1 :(得分:1)

正如@DevilSuichiro所提到的,DbContext是一个工作单元容器。默认情况下,DbContext将所有已加载的对象存储在内存中并跟踪其更改。调用SaveChanges方法时,所有更改都将发送到单个事务中的数据库。

因此,如果您的SuperMethod处理某种逻辑工作单元(例如Web应用程序中的HTTP请求),我只会将上下文实例化一次并将其作为参数传递给子方法。

关于你的第二个问题 - 如果你只实例化一次上下文,那么IMO最好有更多方法,这些方法简单,易于维护并且具有有意义的名称。如果你想在每个子方法中创建一个新的上下文实例,它取决于&#34;可能大的&#34;数字表示: - )