Repository模式中,Update總是失敗及其解析(轉)


出處:http://www.cnblogs.com/scy251147/p/3688844.html

關於Entity Framework中的Attached報錯的完美解決方案終極版

前發表過一篇文章題為《關於Entity Framework中的Attached報錯的完美解決方案》,那篇文章確實能解決單個實體在進行更新、刪除時Attached的報錯,注意我這里說的單個實體,指的是要更新或刪除的實體不包含其它實體(比如導航屬性就包含其它實體),也就是簡單POCO對象;但如果不是呢?那么那篇文章里的方法在一定程度上不起作用了,仍會報錯,我開始也想不明白,明明通過IsAttached函數判斷要更新的實體並未Attached,但進行Attaching時但仍然報錯說有相同Key,開始還以為是MS的BUG,后經過多次反復調試發現,報錯是對的,因為他報的錯並不是我當前要更新的實體,而是該實體中關聯的實體,代碼與演示報錯如下:(僅是演示代碼)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public  class  A
{
    public  string  a{ get ; set ;}
    public  string  b{ get ; set ;}
    public  string  c{ get ; set ;}
    public  virtual  B b{ get ; set ;}
}
 
public  class  B
{
    public  string  x{ get ; set ;}
    public  string  y{ get ; set ;}
    public  string  z{ get ; set ;}
}
 
var  a1= dbContext.Set<A>().Single();
a1.a= "test1" ;
dbContext.SaveChanges();
 
dbContext.Detach(a1); //從緩存中移除a1實體;
 
 
var  a2= dbContext.Set<A>().AsNoTracking().Single();
a2.a= "test2" ;
  dbContext.Set<A>().Attach(a2);  //報錯,說B相同的KEY已經有Attached
dbContext.Entry(entity).State = EntityState.Modified;
dbContext.SaveChanges();

針對這個報錯,我在想,為何查詢實體A的時候能同時關聯查詢實體B並都同時Attached到內存中,而當我執行Detach實體A時,卻沒能關聯Detach實體B,問題根源就在這里,知道這個原因了,現在就是要解決這個問題,如何解決呢?既然知道是Detach實體不全面造成的,那么我只需要獲取到當前DbContext上下文對象中現有的所有已Attached實體,在執行完相應的CRUD時,再全部依次Detach掉即可,解決方案代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// <summary>
/// 清空DB上下文中所有緩存的實體對象
/// </summary>
private  void  DetachedAllEntities()
{
     var  objectContext = ((IObjectContextAdapter) this .baseContext).ObjectContext;
     List<ObjectStateEntry> entries =  new  List<ObjectStateEntry>();
     var  states =  new [] { EntityState.Added, EntityState.Deleted, EntityState.Modified, EntityState.Unchanged };
     foreach  ( var  state  in  states)
     {
         entries.AddRange(objectContext.ObjectStateManager.GetObjectStateEntries(state));
     }
 
     foreach  ( var  item  in  entries)
     {
         objectContext.Detach(item.Entity);
     }
}
 
 
public  void  Commit()   //封裝的統一提交方法
{
     this .baseContext.SaveChanges();
     this .DetachedAllEntities(); //執行清除
}

在使用的時候配合之前那篇文章的IsAttached函數就能完美解決所有的Attached報錯問題了!

=======================================================================================

在Repository模式中,我的Update方法總是無法更新實體,這個非常郁悶,Update方法如下:

   1:  public virtual void Update(T entity)
   2:          {
   3:              try
   4:              {
   5:                  if (entity == null) throw new ArgumentNullException("實體類為空");
   6:                  Context.Entry(entity).State = EntityState.Modified;
   7:                  //Context.SaveChanges();
   8:              }
   9:              catch (DbEntityValidationException dbex)
  10:              {
  11:                  var msg = string.Empty;
  12:                  foreach (var validationErrors in dbex.EntityValidationErrors)
  13:                      foreach (var validateionError in validationErrors.ValidationErrors)
  14:                          msg += string.Format("Property:{0} Error:{1}", validateionError.PropertyName, validateionError.ErrorMessage);
  15:   
  16:                  var fail = new Exception(msg, dbex);
  17:                  throw fail;
  18:              }
  19:          }

 

看上去是沒有任何問題的代碼,一旦有實體更新的時候,總會出現如下的錯誤提示:

Attaching an entity of type  'TinyFrame.Data.DomainModel.t_user_application' failed because another entity of the same type already has the same primary keyvalue. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

看字面意思,好像是我的EntityState設置不正確導致的,雖然我嘗試過重新設置幾次EntityState,但是仍舊無法解決我的問題。

然后實在找不出原因,就利用關鍵字 “ EF Repository Update ”在Google上面搜集,果然找到一篇文章:Advanced Entity Framework 6 Scenarios for an MVC 5 Web Application (12 of 12),其中有一段話,提出了問題的所在:

 

This happened because of the following sequence of events:
 
    The Edit method calls the ValidateOneAdministratorAssignmentPerInstructor method, which retrieves all departments that have Kim Abercrombie as their administrator. That causes the English department to be read. As a result of this read operation, the English department entity that was read from the database is now being tracked by the database context.
    The Edit method tries to set the Modified flag on the English department entity created by the MVC model binder, which implicitly causes the context to try to attach that entity. But the context can't attach the entry created by the model binder because the context is already tracking an entity for the English department.
 
One solution to this problem is to keep the context from tracking in-memory department entities retrieved by the validation query. There's no disadvantage to doing this, because you won't be updating this entity or reading it again in a way that would benefit from it being cached in memory.
 

問題的原因如下:

在Context對象中,已經hold住了一個需要操作的對象,當我們把EntityState修改成modified的時候,Context會再次去加載那個操作對象,但是這樣加載是無法成功的,因為當前已經存在一個對象了,再加載會導致重復,然后拋出失敗的錯誤。

解決方法很簡單,就是在展示列表的時候,利用AsNoTracking將Hold住的對象釋放掉即可。我們修改代碼如下:

 
 
   1:   public virtual T Get(Expression<Func<T, bool>> where)
   2:          {
   3:              return Dbset.Where(where).AsNoTracking().FirstOrDefault<T>();
   4:          }
   5:   
   6:          public virtual IQueryable<T> GetMany(Expression<Func<T, bool>> where)
   7:          {
   8:              return Dbset.Where(where).AsNoTracking();
   9:          }

 

 

然后提交,OK,問題解決。

 

=============================Update 2014.09.19======================

看到評論中有朋友雖然按照上述方法,但是仍然無法解決這一問題。原因是在Context中還保留有當前實體的副本所致,這里只要我們將實體副本從內存中完全移除,就可以了。

復制代碼
   //用於監測Context中的Entity是否存在,如果存在,將其Detach,防止出現問題。
        private Boolean RemoveHoldingEntityInContext(T entity)
        {
            var objContext = ((IObjectContextAdapter)_context).ObjectContext;
            var objSet = objContext.CreateObjectSet<T>();
            var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, entity);

            Object foundEntity;
            var exists = objContext.TryGetObjectByKey(entityKey, out foundEntity);
          
            if (exists)
            {
                objContext.Detach(foundEntity);
            }

            return (exists);
        }
復制代碼

然后在Repository中,在進行更新和刪除之前,運行一下即可:

復制代碼
  public T Remove(T entity)
        {
            try
            {
                RemoveHoldingEntityInContext(entity);

                _context.DbSet<T>().Attach(entity);
                return _context.DbSet<T>().Remove(entity);
            }
            catch (DbEntityValidationException dbex)
            {
                var msg = string.Empty;
                foreach (var validationErrors in dbex.EntityValidationErrors)
                    foreach (var validateionError in validationErrors.ValidationErrors)
                        msg += string.Format("屬性:{0} 錯誤:{1}", validateionError.PropertyName, validateionError.ErrorMessage);
                var fail = new Exception(msg, dbex);
                throw fail;
            }
        }

        public T Update(T entity)
        {
            try
            {
                RemoveHoldingEntityInContext(entity);

                var updated = _context.DbSet<T>().Attach(entity);
                _context.DbContext.Entry(entity).State = EntityState.Modified;
                return updated;
            }
            catch (DbEntityValidationException dbex)
            {
                var msg = string.Empty;
                foreach (var validationErrors in dbex.EntityValidationErrors)
                    foreach (var validateionError in validationErrors.ValidationErrors)
                        msg += string.Format("屬性:{0} 錯誤:{1}", validateionError.PropertyName, validateionError.ErrorMessage);
                var fail = new Exception(msg, dbex);
                throw fail;
            }
        }
復制代碼
 
參考博文:http://bbs.csdn.net/topics/391074562
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM