2012-02-01 48 views
34

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

解决方案1有一个上下文,并小心使用锁定,以确保没有2个线程同时访问它。这将是相对简单的实施,但要求上下文在应用程序的持续时间。让这样一个单独的上下文实例打开是不是一个好主意?

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

问:两种解决方案中的任何一种都是否有效,如果不是,我们如何解决这个问题?

+2

@usr你有更好的主意? – Cocowalla 2013-07-05 10:58:31

+0

@Cocowalla不知道OP正在处理的更大场景,我不知道。他的两个解决方案都会导致一个痛苦的实施,这就是为什么我要警告他。也许他可以采用完全不同的路径,并以单线程方式使用EF(它的使用方式)。 – usr 2013-07-05 11:56:40

+0

另外一点需要注意的:你不能做任何更改的实体时,它是分离的,因为没有上下文目前正在跟踪这种变化。 SaveChanges()稍后调用时,更改不会保留。 – JoeCool 2015-01-07 16:38:53

回答

23

首先,我假设您已阅读this article on MSDN

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

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

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

的缺点是,你需要写很多代码 - 你有你的EF实体,但你也不得不说有重复的特性,许多EF实体的潜在不同基数的业务实体。为了缓解这种情况,您可以使用框架(如AutoMapper)简化从EF实体到业务实体的复制属性,然后再返回。

+0

克里斯离开,谢谢你的回答,这是澄清我们的想法非常有帮助。我们将按照你的建议去做,我想我们已经试图用我们的实体对象去做所有的事情。 – MartinR 2012-02-02 09:16:56

+2

另一个问题与解决方案1是缓存:上下文将缓存的实体,所以当它修改了应用程序 – Cocowalla 2013-07-05 11:01:00

+0

您所提供流下了不少光,我的链接的运行实例外到如何EF工程数据会过时。非常感谢。 – Brandon 2015-09-14 15:17:01

0

你并不想要一个长寿的背景。理想情况下,他们应该是一个请求/数据操作的生命。

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

性能将需要一点一击的,但它提供了确保导航性能不要被“遗忘”对他们错位的防弹方式。

+0

我最近遇到了类似的问题,并最终在低于此值的情况下提高了性能。输入错误的地方,看我的答案 – Otake 2015-10-12 16:33:20

0

它已经一段时间,因为这个问题问,但我最近碰到了一个类似类型的问题,并最终做低于这个帮助我们满足性能标准。

你基本上分割你的名单分成块,并在一个多线程的方式sperate线程中处理它们。每个新线程都会启动自己的uow,这需要您的实体被连接。

有一点要注意的是你的数据库需要进行快照隔离被启用。否则可能会导致死锁。您需要确定您正在执行的操作和相关业务流程是否正常。在我们的案例中,这是产品实体的简单更新。

你可能需要做一些测试,以决定最佳的块大小,也限制了并行所以总有资源完成操作。

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); 
        } 
       }); 
    } 
7

我似乎有关于EF和多线程的线程计算器一打。他们都有答案,可以深入解释这个问题,但并不真正告诉你如何解决这个问题。

EF不是线程安全的,我们现在都知道。但根据我的经验,唯一的风险是上下文创作/操作。 实际上有一个非常简单的解决方法,你可以保持懒惰的加载。

可以说你有一个WPF应用程序和一个MVC网站。 WPF应用程序使用多线程。您只需在多线程中处理数据库上下文,并在不使用时保留它。例如一个MVC网站,上下文会在视图呈现后自动处理。

在WPF应用层使用此:

ProductBLL productBLL = new ProductBLL(true); 

在MVC应用层使用此:

ProductBLL productBLL = new ProductBLL(); 

如何使你的产品的商业逻辑层应该像:

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... 
} 

如何使你的数据库逻辑层应该像这样:

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... 
} 
0

我刚刚在那里试图使用EF多线程导致的错误的项目。

我试图

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

,但它只是改变了错误的类型从DataReader的错误多线程错误。

简单的解决方案是使用存储过程与EF功能的进口

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

的关键是要到数据的来源,从而避免数据模型。