Entity Framework 4.1 DbContext使用記之三——如何玩轉實體的屬性值?
之前的兩篇有關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:
{
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屬性得到單個屬性的原始值和當前值。感覺有點繞?看看下面的這些例子應該就簡單明了多了!
{
// 有關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(數據庫值)吧:
{
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函數實現如下:
{
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這一屬性的當前值:
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中相應的屬性和值變成對象。
{
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函數的實體對象來生成的。
{
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方法:
{
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(...):
{
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:
{
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.rzrgm.cn/LingzhiSun/archive/2011/04/13/EF41_WokingWithProperties.html



浙公網安備 33010602011771號