关于Entity Framework中的Attached报错的完美解决方案

简介:

我们在使用Entity Framework进行CRUD时,为了提升查询效率,一般均会启动NoTracking,即不追踪变化,设置代码如下:

1
2
3
4
5
6
7
//这是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属性来进行判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <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,然后让所有的实体类均实现该接口,最后再改写方案一的方法即可完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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,意味着我们并不能在自己的项目中直接使用,唯一的办法就是采用反射来动态获取该属性,所以整个的实现方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// <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,否则忽略,代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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();
     }
}

 

本文转自 梦在旅途 博客园博客,原文链接:http://www.cnblogs.com/zuowj/p/4523075.html  ,如需转载请自行联系原作者

相关文章
|
6月前
|
存储 API 数据库
【Entity Framework】创建并配置模型
【Entity Framework】创建并配置模型
34 0
|
存储 开发框架 .NET
Entity Framework基础01
Entity Framework基础01
208 0
Entity Framework基础01
|
数据库
Entity Framework 迁移
Entity Framework 迁移
127 0
|
索引
Entity Framework 小知识(四)
Entity Framework 小知识(四)
147 0
|
数据库 容器
Entity Framework Core(3)-配置DbContext
设计时 DbContext 配置 EF Core 设计时工具如迁移需要能够发现和创建的工作实例DbContext以收集有关应用程序的实体类型以及它们如何映射到数据库架构的详细信息的类型。 此过程可以为自动,只要该工具可以轻松地创建DbContext,会将其配置同样到它如何将配置在运行时的方式。
971 0