实体框架 - 并发使用容器

时间:2015-07-17 13:59:38

标签: c# entity-framework concurrency transactions ioc-container

在基于实体框架的应用程序的业务逻辑层中,所有作用于DB的方法应该(如我所知)包含在:

using(FunkyContainer fc = new FunkyContainer())
{
    // do the thing

    fc.SaveChanges();
}

当然,为了我自己的方便,这些方法常常互相使用,为了不重复自己。我在这里看到的风险如下:

public void MainMethod()
{
    using(FunkyContainer fc = new FunkyContainer())
    {
        // perform some operations on fc
        // modify a few objects downloaded from DB

        int x = HelperMethod();

        // act on fc again

        fc.SaveChanges();
    }
}
public int HelperMethod()
{
    using(FunkyContainer fc2 = new FunkyContainer())
    {
        // act on fc2 an then:

        fc2.SaveChanges();

        return 42;
    } 
}

创建容器fc2时,我看起来不太好,而fc仍处于打开状态且尚未保存。所以这引出了我的第一个问题:

  1. 是否同时打开了多个容器并且不小心对它们采取了可接受的做法?
  2. 我得出一个结论,我可以写一个简单的守护风格的对象:

    public sealed class FunkyContainerAccessGuard : IDisposable
    {
        private static FunkyContainer GlobalContainer { get; private set; }
        public FunkyContainer Container // simply a non-static adapter for syntactic convenience
        {
            get
            {
                return GlobalContainer;
            }
        }
    
        private bool IsRootOfHierarchy { get; set; }
    
        public FunkyContainerAccessGuard()
        {
            IsRootOfHierarchy = (GlobalContainer == null);
    
            if (IsRootOfHierarchy)
                GlobalContainer = new FunkyContainer();
        }
    
        public void Dispose()
        {
            if (IsRootOfHierarchy)
            {
                GlobalContainer.Dispose();
    
                GlobalContainer = null;
            }
        }
    }
    

    现在用法如下:

    public void MainMethod()
    {
        using(FunkyContainerAccessGuard guard = new FunkyContainerAccessGuard())
        {
            FunkyContainer fc = guard.Container;
    
            // do anything with fc
    
            int x = HelperMethod();
    
            fc.SaveChanges();
        }
    }
    public int HelperMethod()
    {
        using(FunkyContainerAccessGuard guard = new FunkyContainerAccessGuard())
        {
            FunkyContainer fc2 = guard.Container;
    
            // do anything with fc2
    
            fc2.SaveChanges();
        }
    }
    

    HelperMethod调用MainMethod时,GlobalContainer已经创建,并且两种方法都使用它,因此没有冲突。此外,HelperMethod也可以单独使用,然后创建自己的容器。

    然而,这对我来说似乎是一种巨大的矫枉过正;这样:

    1. 这个问题是否已经以某种类(IoC?)或至少一些不错的设计模式的形式解决了?
    2. 谢谢。

2 个答案:

答案 0 :(得分:3)

  1. 是否同时打开了多个容器并且不小心对它们采取了可接受的做法?
  2. 一般来说,这是完全可以接受的,有时甚至是必要的,但你必须要谨慎。在进行多线程操作时,同时拥有多个容器尤其方便。由于db的工作原理通常每个线程都应该有自己的DbContext,不应该与其他线程共享。在同一时间使用多个DbContext的缺点是它们中的每一个都将使用单独的数据库连接,有时它们是有限的,可能导致应用程序偶尔无法连接到数据库。另一个缺点是由一个DbContext生成的实体可能不会与其他DbContext生成的实体一起使用。在您的示例中,HelperMethod返回基本类型,因此这是非常安全的,但是如果它返回一些实体对象,您希望在MainMethod中为实例分配由MainMethod DbContext创建的实体的某些导航属性,那么您将收到一个异常。要在MainMethod中克服这个问题,您必须使用HelperMethod返回的实体Id来再次检索该实体,这次使用fc上下文。另一方面,使用多个上下文有一个优点 - 如果一个上下文有一些麻烦,例如它试图保存违反索引constaint的东西,那么保存更改的所有下一次尝试将导致相同的异常,因为错误的更改将还在等待。如果你使用多个DbContexts然后如果一个会失败,那么第二个将独立运行 - 这就是为什么DbContexts不应该长寿。所以一般来说我会说最好的使用规则是:

    • 每个帖子都应该使用单独的DbContext
    • 在同一个线程上执行的所有方法都应该共享相同的DbContext

    当然,如果要完成的工作很短,则上述情况适用。 DbContext不应该长寿。最好的例子是Web应用程序 - 每个服务器请求由单独的线程处理,生成响应的操作通常不需要很长时间。在这种情况下,为了方便起见,所有为生成一个响应而执行的方法都应该共享相同的DbContext。但是每个请求都应该由单独的DbContext提供。

    1. 这个问题是否已经以某种类(IoC?)或至少一些不错的设计模式的形式解决了?
    2. 您需要确保的是,您的DbContext类是每个线程的单例,但每个线程都有自己的该类实例。在我看来,确保这一点的最佳方法是使用IoC。例如,在Web应用程序的Autofac中,我使用以下规则注册我的DbContext:

      builder
          .RegisterType<MyDbContext>()
          .InstancePerHttpRequest();
      

      这样autofac IoC就每个请求生成一个DbContext,并在请求服务线程内共享现有实例。你不需要在这里处理你的DbContext。当您的线程结束时,您的IoC将执行此操作。

答案 1 :(得分:2)

在大多数情况下,同时处理多个连接并不是正确的方法,因为:

  1. 您可以获得SQL Server无法解决的分布式死锁。
  2. 您可能看不到以前写过但尚未提交的数据。
  3. 您不能跨上下文边界共享实体(此处:方法)。
  4. 更多资源使用情况。
  5. 无法跨越上下文边界(此处:方法)进行交易。
  6. 这些是非常严重的缺点。通常,最好的模型是为应用程序正在处理的请求(HTTP或WCF请求)提供一个上下文,连接和事务。设置非常简单,避免了很多问题。

    EF应该用作实时对象模型。不要将它减少到CRUD来削弱它。

    static FunkyContainer GlobalContainer
    

    这不起作用。您不应该跨请求共享上下文。超级危险。考虑在HttpContext.Items或您应用中的每个请求存储中存储上下文。