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


在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 key value. 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;
            }
        }

 

 


免責聲明!

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



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