2012-02-01 46 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); 
} 

的關鍵是要到數據的來源,從而避免數據模型。