.net 实体框架和多线程

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9099359/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 16:12:57  来源:igfitidea点击:

Entity Framework and Multi threading

.netmultithreadingframeworksentity

提问by Martin Richards

We are having some trouble designing our multi-threaded Entity Framework driven application and would like some guidance. We are creating entities on different threads, the entities are added to collections which are then data-bound to various WPF controls. The ObjectContext class is not thread-safe, so managing this we have essentially have 2 solutions:

我们在设计多线程实体框架驱动的应用程序时遇到了一些问题,希望得到一些指导。我们在不同的线程上创建实体,实体被添加到集合中,然后数据绑定到各种 WPF 控件。ObjectContext 类不是线程安全的,因此管理它我们基本上有两个解决方案:

Solution 1 has a single context and carefully use locking to ensure no 2 threads access it at the same time. This would be relatively simple to implement but would require the context to be alive for the duration of the application. Is it a bad idea to have a single context instance open like this?

解决方案 1 具有单个上下文并谨慎使用锁定以确保没有 2 个线程同时访问它。这实现起来相对简单,但需要上下文在应用程序的持续时间内处于活动状态。像这样打开单个上下文实例是一个坏主意吗?

Solution 2 is to create context objects on-demand and then detach the objects immediately, then hold them in our own collections, then re-attach them to do any updating. This has some serious problems for use though, as when the objects are detached they lose references to the navigation property objects. Also there is the problem that 2 threads could still try to access a single object, and both try to attach() it to a context. Also, we would need to supply a new context every time we wanted to access entities' navigation properties.

解决方案 2 是按需创建上下文对象,然后立即分离对象,然后将它们保存在我们自己的集合中,然后重新附加它们以进行任何更新。但是,这有一些严重的使用问题,因为当对象被分离时,它们会丢失对导航属性对象的引用。还有一个问题是 2 个线程仍然可以尝试访问单个对象,并且都尝试将其 attach() 到上下文。此外,每次我们想要访问实体的导航属性时,我们都需要提供一个新的上下文。

Q: Are either of the two solutions valid, if not how do you recommend we tackle this?

问:这两种解决方案中的任何一种都有效吗?如果无效,您建议我们如何解决这个问题?

采纳答案by Chris Shain

First off, I'm assuming you have read the article "Multithreading and the Entity Framework"on MSDN.

首先,我假设您已阅读MSDN 上的文章“多线程和实体框架”

Solution #1 is almost certainly the safest from a threading perspective, since you are guaranteeing that only one thread is interacting with the context at any give time. There is nothing inherently wrong with keeping the context around- it isn't keeping database connections open behind the scenes, so it's just memory overhead. This could, of course, present a performance problem if you end up bottlenecked on that thread and the whole application is written with single-db-thread assumptions.

从线程的角度来看,解决方案#1 几乎肯定是最安全的,因为您保证在任何给定时间只有一个线程与上下文交互。保持上下文本身并没有错——它不会在幕后保持数据库连接打开,所以它只是内存开销。当然,如果您最终在该线程上遇到瓶颈并且整个应用程序是用单数据库线程假设编写的,那么这可能会带来性能问题。

Solution #2 seems unworkable to me- you'd end up with subtle bugs throughout the application where people forget to re-attach (or detach) entities.

解决方案#2 对我来说似乎行不通——你最终会在整个应用程序中出现微妙的错误,人们忘记重新附加(或分离)实体。

One solution is to not use your entity objects in the UI layer of the application. I'd recommend this anyway- chances are, the structure/layout of the entity objects is not optimal for how you want to display things on your user interface (this is the reason for the family of MVCpatterns). Your DAL should have methods which are business logic specific (UpdateCustomerfor instance), and it should decide internally whether to create a new Context or use a stored one. You can start with the single stored context approach, and then if you run into bottleneck issues you have a limited surface area where you need to make changes.

一种解决方案是不在应用程序的 UI 层中使用您的实体对象。无论如何,我建议这样做 - 很可能,实体对象的结构/布局对于您希望在用户界面上显示内容的方式不是最佳的(这就是MVC模式系列的原因)。您的 DAL 应该具有特定UpdateCustomer于业务逻辑的方法(例如),并且它应该在内部决定是创建新上下文还是使用存储的上下文。您可以从单一存储上下文方法开始,然后如果遇到瓶颈问题,您需要进行更改的表面积有限。

The downside is that you need to write a lot more code- you'd have your EF entities, but you'd also have business entities that have duplicate properties and potentially differing cardinality of many of the EF entities. To mitigate this, you can use frameworks like AutoMapperto simplify copying properties from the EF entities to the business entities and back again.

缺点是您需要编写更多代码——您将拥有 EF 实体,但您也会拥有具有重复属性和许多 EF 实体的潜在不同基数的业务实体。为了缓解这种情况,您可以使用AutoMapper 之类的框架来简化将属性从 EF 实体复制到业务实体并再次返回的过程。

回答by Eli

I have seem a dozen of stackoverflow threads concerning EF and multithreading. All of them have answers that explains the problem in depth but not really show you how to fix it.

我似乎有十几个关于 EF 和多线程的 stackoverflow 线程。他们都有答案,可以深入解释问题,但没有真正向您展示如何解决问题。

EF is not thread safe, we all know that by now. But in my experience the only risk is the context creations/manipulations. There is actually a very simple fix for this where you can keep your lazy loading.

EF 不是线程安全的,我们现在都知道了。但根据我的经验,唯一的风险是上下文的创建/操作。实际上有一个非常简单的修复方法,您可以在其中保持延迟加载。

Lets say you have a WPF app and a MVC website. where the WPF app uses multihreading. You just dispose the db context in multithreading and keep it when not. Example a MVC website, the context will automaticly disposes after the view has been presented.

假设您有一个 WPF 应用程序和一个 MVC 网站。WPF 应用程序使用多线程的地方。您只需在多线程中处理 db 上下文并在没有时保留它。例如一个 MVC 网站,上下文将在视图呈现后自动处理。

In the WPF application layer you use this:

在 WPF 应用程序层中,您使用:

ProductBLL productBLL = new ProductBLL(true);

In the MVC application layer you use this:

在 MVC 应用程序层中,您使用:

ProductBLL productBLL = new ProductBLL();

How your product business logic layer should look like:

您的产品业务逻辑层应如下所示:

public class ProductBLL : IProductBLL
{
    private ProductDAO productDAO; //Your DB layer

    public ProductBLL(): this(false)
    {

    }
    public ProductBLL(bool multiThreaded)
    {
        productDAO = new ProductDAO(multiThreaded);
    }
    public IEnumerable<Product> GetAll()
    {
        return productDAO.GetAll();
    }
    public Product GetById(int id)
    {
        return productDAO.GetById(id);
    }
    public Product Create(Product entity)
    {
        return productDAO.Create(entity);
    }
    //etc...
}

How your database logic layer should look like:

您的数据库逻辑层应如下所示:

public class ProductDAO : IProductDAO
{
    private YOURDBCONTEXT db = new YOURDBCONTEXT ();
    private bool _MultiThreaded = false;

    public ProductDAO(bool multiThreaded)
    {
        _MultiThreaded = multiThreaded;
    }
    public IEnumerable<Product> GetAll()
    {
        if (_MultiThreaded)
        {
            using (YOURDBCONTEXT  db = new YOURDBCONTEXT ())
            {
                return db.Product.ToList(); //USE .Include() For extra stuff
            }
        }
        else
        {
            return db.Product.ToList();
        }                  
    }

    public Product GetById(int id)
    {
        if (_MultiThreaded)
        {
            using (YOURDBCONTEXT  db = new YOURDBCONTEXT ())
            {
                return db.Product.SingleOrDefault(x => x.ID == id); //USE .Include() For extra stuff
            }
        }
        else
        {
            return db.Product.SingleOrDefault(x => x.ID == id);
        }          
    }

    public Product Create(Product entity)
    {
        if (_MultiThreaded)
        {
            using (YOURDBCONTEXT  db = new YOURDBCONTEXT ())
            {
                db.Product.Add(entity);
                db.SaveChanges();
                return entity;
            }
        }
        else
        {
            db.Product.Add(entity);
            db.SaveChanges();
            return entity;
        }
    }

    //etc...
}

回答by to11mtm

You do -not- want a long lived context. Ideally they should be for the life of a request/data operation.

你不 - 想要一个长期存在的上下文。理想情况下,它们应该在请求/数据操作的整个生命周期内。

When dealing with similar problems, I wound up implementing a repository that cached PK Entities for a given type, and allowed a 'LoadFromDetached' which would Find the entity in the database, and 'copy' all the scalar properties except PK over to the newly attached entity.

在处理类似问题时,我最终实现了一个为给定类型缓存 PK 实体的存储库,并允许使用“LoadFromDetached”在数据库中查找实体,并将除 PK 之外的所有标量属性“复制”到新的附属实体。

The performance will take a bit of a hit, but it provides a bullet proof way of making sure navigation properties don't get mangled by 'forgetting' about them.

性能会受到一些影响,但它提供了一种防弹方法,可确保导航属性不会因“忘记”它们而受到破坏。

回答by Otake

It has been sometime since the question asked but I've recently bumped into a similiar type of problem and ended up doing the below which helped us meeting the performance criteria.

自从提出问题以来已经有一段时间了,但我最近遇到了一个类似类型的问题,最终做了以下帮助我们满足性能标准的操作。

You basically split your list into chunks and process them within sperate threads in a multi-threaded manner. Each new thread also initiates their own uow which requires your entities to be attached.

您基本上将您的列表分成块,并以多线程方式在单独的线程中处理它们。每个新线程也会启动它们自己的 uow,这需要附加您的实体。

One thing to be aware of is your database needs to be enabled for snapshot isolation; otherwise you might end up with deadlocks. You would need to decide if this is ok for the operation you are doing and related business process. In our case it was a simple update on the product entity.

需要注意的一件事是您的数据库需要启用快照隔离;否则你可能会陷入僵局。您需要决定这对于您正在执行的操作和相关业务流程是否合适。在我们的例子中,它是对产品实体的简单更新。

You would probably need to do some tests to decide on the best chunk size and also limit the parallelism so there is always resource to complete the operation.

您可能需要进行一些测试来确定最佳块大小并限制并行度,以便始终有资源来完成操作。

    private void PersistProductChangesInParallel(List<Product> products, 
        Action<Product, string> productOperationFunc, 
        string updatedBy)
    {
        var productsInChunks = products.ChunkBy(20);

        Parallel.ForEach(
            productsInChunks,
            new ParallelOptions { MaxDegreeOfParallelism = 20 },
            productsChunk =>
                {
                    try
                    {
                        using (var transactionScope = new TransactionScope(
                                TransactionScopeOption.Required,
                                new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot }))
                        {
                            var dbContext = dbContextFactory.CreatedbContext();
                            foreach (var Product in productsChunk)
                            {
                                dbContext.products.Attach(Product);
                                productOperationFunc(Product, updatedBy);
                            }
                            dbContext.SaveChanges();
                            transactionScope.Complete();
                        }
                    }
                    catch (Exception e)
                    {
                        Log.Error(e);
                        throw new ApplicationException("Some products might not be updated", e);
                    }
                });
    }

回答by Stuart Lawrence

I've just had a project where trying to use EF with multi threading caused errors.

我刚刚有一个项目,尝试将 EF 与多线程一起使用会导致错误。

I tried

我试过

using (var context = new entFLP(entity_connection))            
{
    context.Product.Add(entity);
    context.SaveChanges();
    return entity;
}

but it just changed the type of the error from datareader error to multiple thread error.

但它只是将错误类型从数据读取器错误更改为多线程错误。

The simple solution was to use stored procedures with EF function imports

简单的解决方案是使用带有 EF 函数导入的存储过程

using (var context = new entFLP(entity_connection))
{
    context.fi_ProductAdd(params etc);
}

The key is to go to the source of the data and avoid the data model.

关键是去数据的源头,避开数据模型。