我们在使用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 ,如需转载请自行联系原作者