之前的两篇有关EF4.1的文章反响不错,感谢大家的支持!想体验EF4.1的新功能?RTW版本已经发布啦,http://www.microsoft.com/downloads/en/details.aspx?FamilyID=b41c728e-9b4f-4331-a1a8-537d16c6acdf&displaylang=en
Entity Framework 4.1 DbContext使用记之一——如何查找实体? DbSet.Find函数的使用与实现
Entity Framework 4.1 DbContext使用记之二——如何玩转本地实体? DbSet.Local属性的使用与实现
今天的主题是如何玩转EF4.1中实体的属性。实体的属性其实是我们使用EF来访问和修改实体的关键。在EF以前版本中,如果我们一般会直接访问对象的属性,比如得到PersonID大于100的所有Person实体的ID和Name:
using
(var context
=
new
MyContext())
{
var people
=
context.People.Where(p
=>
p.PersonID
>
100
);
for
(var p
in
people)
{
Console.WriteLine(
"
ID: {0}, Name:{1}
"
, p.PersonID, p.Name);
}
}
但是如果要得到ObjectContext所track的实体属性的OriginalValues(原始值)和CurrentValues(当前值),则不是很方便。
而在EF4.1中,由于我们可以使用DbContext.Entry()或DbContext.Entry<T>()来得到DbEntityEntry或DbEntityEntry<T>对象。通过DbEntityEntry (DbEntityEntry<T>)的OriginalValues和CurrentValues属性,我们可以方便地得到相应的属性集合(DbPropertyValues)。通过DbEntityEntry (DbEntityEntry<T>)的Property(Property<T>)方法得到DbPropertyEntry(DbPropertyEntry<T>)之后,我们也能通过相应的OriginalValue和CurrentValue属性得到单个属性的原始值和当前值。感觉有点绕?看看下面的这些例子应该就简单明了多了!
using
(var context
=
new
MyDbContext())
{
//
有关Find方法,请看EF4.1系列博文一
var person
=
context.People.Find(
1
);
//
得到Person.Name属性的当前值
string
currentName
=
context.Entry(person).Property(p
=>
p.Name).CurrentValue;
//
设置Person.Name属性的当前值
context.Entry(person).Property(p
=>
p.Name).CurrentValue
=
"
Michael
"
;
//
通过string值"Name"来获得DbPropertyEntry,而非通过Lambda Expression
object
currentName2
=
context.Entry(person).Property(
"
Name
"
).CurrentValue;
}
再来看看如何得到实体的所有属性的OriginalValue(原始值),CurrentValue(当前值)和DatabaseValue(数据库值)吧:
using
(var context
=
new
MyDbContext())
{
var person
=
context.People.Find(
1
);
//
改变对应的实体的Name属性
person.Name
=
"
Michael
"
;
//
改变对应属性的数据库值
context.Database.ExecuteSqlCommand(
"
update Person set Name = 'Lingzhi' where PersonID = 1
"
);
//
输出对应实体所有属性的当前值,原始值和数据库值
Console.WriteLine(
"
Current values:
"
);
PrintValues(context.Entry(person).CurrentValues);
Console.WriteLine(
"
\nOriginal values:
"
);
PrintValues(context.Entry(person).OriginalValues);
Console.WriteLine(
"
\nDatabase values:
"
);
PrintValues(context.Entry(person).GetDatabaseValues());
}
这里用到的PrintValues函数实现如下:
public
void
PrintValues(DbPropertyValues values)
{
foreach
(var propertyName
in
values.PropertyNames)
{
Console.WriteLine(
"
Property {0} has value {1}
"
,
propertyName, values[propertyName]);
}
}
这里具体的输出大家就当小练习吧,呵呵。
下面再为大家介绍两个我个人认为比较cool的小功能:
1) 设置多层的复杂类型的属性。
例如,Person实体含有Address复杂类型属性(Complex - type),Address又含有City这一string类型属性,那么我们可以这样来获得City这一属性的当前值:
//
使用Lambda Expression
string
city
=
context.Entry(person).Property(p
=>
p.Address.City).CurrentValue;
//
使用string类型的属性表达
object
city
=
context.Entry(person).Property(
"
Address.City
"
).CurrentValue;
这里层次的次数其实没有限制,可以一直点下去的。:) EF的内部实现也是使用递归调用的方式,之后会有详细的介绍。
2) 克隆含有实体属性当期值,原始值或数据库值的对象。
我们可以使用DbPropertyValues.ToObject()方法来克隆将DbPropertyValues中相应的属性和值变成对象。
using
(var context
=
new
MyDbContext())
{
var person
=
context.People.Find(
1
);
var clonedPerson
=
context.Entry(person).GetDatabaseValues().ToObject();
}
这里克隆到的对象不是实体类,也不被DbContext所跟踪。但这样的对象在处理数据库并发等问题时会很有用。
又到了探寻以上介绍的功能的内部实现的时候啦!我们还是照例使用.NET Reflector来查看EntityFramework.dll。 大家可以打开Reflector和我一起来做个简单的分析。
首先是从DbContext.Entry方法得到DbEntityEntry。Entry方法通过调用DbEntityEntry internal的构造函数DbEntityEntry(InternalEntityEntry internalEntityEntry)来创建一个DbEntityEntry对象。这里的InternalEntityEntry又是通过DbContext.InternalContext和先前传入Entry函数的实体对象来生成的。
public
InternalEntityEntry(InternalContext internalContext,
object
entity)
{
this
._internalContext
=
internalContext;
this
._entity
=
entity;
//
ObjectContextTypeCache应该是EF内部保存的静态的类型缓存,用于查找实体类型
this
._entityType
=
ObjectContextTypeCache.GetObjectType(
this
._entity.GetType());
//
InternalContext.GetStateEntry内部则调用了ObjectStateManager.TryGetObjectStateEntry方法
this
._stateEntry
=
this
._internalContext.GetStateEntry(entity);
if
(
this
._stateEntry
==
null
)
{
this
._internalContext.Set(
this
._entityType).InternalSet.Initialize();
}
}
下面来看看DbEntityEntry.CurrentValues/OriginalValues。CurrentValues和OriginalValues属性都是DbPropertyValues类型的。DbPropertyValues可以被理解为对于一个实体或复杂类型所有属性信息的集合。我们通过PropertyNames属性拿到其所有属性的名字,通过GetValue或SetValues方法得到或设置属性值。还能通过我们之前讨论的ToObject方法来克隆一个有用与对应实体或复杂类型对象一样属性值的对象。演示一下如何使用DbPropertyValues.SetValuesF方法:
using
(var context
=
new
MyDbContext())
{
var person
=
context.People.Find(
1
);
var person1
=
new
Person { PersonID
=
1
, Name
=
"
Michael
"
};
var person2
=
new
Person { PersonID
=
1
, Name
=
"
Lingzhi
"
};
var entry
=
context.Entry(person);
//
这里直接将拥有相应属性值的实体对象直接赋给SetValues方法,直接对person实体的CurrentValues和OriginalValues进行赋值
entry.CurrentValues.SetValues(person1);
entry.OriginalValues.SetValues(person2);
}
这里SetValues内部首先调用了DbHelpers.GetPropertyGetters方法。DbHelpers是System.Data.Entity.Internal命名空间下Internal的类,包含了许多静态的辅助方法。这里的GetPropertyGetters顾名思义就是得到属性Getter方法delegate的集合,内部当然是通过PropertyInfo以及.NET Reflection来完成。有了这个Getter方法delegate的集合,我们就能方便地拿到传入SetValues方法的对象的所有属性值了,最后则按照所得到的属性值来进行赋值。
接着我们再来看看如何从DbEntityEntry.Property得到DbPropertyEntry。我们可以传递两种property的表达方式给DbEntityEntry.Property方法:1) Lambda Expression 2) string类型的property表示。先来说说1),在将Lambda Expression解析为对应property时,EF使用了辅助静态方法DbHelpers.ParsePropertySelector,ParsePropertySelector又调用了另一静态辅助方法DbHelpers.TryParsePath。具体算法在这里就不做深入解析,简单说这个TryParsePath方法就是递归地将Lambda Expression所表示的property层层深入地获取到,例如像这样的Lambda Expression:person => person.Address.City最后就变为"Address.City"。在解析完毕之后,ParsePropertySelector其实也是将Lambda Expression变成了string类型的property表示。自然,EF此时就调用了第二个接受string类型的property表示的DbEntityEntry.Property重载。DbEntityEntry.Property(string)在经过了一系列其他的内部调用之后,到了System.Data.Entity.Internal.InternalEntityEntry.Property(...):
private
InternalPropertyEntry Property(InternalPropertyEntry parentProperty,
string
propertyName, IList
<
string
>
properties, Type requestedType,
bool
requireComplex)
{
bool
flag
=
properties.Count
>
1
;
Type type
=
flag
?
typeof
(
object
) : requestedType;
Type declaringType
=
(parentProperty
!=
null
)
?
parentProperty.EntryMetadata.ElementType :
this
.EntityType;
PropertyEntryMetadata metadata
=
this
.ValidateAndGetPropertyMetadata(properties[
0
], declaringType, type);
if
((metadata
==
null
)
||
((flag
||
requireComplex)
&&
!
metadata.IsComplex))
{
if
(flag)
{
throw
Error.DbEntityEntry_DottedPartNotComplex(properties[
0
], propertyName, declaringType.Name);
}
throw
(requireComplex
?
Error.DbEntityEntry_NotAComplexProperty(properties[
0
], declaringType.Name) : Error.DbEntityEntry_NotAScalarProperty(properties[
0
], declaringType.Name));
}
InternalPropertyEntry entry
=
(InternalPropertyEntry) metadata.CreateMemberEntry(
this
, parentProperty);
if
(
!
flag)
{
return
entry;
}
return
this
.Property(entry, propertyName,
properties.Skip
<
string
>
(
1
).ToList
<
string
>
()
, requestedType, requireComplex);
}
从标黄的语句中不难发现,这个函数也是递归调用,并且将多层的property一一解析。 System.Data.Entity.Internal.InternalEntityEntry.Property函数返回InternalPropertyEntry类型的对象。这个对象又被返回给DbPropertyEntry.Create方法,Create方法又调用了InternalPropertyEntry.CreateDbMemberEntry:
public
override
DbMemberEntry
<
TEntity, TProperty
>
CreateDbMemberEntry
<
TEntity, TProperty
>
()
where
TEntity:
class
{
if
(
!
this
.EntryMetadata.IsComplex)
{
return
new
DbPropertyEntry
<
TEntity, TProperty
>
(
this
);
}
return
new
DbComplexPropertyEntry
<
TEntity, TProperty
>
(
this
);
}
这里的逻辑很简单,将property分成一般的属性或复杂类型的属性,分别处理之。分析到这里,大家应该也发现了DbPropertyEntry中大部分信息都保存在其内部的InternalPropertyEntry对象里。这样的设计在解析EntityFramework.dll时,我们会经常看到。
呼!这篇文章不是一口气写完的了,这几天挺忙的,不过每天添几笔,可能思路有些混乱,大家见谅,哈哈!还是那句话,希望对您学习EF有点帮助吧!
PS1:这里为大家带来一个好消息:微软一站式实例代码库(Microsoft All-In-One Code Framework)即日起正式迁移至MSDN代码库了,新的平台会帮您更轻松地解决开发难题、节省更多时间、获得更友好的用户体验。本人作为这个项目的元老,见到我们已拥有600多个经典的代码实例,甚感欣慰啊! 更详细信息,请看http://msdn.microsoft.com/zh-cn/hh124104.aspx?ocid=ban-f-cn-loc-OC201104-MSDN
之后我将尽力为大家带来更多有关EF的代码实例以及相关的介绍!
PS2:同事开发了一个很cool的MSDN论坛桌面小工具,绝对给力!欢迎使用!(我也出了不少力啊)
也欢迎到MSDN中文论坛ADO.NET与LINQ论坛来提问EF的问题啊,可以试试直接报我的名字Michael Sun,哈哈!
如需转发请注明原文出处,谢谢: http://www.cnblogs.com/LingzhiSun/archive/2011/04/13/EF41_WokingWithProperties.html