代码之家  ›  专栏  ›  技术社区  ›  Wojciech Kozaczewski

实体框架-容器的并发使用

  •  5
  • Wojciech Kozaczewski  · 技术社区  · 10 年前

    在基于实体框架的应用程序的业务逻辑层中,所有作用于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. 同时打开多个容器并不小心地对其进行操作是可接受的做法吗?

    我得出一个结论,我可以编写一个简单的守卫式对象,如下所示:

    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 已创建,并且两种方法都使用它,因此不存在冲突。此外 帮助者方法 也可以单独使用,然后创建自己的容器。

    然而,这对我来说似乎是一个巨大的过度;因此:

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

    非常感谢。

    2 回复  |  直到 10 年前
        1
  •  3
  •   mr100    10 年前
    1. 同时打开多个容器并不小心地对其进行操作是可接受的做法吗?

    一般来说,这是完全可以接受的,有时甚至是必要的,但你必须对此持谨慎态度。在执行多线程操作时,同时拥有多个容器特别方便。由于db的工作方式,通常每个线程都应该有自己的DbContext,不应该与其他线程共享。同时使用多个DbContext的缺点是,它们中的每一个都将使用单独的数据库连接,有时它们是有限的,这可能会导致应用程序偶尔无法连接到数据库。另一个缺点是,一个DbContext生成的实体不能与另一个DbContext生成的实体一起使用。在您的示例中,HelperMethod返回基元类型,因此这是完全安全的,但如果它将返回一些实体对象,例如,在MainMethod中,您希望将其分配给MainMethod DbContext创建的实体的某些导航属性,则会收到一个异常。要在MainMethod中克服此问题,您必须使用HelperMethod返回的实体Id再次检索该实体,这一次使用fc上下文。另一方面,使用多个上下文有一个好处——如果一个上下文有一些问题,例如,它试图保存违反索引常量的内容,那么所有下一次保存更改的尝试都将导致相同的异常,因为错误的更改仍将待决。如果您使用多个DbContexts,那么如果其中一个失败,那么第二个将独立运行-这就是DbContexts不应该长寿的原因。因此,一般来说,我认为最好的使用规则是:

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

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

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

    您需要确保的是,您的DbContext类是单线程的,但每个线程都有自己的类实例。在我看来,确保这一点的最佳方式是IoC。例如,在web应用程序的Autofac中,我使用以下规则注册DbContext:

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

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

        2
  •  2
  •   usr    10 年前

    大多数情况下,在多个连接中同时工作不是正确的方法,因为:

    1. 您可以获得SQL Server无法解决的分布式死锁。
    2. 您可能看不到以前写入但尚未提交的数据。
    3. 不能跨上下文边界共享实体(此处:方法)。
    4. 更多的资源使用。
    5. 无法跨上下文边界进行事务处理(此处:方法)。

    这些都是非常严重的缺点。通常,最好的模型是为应用程序正在处理的请求(HTTP或WCF请求)提供一个上下文、连接和事务。这非常简单,避免了很多问题。

    EF应该用作一个活动对象模型。不要通过将其降低到CRUD来削弱它。

    static FunkyContainer GlobalContainer
    

    这行不通。您不应该在请求之间共享上下文。超级危险。考虑将上下文存储在 HttpContext.Items 或者应用程序中的每个请求存储。