文章出處

我們在使用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();
            }
        }

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()