LINQ之路10:LINQ to SQL 和 Entity Framework(下)

來源:互聯網
上載者:User

在本篇中,我們將接著上一篇“LINQ to SQL 和 Entity Framework(上)”的內容,繼續使用LINQ to SQL和Entity Framework來實踐“解釋查詢”,學習這些技術的關鍵特性。我們在此關注的是LINQ to SQL和Entity Framework中的”LINQ”部分,並會比較這兩種技術的相同和不同之處。通過我們之前介紹的LINQ知識還有將來會討論的更多LINQ Operators,相信閱者能針對LINQ to SQL和Entity Framework寫出優雅高效的查詢。為了簡單清晰,文中有些地方對LINQ to SQL和Entity Framework進行了縮寫,分別為:L2S和EF。

LINQ to SQL和Entity Framework的順延強制

和本地查詢一樣,L2S和EF查詢也是順延強制的,這樣就允許我們漸進地建立LINQ查詢。但是,有一個方面,L2S和EF有自己特殊的順延強制語義,這就是當一個子查詢出現在Select運算式中時:

  • 對於本地查詢,你獲得了兩個順延強制,因為從功能角度來看,你選擇了包含多個查詢的一個sequence。若以當你遍曆外層結果sequence時,並不會遍曆內部子查詢,所以子查詢此時也就不會執行。
  • 而對於L2S/EF,子查詢和外層的主查詢在同一時間被執行,這樣就避免了過度的串連遠端資料庫導致效能問題。

比如,對於L2S/EF,下面的查詢在第一個foreach語句時執行,且只執行一次:

            var context = new LifePoemContext("database connection string");

var query = from c in context.Customers
select
from o in context.Orders
select new { c.Name, o.Price };

foreach (var customerOrders in query)
foreach (var namePrice in customerOrders)
Console.WriteLine(namePrice.Name + " spent " + namePrice.Price);

換句話說,我們在select運算式中明確指定的EntitySets/EntityCollections總是在一次執行中就能被擷取到:

            var context = new LifePoemContext("database connection string");
var query = from c in context.Customers
select new { c.Name, c.Orders };

foreach (var row in query)
foreach (var order in row.Orders) // 沒有額外的串連查詢
Console.WriteLine(row.Name + " spent " + order.Price);

但如果我們沒有事先進行資料轉換,就對EntitySet/EntityCollection屬性進行遍曆的話,就會適用於順延強制。下面的樣本中,L2S和EF在每一次迴圈中都會執行另外的Orders查詢:

            context.ContextOptions.DeferredLoadingEnabled = true;   // 僅EF需要此句

foreach (Customer c in context.Customers)
foreach (Order o in c.Orders) // 每次都會開始一個新的SQL查詢
Console.WriteLine(c.Name + " spent " + o.Price);

這種模式在我們需要有條件的執行內部查詢時具有優勢,比如我們可能需要依靠用戶端來做某個條件測試時:

            foreach (Customer c in context.Customers)
if(myWebService.HasBadCreditHistory(c.ID))
foreach (Order o in c.Orders) // 開始一個新的SQL查詢
Console.WriteLine(c.Name + " spent " + o.Price);

上面我們看到了如何對關聯屬性進行顯示的資料轉換(select)來避免重複執行。稍後我們就會看到,L2S和EF還提供了其他的機制來實現這個功能。

 

DataLoadOptions

DataLoadOptions類是L2S的特性,它有兩個特殊的作用:

  • AssociateWith讓你能夠事先對EntitySet關聯設定過濾條件
  • LoadWith讓你能夠設定某些EntitySets為主動載入(eager loading),從而減少串連資料庫的次數

設定過濾條件

假如我們只關心那些Price大於1000的Orders,我們就可以通過DataLoadOptions來設定過濾條件:

            var context = new LifePoemContext("database connection string");

DataLoadOptions options = new DataLoadOptions();
options.AssociateWith<Customer>(c => c.Orders.Where(order => order.Price > 1000));
context.LoadOptions = options;

foreach (Customer c in context.Customers)
if (myWebService.HasBadCreditHistory(c.ID))
ProcessCustomer(c); // 如果在該方法中引用c.Orders,只有那些Price > 1000的Orders被返回

這會指示我們的DataContext執行個體總是使用給定的條件對Customer的Orders進行過濾。需要注意的是,AssociateWith並不會改變順延強制的語義,它只是命令對特定的關係進行隱式的過濾。

主動載入(Eager Loading)

DataLoadOptions的第二個作用是請求讓某個EntitySets跟隨父EntitySets一起載入。比如,假設你想在載入所有Customers的同時一起載入他們的Orders,而不是對每一個Customer分別查詢一次Orders:

            var context = new LifePoemContext("database connection string");

DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Customer>(c => c.Orders);
context.LoadOptions = options;

foreach (Customer c in context.Customers) // 一次查詢
foreach (Order o in c.Orders) // 因為上面的DataLoadOptions,所有的Orders都在上面的查詢中被同時Load了
Console.WriteLine(c.Name + " bought a " + o.Description);

這會指示DataContext,不論何時,只要一個Customer被擷取,它的Orders也會在同一時間被載入。我們可以組合LoadWith和AssociateWith方法,這樣既能獲得主動載入,還能對載入的EntitySets進行過濾,比如:

            DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Customer>(c => c.Orders);
options.AssociateWith<Customer>(c => c.Orders.Where(order => order.Price > 1000));

Entity Framework中的主動載入(Eager Loading)

在Entity Framework中,如果我們希望某個關聯的EntitySets被主動載入,則可以使用Include方法。下面的代碼就是在一個SQL查詢中,擷取所有的Customers和他們的Orders:

            foreach (var c in context.Customers.Include("Orders"))
foreach (var order in c.Orders)
Console.WriteLine(order.Description);

Include可以被級聯使用,假如每個Order都有OrderDetails和SalesPersons導覽屬性的話,我們可以寫出如下的查詢讓這些資料也被一同載入:

            context.Customers.Include("Orders.OrderDetails")
.Include("Orders.SalesPersons")

 

更新

L2S和EF也會跟蹤你對Entities所做的修改並允許你把他們更新到資料庫。對於L2S,我們調用DataContext對象的SubmitChanges方法;而對於EF,我們調用ObjectContext對象的SaveChagnes方法。

L2S的Table<>類提供了InsertOnSubmit和DeleteOnSubmit方法讓我們從一個表中插入或刪除行;EF的ObjectSet<>類則提供了AddObject和DeleteObject方法來實現相同的功能,請看下面的程式碼範例:

            var context = new LifePoemContext("database connection string");

Customer cust = new Customer { ID = 1000, Name = "Yoyoo" };
context.Customers.InsertOnSubmit(cust); // 插入Customer,在EF中使用AddObject
context.SubmitChanges(); // 在EF中使用SaveChanges

// 現在我們擷取上面插入的資料行,對其進行更新,然後刪除它
Customer cust2 = context.Customers.Single(c => c.ID == 1000);
cust2.Name = "Yoyoo2";
context.SubmitChanges(); // 更新Customer

context.Customers.DeleteOnSubmit(cust2); //在EF中使用DeleteObject
context.SubmitChanges(); // 刪除Customer

SubmitChanges/SaveChanges會收集自context建立(或上一次Save)以來對entities所做的所有修改,然後執行一個SQL語句來把他們寫回資料庫。

我們還可以調用Add方法來向一個EntitySet/EntitiyCollection添加資料行,在執行SubmitChanges或SaveChanges,L2S和EF會自動產生相應的外鍵:

            Order o1 = new Order { ID = 100, OrderDate = DateTime.Now, Price = 100 };
Order o2 = new Order { ID = 101, OrderDate = DateTime.Now, Price = 500 };

Customer cust = context.Customers.Single(c => c.ID == 1);
cust.Orders.Add(o1);
cust.Orders.Add(o2);

context.SubmitChanges();

在這個例子中,L2S/EF會自動把外索引值1寫入新增的Order的CustomerID列,這是因為我們為Customer和Order定義了關聯屬性,如:

        // With L2S
[Association(Name="Customer_Order", Storage="_Orders", ThisKey="ID", OtherKey="CustomerID")]
public EntitySet<Order> Orders { get {...} set {...} }

當你從一個EntitySet/EntityCollection中移除某行資料時,他的外鍵列會被自動化佈建為null。下面的代面會在我們最近新增的兩個orders和他們的Customer之間移除關聯,注意,只是去除關聯,Remove並不會刪除子entities:

            var context = new LifePoemContext("database connection string");

Customer cust = context.Customers.Single(c => c.ID == 1);
cust.Orders.Remove(cust.Orders.Single(order => order.ID == 100));
cust.Orders.Remove(cust.Orders.Single(order => order.ID == 101));

context.SubmitChanges();

因為上面的代碼會把每個Order的CustomerID列設為空白,所以資料庫中Order.CustomerID列必需是可空的,否則會拋出異常。

如果我們要完全刪除子entities,則需要調用DeleteOnSubmit:

            // with L2S
context.Orders.DeleteOnSubmit(context.Orders.Single(order => order.ID == 100));
context.Orders.DeleteOnSubmit(context.Orders.Single(order => order.ID == 101));
context.SubmitChanges();

// with EF
context.Orders.DeleteObject(context.Orders.Single(order => order.ID == 100));
context.Orders.DeleteObject(context.Orders.Single(order => order.ID == 101));
context.SaveChanges();

 

LINQ to SQL和Entity Framework的API對比

正如我們在這兩篇文章中看到的那樣,L2S和EF在LINQ查詢和資料更新方面非常相似,只是建立的對象或調用的方法有所不同罷了,下表總結了他們的API差異: 

目的

LINQ to SQL

Entity Framework

擷取保持所有CRUD操作的類

DataContext

ObjectContext

從資料庫中(延遲)擷取某種類型的所有entities

GetTable

CreateObjectSet

上面方法的傳回型別

Table<T>

ObjectSet<T>

提交對實體物件的更新

SubmitChanges

SaveChanges

新增一個entity

InsertOnSubmit

AddObject

刪除一個entity

DeleteOnSubmit

DeleteObject

代表關聯屬性(有多個相關entities的那一方)的類型

EntitySet<T>

EntityCollection<T>

代表關聯屬性(有多個相關entities的那一方)的類型(欄位類型)

EntityRef<T>

EntityReference<T>

裝載關聯屬性時的預設策略

自動消極式載入

明確調用

主動載入(eager loading)

DataLoadOptions

.Include()

 

通過這兩篇文章,我們有針對性的瞭解了L2S和EF在LINQ查詢支援上的特性。通過比較他們的異同,讓我們更好的他們的內在聯絡和區分他們在使用上的差別。在接下來的幾篇部落格中,我準備對LINQ查詢運算子(LINQ Operators)進行更加詳細的分類介紹。只有在瞭解了大多數查詢運算子後,才能更好的寫出功能強大而又簡潔優雅的 LINQ查詢。

 

系列部落格導航:

LINQ之路系列部落格導航

LINQ之路 1:LINQ介紹

LINQ之路 2:C# 3.0的語言功能(上)

LINQ之路 3:C# 3.0的語言功能(下)

LINQ之路 4:LINQ方法文法

LINQ之路 5:LINQ查詢運算式

LINQ之路 6:順延強制(Deferred Execution)

LINQ之路 7:子查詢、建立策略和資料轉換

LINQ之路 8:解釋查詢(Interpreted Queries)

LINQ之路 9:LINQ to SQL 和 Entity Framework(上)

LINQ之路10:LINQ to SQL 和 Entity Framework(下)

LINQ之路11:LINQ Operators之過濾(Filtering)

LINQ之路12:LINQ Operators之資料轉換(Projecting)

LINQ之路13:LINQ Operators之串連(Joining)

LINQ之路14:LINQ Operators之排序和分組(Ordering and Grouping)

LINQ之路15:LINQ Operators之元素運算子、集合方法、量詞方法

LINQ之路16:LINQ Operators之集合運算子、Zip操作符、轉換方法、產生器方法

LINQ之路17:LINQ to XML之X-DOM介紹

LINQ之路18:LINQ to XML之導航和查詢

LINQ之路19:LINQ to XML之X-DOM更新、和Value屬性互動

LINQ之路20:LINQ to XML之Documents、Declarations和Namespaces

LINQ之路21:LINQ to XML之產生X-DOM(Projecting)

LINQ之路系列部落格後記

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.