本篇文章將會剖析為什么會出現這一現象、以及解決的辦法
先來看一下代碼
public static TResult AddTest() { TestDAL testdal = DALFactory.CreateDAL<TestDAL>(); TResult t; Transaction.BeginTransaction(); try { testdal.Insert(new Test { ID=1 }); testdal.ExecuteSqlCommand("update test set name='111' where id=1"); //Test test = testdal.GetSingle(m => m.ID == 1); //test.Name1 = "asdafaff"; testdal.Update(m => m.ID == 1, m => { m.Name1 = "asdafaff"; }); Transaction.Commit(); } catch(Exception ex) { Transaction.Rollback(); } finally { t= new TResult(); } return t; }
按照代碼的邏輯,在事務提交完畢的時候應該ID NAME NAME1都應該有值得,但是結果卻是NAME為NULL。令人大跌眼鏡
並不是事務出現了問題,
問題出現在ExecuteSqlCommand是通過sql來更新數據庫的,沒有更新緩存中的實體實例上下文,當再次Update的時候,就會用到緩存中的實體實例,EF根據其狀態進行更新或者刪除等操作,所以自然就會把之前更新的又全部沖銷覆蓋了。
因此要解決這個問題,先看看UPDATE代碼是怎么寫的
public TResult Update(Expression<Func<TEntity, bool>> predicate, Action<TEntity> updateAction) { if (predicate == null) throw new ArgumentNullException("predicate"); if (updateAction == null) throw new ArgumentNullException("updateAction"); MyDbContext dbContext = this.GetDbContext(); //dbContext.Configuration.AutoDetectChangesEnabled = true; var _model = dbContext.Set<TEntity>().Where(predicate).ToList(); if (_model == null) return new TResult(false, "參數為NULL"); _model.ForEach(p => { updateAction(p); dbContext.Entry<TEntity>(p).State = EntityState.Modified; }); return Save(EntityState.Modified); }
原來dbContext.Set<TEntity>().Where(predicate).ToList();EF會像數據庫中做一次查詢,查詢出來后通過對象上下文根據主鍵去映射緩存對象,如果存在了,就用上下文中的緩存對象,因為還是舊的實例對象,所以這個值沒有更新,就會沖銷覆蓋掉原來的數據。
因此如果要獲取最新的值就必須不需要映射緩存對象
var _model = dbContext.Set<TEntity>().AsNoTracking().Where(predicate).ToList();
下面是AsNoTracking()的注釋
// 摘要:
// Returns a new query where the entities returned will not be cached in the System.Data.Entity.DbContext.
//
// 返回結果:
// A new query with NoTracking applied.
返回一個新的查詢,獲得的實體對象不會被緩存在數據上下文中,所以不難理解,既然不會去緩存在上下文中,肯定就不會去做映射匹配的判斷。否則必然會根據主鍵去匹配以避免重復的數據示例對象出現在緩存中,這也就不難理解EF實體對象中為什么一定要求有主鍵的原因了。
現在已經解決了實體是最新的實體了。可是接下來會出現問題
Attaching an entity of type '' failed because another entity of the same type already has the same primary key value.
因為緩存中有主鍵相同的實體對象了,所以再附加的時候要將舊的示例分離掉
public TResult Update(Expression<Func<TEntity, bool>> predicate, Action<TEntity> updateAction) { if (predicate == null) throw new ArgumentNullException("predicate"); if (updateAction == null) throw new ArgumentNullException("updateAction"); MyDbContext dbContext = this.GetDbContext(); //dbContext.Configuration.AutoDetectChangesEnabled = true; var _model = dbContext.Set<TEntity>().AsNoTracking().Where(predicate).ToList(); if (_model == null) return new TResult(false, "參數為NULL"); _model.ForEach(p => { updateAction(p); DetachExistsEntity(p); dbContext.Entry<TEntity>(p).State = EntityState.Modified; }); return Save(EntityState.Modified); }
private Boolean DetachExistsEntity(TEntity entity) { var objContext = ((IObjectContextAdapter)this.GetDbContext()).ObjectContext; var objSet = objContext.CreateObjectSet<TEntity>(); var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, entity); Object foundEntity; var exists = objContext.TryGetObjectByKey(entityKey, out foundEntity); if (exists) { objContext.Detach(foundEntity); } return (exists); }
解決方案到此結束,就不詳細說明了。