• 关于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();
                }
            }
    
  • 相关阅读:
    PAT 甲级 1027 Colors in Mars
    PAT 甲级 1026 Table Tennis(模拟)
    PAT 甲级 1025 PAT Ranking
    PAT 甲级 1024 Palindromic Number
    PAT 甲级 1023 Have Fun with Numbers
    PAT 甲级 1021 Deepest Root (并查集,树的遍历)
    Java实现 蓝桥杯VIP 算法训练 无权最长链
    Java实现 蓝桥杯VIP 算法训练 无权最长链
    Java实现 蓝桥杯 算法提高 抽卡游戏
    Java实现 蓝桥杯 算法提高 抽卡游戏
  • 原文地址:https://www.cnblogs.com/zuowj/p/4523075.html
Copyright © 2020-2023  润新知