我們在使用Entity Framework進行CRUD時,為了提升查詢效率,一般均會啟動NoTracking,即不追蹤變化,設置代碼如下:
//這是DB First模式下設置方法: aTestEntities db = new aTestEntities(); db.Companies.MergeOption = MergeOption.NoTracking; //這是CODE First及Model First模式下設置方法: aTestEntities db = new aTestEntities(); db.Companies.AsNoTracking();
雖然啟動NoTracking,查詢效率提高了,但我們在進行CUD時,有時又會出現如下之類的報錯:
無法附加此對象,因為它已經在對象上下文中。對象只有在處于未更改狀態時才能重新附加。
因為查詢時啟用了NoTracking,即表明查詢的實體對象是處于Detached,我們再進行CRD時,必須先進行Attach操作,然后才能執行相應的增加、更新、刪除操作,但由于在有些情況下我們并不能保證需要進行CRD的實體為Detached,所以易造成重復Attach,從而導致報上面的錯誤或其它錯誤。
若要避免重復Attach,我們則必需要有一個能夠判斷實體的狀態是否為Attach,如果已Attached,我們就不需要再進行Attach操作,EF中并沒有這類的方法,所以我這里總結了如下幾個方案(IsAttached方法),可以避免此類問題的發生:
方案一:采用DB First時,由于實體類均繼承自EntityObject,所以我們可以通過EntityObject.EntityKey屬性來進行判斷
/// <summary> /// 判斷entity是否已經Attached /// </summary> /// <param name="entity"></param> /// <returns></returns> public bool IsAttached<TEntity>(TEntity entity) where TEntity : EntityObject { ObjectStateEntry entry = null; if (dbContext.ObjectStateManager.TryGetObjectStateEntry(entity.EntityKey, out entry)) { if (entry.State != EntityState.Detached) { return true; } } return false; }
方案二:采用Model First或Code First時,由于實體類為我們自己設計的,默認并沒有繼承自EntityObject,所以就不能使用上面的方法,但我們可以以方案一中的思想,來設計實體類,我們可以定義一個接口IEntityWithId,然后讓所有的實體類均實現該接口,最后再改寫方案一的方法即可完成
public interface IEntityWithId { Guid Id { get; set; } } public class EntityClass : IEntityWithId { Guid Id { get; set; } //...其它屬性 } /// <summary> /// 判斷entity是否已經Attached /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="entity"></param> /// <returns></returns> public bool IsAttached<TEntity>(TEntity entity) where TEntity : IEntityWithId { TEntity localEntity = dbContext.Set<TEntity>().Local.Where(t => t.Id == entity.Id).FirstOrDefault(); if (localEntity != null) { if (dbContext.Entry(localEntity).State != EntityState.Detached) { return true; } } return false; }
方案三:采用Model First或Code First時,若沒有定義統一的接口,那么我們就不能使用方案二中的IsAttached方法,這時該怎么辦呢?通過VS Debug時瀏覽實體對象發現,實體的類型并不是我們所定義的類型,而是變成了EntityWrapperWithoutRelationships<TEntity>,該類中有一個公共字段屬性:_entityWrapper,然后繼續查看該字段的類型,又發現了EntityWrapper類,該類中就有了EntityKey屬性,該屬性與方案一中的EntityObject.EntityKey屬性類型相同,如果我們能夠獲取到該EntityKey屬性,那么就可以使用方案一中的方法進行判斷了,但高興之余又發現,EntityWrapperWithoutRelationships及EntityWrapper類的訪問修飾符為internal,意味著我們并不能在自己的項目中直接使用,唯一的辦法就是采用反射來動態獲取該屬性,所以整個的實現方法如下:
/// <summary> /// 判斷entity是否已經Attached /// </summary> /// <param name="entity"></param> /// <returns></returns> private bool IsAttached(TEntity entity) { var objectContext = ((IObjectContextAdapter)this.baseContext).ObjectContext; ObjectStateEntry entry = null; if (objectContext.ObjectStateManager.TryGetObjectStateEntry(GetEntityKey(entity), out entry)) { if (entry.State != EntityState.Detached) { return true; } } return false; } /// <summary> /// 通過反射獲取實體對象的EntityKey /// </summary> /// <param name="entity"></param> /// <returns></returns> private EntityKey GetEntityKey(TEntity entity) { var entityWrapper = entity.GetType().GetField("_entityWrapper").GetValue(entity);//獲取字段_entityWrapper的值 var entityWrapperType = entityWrapper.GetType();//獲取字段的類型 var entityKey = entityWrapperType.GetProperty("EntityKey").GetValue(entityWrapper, null);//獲取EntityKey屬性的值 return (EntityKey)entityKey; }
實現了IsAttached方法后,那么我們就再也不用擔心出現重復Attach的情況,使用方法很簡單,只需要在需要進行更新、刪除操作時前調用IsAttached方法判斷一下實體是否為Attached,若不是才Attach,否則忽略,代碼示例如下:
public virtual void Update(TEntity entity, bool autoCommit = false) { this.ValidateEntity(entity, false); if (!this.IsAttached(entity)) { this.objectSet.Attach(entity); this.baseContext.Entry(entity).State = EntityState.Modified; } if (autoCommit) { this.Commit(); } } public virtual void Remove(TEntity entity, bool autoCommit = false) { if (!this.IsAttached(entity)) { this.objectSet.Attach(entity); } this.objectSet.Remove(entity); if (autoCommit) { this.Commit(); } }
文章列表