关于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  ,如需转载请自行联系原作者

相关文章
|
数据建模
SAP SEGW 里的 Entity Type 作用讲解
SAP SEGW 里的 Entity Type 作用讲解
157 0
SAP SEGW 里的 Entity Type 作用讲解
|
存储 开发框架 .NET
Entity Framework基础01
Entity Framework基础01
186 0
Entity Framework基础01
SAP S/4HANA key user tool extensibility原理
Reasons of different behavior in “UI and Reports” list
SAP S/4HANA key user tool extensibility原理
如何处理SAP CRM Web Service错误 - Virtual Interface Method XXXX not supported
如何处理SAP CRM Web Service错误 - Virtual Interface Method XXXX not supported
162 0
如何处理SAP CRM Web Service错误 - Virtual Interface Method XXXX not supported
filter operation implementation in SAP Gateway framework
Created by Wang, Jerry, last modified on Dec 30, 2015
filter operation implementation in SAP Gateway framework
|
数据库
Entity Framework 迁移
Entity Framework 迁移
104 0