36

在以下情况下,由于方法调用而嵌套了两个 DbContext:

public void Method_A() {
    using (var db = new SomeDbContext()) {
        //...do some work here
        Method_B();
        //...do some more work here
    }
}

public void Method_B() {
    using (var db = new SomeDbContext()) {
        //...do some work
    }
}

问题:

  1. 这种嵌套会导致任何问题吗?(并且正确的 DbContext 会在正确的时间处理吗?)

  2. 这种嵌套是否被认为是不好的做法,是否应该将 Method_A 重构为:

    public void Method_A() {
        using (var db = new SomeDbContext()) {
            //...do some work here
        }
    
        Method_B();
    
        using (var db = new SomeDbContext()) {
            //...do some more work here
        }
    }
    

谢谢。

4

4 回答 4

42

您的DbContext派生类实际上在这里为您管理至少三件事:

  • 描述数据库和实体模型的元数据,
  • 底层数据库连接,以及
  • 使用上下文加载的实体的客户端“缓存”,用于更改跟踪、关系修复等(请注意,尽管我将其称为“缓存”是因为需要更好的词,但这通常是短暂的,只是为了支持EF 功能。如果适用,它不能替代应用程序中的适当缓存。)

Entity Framework 通常缓存元数据(第 1 项),以便所有上下文实例(或至少使用相同连接字符串的所有实例)共享它。因此,在这里,您无需担心。

正如其他评论中提到的,您的代码导致使用两个数据库连接。这对您来说可能是也可能不是问题。

您最终还会得到两个客户端缓存(第 3 项)。如果您碰巧从外部上下文加载实体,然后再次从内部上下文加载,您将在内存中拥有它的两个副本。这肯定会令人困惑,并可能导致细微的错误。这意味着,如果您不想使用共享上下文对象,那么您的选项 2 可能会比选项 1 更好。

如果您使用transactions,还有其他考虑因素。拥有多个数据库连接可能会导致事务被提升为分布式事务,这可能不是您想要的。由于您没有提及数据库事务,因此我不会在这里进一步讨论。

那么,这会让你在哪里?

如果您使用此模式只是为了避免DbContext在代码中传递对象,那么您最好重构MethodB以接收上下文作为参数。应该反复出现多长寿命的上下文对象的问题。根据经验,为单个数据库操作或一系列相关数据库操作创建新上下文。(例如,请参阅此博客文章此问题。)

(作为替代方案,您可以将构造函数添加到DbContext接收现有连接的派生类。然后您可以在多个上下文之间共享相同的连接。)

一种有用的模式是编写自己的类来创建上下文对象并将其存储为私有字段或属性。然后你让你的类实现IDisposable并且它的Dispose()方法处理上下文对象。您的调用代码会更新您的类的一个实例,并且根本不必担心上下文或连接。

您何时可能需要同时激活多个上下文?

当您需要编写多线程代码时,这会很有用。数据库连接不是线程安全的,因此您一次只能从一个线程访问一个连接(因此也是一个 EF 上下文)。如果这限制性太大,您需要多个连接(和上下文),每个线程一个。你可能会觉得很有趣。

于 2013-01-29T13:45:12.700 回答
1

您可以通过将上下文传递给 Method_B 来更改您的代码。如果这样做,则不需要创建第二个数据库调用 SomeDbContext。

在此链接中,stackoverflow 中有一个问题的答案 正确使用数据上下文的“使用”语句

于 2013-01-28T22:52:13.627 回答
1

答案有点晚了,但人们可能还在寻找,所以这是另一种方式。

创建类,关心为您处理。在某些情况下,解决方案中的不同地方会有一个可用的功能。这样可以避免创建 DbContext 的多个实例,并且可以使用任意数量的嵌套调用。

粘贴简单的例子。

public class SomeContext : SomeDbContext
{
    protected int UsingCount = 0;
    public static SomeContext GetContext(SomeContext context)
    {
        if (context != null)
        {
            context.UsingCount++;
        }
        else
        {
            context = new SomeContext();
        }
        return context;
    }

    private SomeContext()
    {
    }

    protected bool MyDisposing = true;
    protected override void Dispose(bool disposing)
    {
        if (UsingCount == 0)
        {
            base.Dispose(MyDisposing);
            MyDisposing = false;
        }
        else
        {
            UsingCount--;
        }
    }

    public override int SaveChanges()
    {
        if (UsingCount == 0)
        {
            return base.SaveChanges();
        }
        else
        {
            return 0;
        }
    }
}

使用示例

public class ExmapleNesting
{
    public void MethodA()
    {
        using (var context = SomeContext.GetContext(null))
        {
            // manipulate, save it, just do not call Dispose on context in using
            MethodB(context);
        }

        MethodB();
    }

    public void MethodB(SomeContext someContext = null)
    {
        using (var context = SomeContext.GetContext(someContext))
        {
            // manipulate, save it, just do not call Dispose on context in using
            // Even more nested functions if you'd like
        }
    }
}

简单易用。

于 2016-07-22T13:15:29.247 回答
0

如果您认为与数据库的连接数以及必须打开新连接的次数的影响,这不是一个重要问题,并且您没有限制支持您的应用程序以最佳性能运行,那么一切都很好。您的代码运行良好。因为只创建一个数据库上下文对你的性能影响很小,元数据在第一次加载后会被缓存,当代码需要执行查询时才会连接到你的数据库。考虑到很少的性能考虑和代码设计,我建议您创建上下文工厂,以便为您的应用程序的每个实例提供每个 Db 上下文的实例。

您可以查看此链接以了解更多性能注意事项 http://msdn.microsoft.com/en-us/data/hh949853

于 2013-01-27T09:52:19.730 回答