Entity Framework 4.1 DbContext使用记之三——如何玩转实体的属性值?
之前的兩篇有關(guān)EF4.1的文章反響不錯(cuò),感謝大家的支持!想體驗(yàn)EF4.1的新功能?RTW版本已經(jīng)發(fā)布啦,http://www.microsoft.com/downloads/en/details.aspx?FamilyID=b41c728e-9b4f-4331-a1a8-537d16c6acdf&displaylang=en???
Entity Framework 4.1 DbContext使用記之一——如何查找實(shí)體? DbSet.Find函數(shù)的使用與實(shí)現(xiàn)
Entity Framework 4.1 DbContext使用記之二——如何玩轉(zhuǎn)本地實(shí)體? DbSet.Local屬性的使用與實(shí)現(xiàn)
?
今天的主題是如何玩轉(zhuǎn)EF4.1中實(shí)體的屬性。實(shí)體的屬性其實(shí)是我們使用EF來訪問和修改實(shí)體的關(guān)鍵。在EF以前版本中,如果我們一般會(huì)直接訪問對(duì)象的屬性,比如得到PersonID大于100的所有Person實(shí)體的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的實(shí)體屬性的OriginalValues(原始值)和CurrentValues(當(dāng)前值),則不是很方便。
?
而在EF4.1中,由于我們可以使用DbContext.Entry()或DbContext.Entry<T>()來得到DbEntityEntry或DbEntityEntry<T>對(duì)象。通過DbEntityEntry (DbEntityEntry<T>)的OriginalValues和CurrentValues屬性,我們可以方便地得到相應(yīng)的屬性集合(DbPropertyValues)。通過DbEntityEntry (DbEntityEntry<T>)的Property(Property<T>)方法得到DbPropertyEntry(DbPropertyEntry<T>)之后,我們也能通過相應(yīng)的OriginalValue和CurrentValue屬性得到單個(gè)屬性的原始值和當(dāng)前值。感覺有點(diǎn)繞?看看下面的這些例子應(yīng)該就簡單明了多了!?
using?(var?context?=?new?MyDbContext()){
????//?有關(guān)Find方法,請(qǐng)看EF4.1系列博文一
????var?person?=?context.People.Find(1);
????//?得到Person.Name屬性的當(dāng)前值
????string?currentName?=?context.Entry(person).Property(p?=>?p.Name).CurrentValue;
????//?設(shè)置Person.Name屬性的當(dāng)前值
????context.Entry(person).Property(p?=>?p.Name).CurrentValue?=?"Michael";
????//?通過string值"Name"來獲得DbPropertyEntry,而非通過Lambda?Expression
????object?currentName2?=?context.Entry(person).Property("Name").CurrentValue;
}
?
再來看看如何得到實(shí)體的所有屬性的OriginalValue(原始值),CurrentValue(當(dāng)前值)和DatabaseValue(數(shù)據(jù)庫值)吧:
using?(var?context?=?new?MyDbContext()){
????var?person?=?context.People.Find(1);
????//?改變對(duì)應(yīng)的實(shí)體的Name屬性
????person.Name?=?"Michael";
????//?改變對(duì)應(yīng)屬性的數(shù)據(jù)庫值
????context.Database.ExecuteSqlCommand("update?Person set?Name?=?'Lingzhi'?where PersonID =?1");
????//?輸出對(duì)應(yīng)實(shí)體所有屬性的當(dāng)前值,原始值和數(shù)據(jù)庫值
????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函數(shù)實(shí)現(xiàn)如下:
public?void?PrintValues(DbPropertyValues?values){
????foreach?(var?propertyName?in?values.PropertyNames)
????{
????????Console.WriteLine("Property?{0}?has?value?{1}",
??????????????????????????propertyName,?values[propertyName]);
????}
}
這里具體的輸出大家就當(dāng)小練習(xí)吧,呵呵。
?
下面再為大家介紹兩個(gè)我個(gè)人認(rèn)為比較cool的小功能:
1) 設(shè)置多層的復(fù)雜類型的屬性。
例如,Person實(shí)體含有Address復(fù)雜類型屬性(Complex - type),Address又含有City這一string類型屬性,那么我們可以這樣來獲得City這一屬性的當(dāng)前值:
//?使用Lambda?Expressionstring?city?=?context.Entry(person).Property(p?=>?p.Address.City).CurrentValue;
//?使用string類型的屬性表達(dá)
object?city?=?context.Entry(person).Property("Address.City").CurrentValue;
這里層次的次數(shù)其實(shí)沒有限制,可以一直點(diǎn)下去的。:) ? EF的內(nèi)部實(shí)現(xiàn)也是使用遞歸調(diào)用的方式,之后會(huì)有詳細(xì)的介紹。?
?
2) 克隆含有實(shí)體屬性當(dāng)期值,原始值或數(shù)據(jù)庫值的對(duì)象。
我們可以使用DbPropertyValues.ToObject()方法來克隆將DbPropertyValues中相應(yīng)的屬性和值變成對(duì)象。
using?(var?context?=?new?MyDbContext()){
????var?person?=?context.People.Find(1);
????var?clonedPerson?=?context.Entry(person).GetDatabaseValues().ToObject();
}?
這里克隆到的對(duì)象不是實(shí)體類,也不被DbContext所跟蹤。但這樣的對(duì)象在處理數(shù)據(jù)庫并發(fā)等問題時(shí)會(huì)很有用。
又到了探尋以上介紹的功能的內(nèi)部實(shí)現(xiàn)的時(shí)候啦!我們還是照例使用.NET Reflector來查看EntityFramework.dll。 大家可以打開Reflector和我一起來做個(gè)簡單的分析。
首先是從DbContext.Entry方法得到DbEntityEntry。Entry方法通過調(diào)用DbEntityEntry internal的構(gòu)造函數(shù)DbEntityEntry(InternalEntityEntry internalEntityEntry)來創(chuàng)建一個(gè)DbEntityEntry對(duì)象。這里的InternalEntityEntry又是通過DbContext.InternalContext和先前傳入Entry函數(shù)的實(shí)體對(duì)象來生成的。
public?InternalEntityEntry(InternalContext?internalContext,?object?entity){
????this._internalContext?=?internalContext;
????this._entity?=?entity;
????
?? ?//?ObjectContextTypeCache應(yīng)該是EF內(nèi)部保存的靜態(tài)的類型緩存,用于查找實(shí)體類型
????this._entityType?=?ObjectContextTypeCache.GetObjectType(this._entity.GetType());
????
?? ?//?InternalContext.GetStateEntry內(nèi)部則調(diào)用了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可以被理解為對(duì)于一個(gè)實(shí)體或復(fù)雜類型所有屬性信息的集合。我們通過PropertyNames屬性拿到其所有屬性的名字,通過GetValue或SetValues方法得到或設(shè)置屬性值。還能通過我們之前討論的ToObject方法來克隆一個(gè)有用與對(duì)應(yīng)實(shí)體或復(fù)雜類型對(duì)象一樣屬性值的對(duì)象。演示一下如何使用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);
????
????//?這里直接將擁有相應(yīng)屬性值的實(shí)體對(duì)象直接賦給SetValues方法,直接對(duì)person實(shí)體的CurrentValues和OriginalValues進(jìn)行賦值
????entry.CurrentValues.SetValues(person1);
????entry.OriginalValues.SetValues(person2);
}
?
這里SetValues內(nèi)部首先調(diào)用了DbHelpers.GetPropertyGetters方法。DbHelpers是System.Data.Entity.Internal命名空間下Internal的類,包含了許多靜態(tài)的輔助方法。這里的GetPropertyGetters顧名思義就是得到屬性Getter方法delegate的集合,內(nèi)部當(dāng)然是通過PropertyInfo以及.NET Reflection來完成。有了這個(gè)Getter方法delegate的集合,我們就能方便地拿到傳入SetValues方法的對(duì)象的所有屬性值了,最后則按照所得到的屬性值來進(jìn)行賦值。
?
接著我們?cè)賮砜纯慈绾螐腄bEntityEntry.Property得到DbPropertyEntry。我們可以傳遞兩種property的表達(dá)方式給DbEntityEntry.Property方法:1) Lambda Expression ? 2) string類型的property表示。先來說說1),在將Lambda Expression解析為對(duì)應(yīng)property時(shí),EF使用了輔助靜態(tài)方法DbHelpers.ParsePropertySelector,ParsePropertySelector又調(diào)用了另一靜態(tài)輔助方法DbHelpers.TryParsePath。具體算法在這里就不做深入解析,簡單說這個(gè)TryParsePath方法就是遞歸地將Lambda Expression所表示的property層層深入地獲取到,例如像這樣的Lambda Expression:person => person.Address.City最后就變?yōu)?#34;Address.City"。在解析完畢之后,ParsePropertySelector其實(shí)也是將Lambda Expression變成了string類型的property表示。自然,EF此時(shí)就調(diào)用了第二個(gè)接受string類型的property表示的DbEntityEntry.Property重載。DbEntityEntry.Property(string)在經(jīng)過了一系列其他的內(nèi)部調(diào)用之后,到了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);
}?
從標(biāo)黃的語句中不難發(fā)現(xiàn),這個(gè)函數(shù)也是遞歸調(diào)用,并且將多層的property一一解析。?System.Data.Entity.Internal.InternalEntityEntry.Property函數(shù)返回InternalPropertyEntry類型的對(duì)象。這個(gè)對(duì)象又被返回給DbPropertyEntry.Create方法,Create方法又調(diào)用了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分成一般的屬性或復(fù)雜類型的屬性,分別處理之。分析到這里,大家應(yīng)該也發(fā)現(xiàn)了DbPropertyEntry中大部分信息都保存在其內(nèi)部的InternalPropertyEntry對(duì)象里。這樣的設(shè)計(jì)在解析EntityFramework.dll時(shí),我們會(huì)經(jīng)常看到。
?
呼!這篇文章不是一口氣寫完的了,這幾天挺忙的,不過每天添幾筆,可能思路有些混亂,大家見諒,哈哈!還是那句話,希望對(duì)您學(xué)習(xí)EF有點(diǎn)幫助吧!
?
PS1:這里為大家?guī)硪粋€(gè)好消息:微軟一站式實(shí)例代碼庫(Microsoft All-In-One Code Framework)即日起正式遷移至MSDN代碼庫了,新的平臺(tái)會(huì)幫您更輕松地解決開發(fā)難題、節(jié)省更多時(shí)間、獲得更友好的用戶體驗(yàn)。本人作為這個(gè)項(xiàng)目的元老,見到我們已擁有600多個(gè)經(jīng)典的代碼實(shí)例,甚感欣慰啊!??更詳細(xì)信息,請(qǐng)看http://msdn.microsoft.com/zh-cn/hh124104.aspx?ocid=ban-f-cn-loc-OC201104-MSDN
之后我將盡力為大家?guī)砀嘤嘘P(guān)EF的代碼實(shí)例以及相關(guān)的介紹!
PS2:同事開發(fā)了一個(gè)很cool的MSDN論壇桌面小工具,絕對(duì)給力!歡迎使用!(我也出了不少力啊)
也歡迎到MSDN中文論壇ADO.NET與LINQ論壇來提問EF的問題啊,可以試試直接報(bào)我的名字Michael Sun,哈哈!
?
如需轉(zhuǎn)發(fā)請(qǐng)注明原文出處,謝謝:?http://www.cnblogs.com/LingzhiSun/archive/2011/04/13/EF41_WokingWithProperties.html
轉(zhuǎn)載于:https://www.cnblogs.com/LingzhiSun/archive/2011/04/13/EF41_WokingWithProperties.html
總結(jié)
以上是生活随笔為你收集整理的Entity Framework 4.1 DbContext使用记之三——如何玩转实体的属性值?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多思计组原理虚拟实验室_先睹为快!汽院实
- 下一篇: 第4天:调用样式表