本篇文章将会剖析为什么会出现这一现象、以及解决的办法
先来看一下代码
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); }
解决方案到此结束,就不详细说明了。