關於Entity Framework中的Attached報錯的完美解決方案


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

 


免責聲明!

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



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