由于方法调用而嵌套的DbContext - Entity Framework

时间:2012-10-16 10:53:22

标签: entity-framework

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

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
        }
    }
    
  3. 感谢。

4 个答案:

答案 0 :(得分:39)

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

  • 描述数据库和实体模型的元数据
  • 基础数据库连接和
  • 使用上下文加载的实体的客户端“缓存”,用于更改跟踪,关系修正等。(请注意,虽然我将这称为“缓存”,因为缺少更好的单词,这通常是短暂的,并且是只是为了支持EF的功能。如果适用的话,它不能代替您的应用程序中的正确缓存。)

实体框架通常会缓存元数据(第1项),以便所有上下文实例(或至少所有使用相同连接字符串的实例)共享它。所以这里没有理由让你担心。

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

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

如果您使用交易,则需要进一步考虑。拥有多个数据库连接可能会导致事务被提升为分布式事务,这可能不是您想要的。既然你没有提到数据库事务,我不会在这里进一步讨论。

那么,这会让你离开?

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

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

一个有用的模式是编写自己的类,创建上下文对象并将其存储为私有字段或属性。然后,您使类实现IDisposable,并且其Dispose()方法处理上下文对象。您的呼叫代码会通知您班级的实例,而不必担心上下文或连接。

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

当您需要编写多线程代码时,这非常有用。数据库连接不是线程安全的,因此您必须一次只能从一个线程访问连接(因此也就是EF上下文)。如果限制太多,则需要多个连接(和上下文),每个线程一个。您可能会发现this很有趣。

答案 1 :(得分:1)

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

在此链接的stackoverflow中有一个问题答案 Proper use of "Using" statement for datacontext

答案 2 :(得分: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
        }
    }
}

简单易用。

答案 3 :(得分:0)

如果您认为与数据库的连接数量,以及必须打开新连接的次数的影响,这不是一个重要问题,并且您没有限制支持您的应用程序以最佳性能运行,一切正常。 你的代码效果很好。因为只创建db上下文对性能的影响很小,所以元数据将在首次加载后进行缓存,并且当代码需要执行查询时,就会发生与数据库的连接。通过liitle性能考虑和代码设计,我建议你让上下文工厂为每个应用程序实例提供每个Db Context的实例。

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