实体框架 - 并发使用容器

Woj*_*ski 5 c# concurrency entity-framework transactions ioc-container

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

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

    fc.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

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

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;
    } 
}
Run Code Online (Sandbox Code Playgroud)

当容器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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在用法如下:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

谢谢.

mr1*_*100 3

  1. 同时打开多个容器并不小心对其进行操作是可以接受的做法吗?

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

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

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

  1. 这个问题是否已经以某种类(IoC?)或至少某种不错的设计模式的形式得到解决?

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

builder
    .RegisterType<MyDbContext>()
    .InstancePerHttpRequest();
Run Code Online (Sandbox Code Playgroud)

通过这种方式,autofac IoC 为每个请求生成一个 DbContext,并在请求服务线程内共享现有实例。您不需要在这里关心 DbContext 的处置。当你的线程结束时,你的 IoC 将会执行此操作。