Entity Framework技巧系列之二 - Tip 6 - 8
提示6. 如何及何時使用貪婪加載
什么時候你需要使用貪婪加載?
????通常在你的程序中你知道對查詢到的實體將要進行怎樣的操作。
例如,如果你查詢一個訂單以便為一個客戶重新打印,你知道沒有組成訂單的項目即產品的信息重打印將是不完整的,所以你知道你將需要同時加載這些信息。
????這是貪婪加載起作用的一類場景。
如果你知道你需要額外信息,或實體,你可能也會預先加載這些實體(貪婪加載),因為這將省下生在將來的查詢。
怎樣進行貪婪加載?
與一些普遍存在的錯誤觀念相反,Entity Framework中貪婪加載即可行也易行,你僅使用Include()方法如下這樣啟動查詢即可:
1 var reprint = (from order in ctx.Orders.Include("Items.Product") 2 where order.Customer.Name == "Fred Blogs" 3 && order.Status == "Unshipped" 4 select order).First();這個查詢意思是,在每個滿足查詢條件的order中包含其"Items",同時每個Item中也包含其"Product".
這樣的結果是,如下代碼:
1 foreach (var item in reprint.Items) 2 { 3 Console.WriteLine("\t{0} {1} = ${2}", 4 item.Quantity, 5 item.Product.Name, 6 item.Cost); 7 } 8 Console.WriteLine(reprint.TotalCost);不再需要新的查詢。
注意:
不需要顯式?Include("Items")?,對?Include("Items.Product")?的調用會隱式包含Items。
?
提示7 – 怎么在.NET 3.5 SP1中模擬外鍵屬性
背景
如果你一直關注EF Design Blog這個團隊博客,你可能已經看到近期我們宣布了一個用于.NET 4.0中EF的稱為FK Associations的特性。
然而在.NET 3.5 SP1中,我們僅支持獨立關聯。這表示外鍵列不會做為可用的屬性出現在實體中。相反這表示你不得不通過到其它實體的引用來建立關聯。
例如,不同于Linq to SQL,下面的寫法在EF中不可用:
1 product.CategoryID = 2;因為在product實體中沒有"CategoryID"這個屬性。
你不得不使用類似下面的替換方法:
1 product.Category = ctx.Categories.First(c => c.ID == 2);也就是你需要使用Category引用。這意味著你不得不在內存中放置一個Category對象,或者像上面例子那樣通過查詢獲取一個。
不存在一個你可以直接設置CategoryID的屬性的問題可能造成很大的麻煩,而這就是我們在.NET 4.0中添加了FK Associations與FK Properties的原因。
這看起來都不錯,但Julie Lerman總習慣提醒我,現在正在使用.NET 3.5 SP1的人們怎么辦?我們的新特性現在幫不上忙。
所以…
怎樣在CategoryID不實際存在的情況下設置它?
這問題是個圈套。你當然實現不了這種想法。
但是你可以使用下面的代碼實現同樣的效果:
1 product.CategoryReference.EntityKey = new EntityKey("MyContainer.Categories", "ID", 2);這看起來有點復雜不是嗎?所以讓我們拆開分析:
1. 對于每一個像Category一樣的引用"xxx",Entity Framework也生成了一個返回EntityReference類型的"xxxReference"屬性。(EF中大量使用EntityReference來提供完成像保持關系一致性,及基于EntityKeys的實體查找等操作所需的設施與服務)
2. EntityReference的其中一個屬性是EntityKey。當我們希望改變外鍵的值時我們需要將一個新的EntityKey設置到EntityKey屬性。
3. 要創建一個新的EntityKey,我們需要知道如下3條:
????1). 我們想要設置給"外鍵"的值。與前面代碼段一致,這個值就是2。
????2). 目標Entity所屬的EntitySet的完成限定名。在這個例子中,我們的目標是一個Category,我們由"MyContainer.Categories"集來獲取Category。
????3). 目標實體中主鍵屬性的名稱。這個例子中Category的主鍵為"ID"屬性。
很顯然這不是你想要在你需要的任何地方書寫的那種類型的代碼。所以我們以這種方式來封裝。以便…
怎樣使用這些代碼來仿造一個外鍵屬性?
幸運的是Entity Framework為實體生成了部分類,所以你可以像這樣簡單的將這些邏輯放置于你自己的Product部分類中:
1 public int CategoryID { 2 get {3 if (this.CategoryReference.EntityKey == null) return 0;4 else return (int) this.CategoryReference 5 .EntityKey.EntityKeyValues[0].Value; 6 } 7 set { 8 this.CategoryReference.EntityKey 9 = new EntityKey("MyContainer.Categories", "ID", value); 10 } 11 }注意我們在get訪問器中也使用了CategoryReference。這樣我們的CategoryID屬性僅就是一個基于CategoryReference的視圖,這樣如果EF在底層做了任何改變我們也無需被通知。
添加這樣的代碼后,之前我們想要的寫法也就可以實現:
1 product.CategoryID = 2;這在做法在很多場景中都特別有用,例如MVC控制器與數據綁定中。
外傳
這種解決方案本質上僅僅是隱藏了獨立關聯的一些限制的變通方案,同所有的變通方案其也存在缺陷。關鍵就是在優點與缺陷之間做出全面的權衡。一些關鍵的缺陷如下:
1. 部分類中的屬性不會被Entity Framework所識別,所以你不能在LINQ查詢中使用它們。
2. 由于在setter訪問器中我們通過完全限定名引用了EntitySet,我們將Entity Class與EntitySet直接耦合在一起。對于大多數人這不成問題,但是當你試圖在不同上下文間重用你的實體類型或使用MEST(MultipleEntitySets per?Type)時這種方法不會工作。
3. 可能還有更多缺陷…當想到它們時我將陸續添加!
?
提示8 – 怎樣使用LINQ to Entities編寫"WHERE IN"形式的查詢
想象你有一個人員表,你想查詢其中姓氏包含于一個有趣的姓氏列表中的那部分人。在SQL中這很平常,你編寫這樣的查詢:
1 SELECT * FROM People 2 WHERE Firstname IN ('Alex', 'Colin', 'Danny', 'Diego')SQL中的IN等價于LINQ中的Contains
在LINQ(to objects)的世界不存在'IN',所以你需要像倒置順序那樣來使用Contains方法:
1 var names = new string[] { "Alex", "Colin", "Danny", "Diego" }; 2 var matches = from person in people 3 where names.Contains(person.Firstname) 4 select person;注意語義上我們已經由SQL中的:
?value.IN(set)???
變為LINQ中的
?set.Contains(value)??
結果是相同的。
.NET 3.5 SP1與.NET 4.0對Contains的支持
在.NET 4.0的EF中將支持?IEnumerable<T>.Contains(T t)?方法,所以在下個版本EF中你可以編寫上文LINQ查詢那樣的查詢。
不幸的是這種寫法不被.NET 3.5 SP1支持:當遇到這種形式的LINQ表達式時LINQ to Entities會報錯,因為其不知道怎樣將對Contains的調用轉換為SQL。
但是我們.NET 3.5 SP1的用戶怎么辦呢?他們應該怎樣做?
.NET 3.5 SP1的變通方案
有一個由EF團隊"大腦"之一Colin提供的變通方案。
這種變通方案的本質是你可以以如下方式來重寫上面的查詢:
1 var matches = from person in people 2 where person.Firstname == "Alex" || 3 person.Firstname == "Colin" || 4 person.Firstname == "Danny" || 5 person.Firstname == "Diego" 6 select person;當然這會使代碼變長且編寫這樣的代碼是痛苦的,但是它同樣可以工作。
所以如果有一些工具方法可以很容易的創建這種類型的LINQ表達式,我們將很好的處理業務。
而恰巧不久之前Colin在論壇上給出了他完成的幫助方法,借助他的方法,你可以如下這樣編寫查詢:
1 var matches = ctx.People.Where( 2 BuildOrExpression<People, string>( 3 p => p.Firstname, names 4 ) 5 );這會生成一個與下面語句相同效果的查詢:
1 var matches = from p in ctx.People 2 where names.Contains(p.Firstname) 3 select p;但事實上后者一個明顯的問題是其不可以在.NET 3.5 SP1下工作。
外傳
如果你已經閱讀至此,很好!
下面是使變通寫法成為可能的功能函數:
1 public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>( 2 Expression<Func<TElement, TValue>> valueSelector, 3 IEnumerable<TValue> values 4 ) 5 { 6 if (null == valueSelector) 7 throw new ArgumentNullException("valueSelector");8 if (null == values) 9 throw new ArgumentNullException("values"); 10 ParameterExpression p = valueSelector.Parameters.Single(); 11 if (!values.Any()) 12 return e => false; 13 var equals = values.Select(value => 14 (Expression)Expression.Equal( 15 valueSelector.Body, 16 Expression.Constant( 17 value, 18 typeof(TValue) 19 ) 20 ) 21 ); 22 var body = equals.Aggregate<Expression>( 23 (accumulate, equal) => Expression.Or(accumulate, equal) 24 ); 25 26 return Expression.Lambda<Func<TElement, bool>>(body, p); 27 }此函數本質上構造了一個針對所有使用?valueSelector (i.e. p => p.Firstname)?的值斷言表達式,并將這些斷言進行OR連接來為這個完整的預言創建一個表達式,除此之外,我不打算試圖解釋這個函數。
轉載于:https://www.cnblogs.com/sylone/p/6094548.html
總結
以上是生活随笔為你收集整理的Entity Framework技巧系列之二 - Tip 6 - 8的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 判断ic卡类型
- 下一篇: 在Asp.net core返回PushS