1. 程式人生 > >一例一個實體物件不能由多個 IEntityChangeTracker 例項引用的解決

一例一個實體物件不能由多個 IEntityChangeTracker 例項引用的解決

前幾日在檢查日誌的時候發現系統偶爾會出現 Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded 的錯誤,經過查詢資料,是因為隨著訪問量的增多,併發增多造成。解決辦法從微軟的msdn上找到了想改解決辦法。

Entity Framework Optimistic Concurrency Patterns  這裡給出了微軟這篇文章的連結,我採用的是微軟提出的第一種解決辦法:Resolving optimistic concurrency exceptions with Reload   其中 update 方法的寫法修改成這樣:

 public bool Update(T entity)
        {
            using (var dbContext = new F())
            {


                bool saveFailed;
                do
                {
                    saveFailed = false;

                    try
                    {
                        
                        //把entity附加到當前dbContext
                        dbContext.Set<T>().Attach(entity);
                        dbContext.Entry<T>(entity).State = EntityState.Modified;

                        return dbContext.SaveChanges() > 0;
                    }
                    catch (DbUpdateConcurrencyException ex)
                    {
                        saveFailed = true;
                        // Update the values of the entity that failed to save from the store 
                        ex.Entries.Single().Reload();
                    }

                } while (saveFailed);

                return false;
            }

        }


當然,新增和刪除的相應方法也做了同樣的修改,修改後對相應的方法做測試,沒有發現什麼問題。於是就上線了。因為併發效果在本地沒法實現,實際效果需要上線後觀察。過了個週末,然後週一來檢查執行的效果,Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded 的錯誤沒出現,但是發現大量的 一個實體物件不能由多個 IEntityChangeTracker 例項引用的錯誤。通過定位,是update方法出現了問題。網上搜索這個錯誤出現的原因,搜尋到的大部分是說由於主從表的關係導致此問題,而且說的都雲裡霧裡,看不懂他們說的意思。但是雖然出錯的地方剛好有主從表,但我明白,問題恐怕不是這裡。因為這個錯誤大量出現是在我更改了update方法後才出現的。通過分析我更改的update方法前後的變化,更改前update方法傳入的entity例項和update方法SaveChanges 共用的是一個dbcontext, 而更改後,由於使用了 using new 了一個新的 dbcontext,這兩個 dbcontext 不一樣了,懷疑是這裡的問題。而通過把 using 去掉,也真解決了這個問題,不出現 一個實體物件不能由多個 IEntityChangeTracker 例項引用的錯誤了。但是這樣的解決不能令人滿意。因為沒有理論支撐。由於百度和google都沒有找到令人滿意的解釋,於是把目光投向了 stackoverflow 網站,這個網站還是有許多高手,一些解釋感覺很深刻。 通過不停的變換關鍵詞搜尋,終於找到了兩篇文章解釋的比較清楚。我總結了下我這個問題出現的原因是這樣的,看這兩行程式碼:

var examList = es2.Get(m => m.ExamMainGID == examMain.GID && m.QuestionLibraryGID == gid).FirstOrDefault();

es2.Update(examList); 

第一行程式碼es2.Get方法使用的資料連線是_dbContext, entity framework 每一個查詢方法都會自動建立一個 IEntityChangeTracker 的跟蹤,這個跟蹤是使用的_dbContext連線建立的,他會跟蹤exmalList的例項狀態變化,比如狀態的修改,刪除等。而在執行 es2.Update(examList);  這個方法的時候,由於Update方法的實現是 using 了一個新的dbContext: using (var dbContext = new F()), 在這句:dbContext .Set<T>().Attach(entity); 就是把 entity附加到了新的dbContext物件中。按ef的規則,這裡是需要建立一個新的 IEntityChangeTracker  跟蹤器,就是這行出現了錯誤。原因就是一個例項的跟蹤器只能有一個,在這裡這個例項就是examList 。在stackoverflow 網站的這兩篇文章中也給出瞭解決辦法。有兩種,第一種是公用一個數據連線dbContext,就是我以前的解決辦法,不再重新new 一個新的資料連線上下文,而仍然使用es2.Get方法使用的資料連線。另外一種解決辦法就屬於場合用法了。即把通過 es2.Get方法獲取的例項 examList 同_dbContext 剝離開來,然後再把exmaList物件附加到新的 dbContext 物件中。具體的寫法是這樣的:

//從_dbContext中分離entity
((IObjectContextAdapter)_dbContext).ObjectContext.Detach(entity);
//把entity附加到當前dbContext
dbContext.Set<T>().Attach(entity);
 dbContext.Entry<T>(entity).State = EntityState.Modified;

  return _dbContext.SaveChanges() > 0;

這裡的((IObjectContextAdapter)_dbContext).ObjectContext.Detach(entity);就是用 Detach 把 entity從 _dbContext上下文資料連線剝離出來。剝離後再附加到新的資料連線中就不會再出現錯誤。這個方法是不推薦的。如果每次都這樣採用剝離的方法,這是增加程式碼量,共用Context 資料連線才是正確的方法。

這裡給出兩篇stakoverflow的連線,有興趣的可以看下: