這裡列出我對 Select 區段要注意的筆記,希望能幫到你
開發環境
- LinqPad
- Entity Framework 6.1.3
- 資料庫 AdventureWorks2012
https://github.com/Microsoft/sql-server-samples/releases/tag/adventureworks - 資料庫 Northwind
範例會用下圖地的資料表來演練,關係圖如下:
在導覽屬性調用立即執行的方法不會送 T-SQL 命令
FirstOrDefault 方法是一個立即執行的方法,一般情況下會馬上送出 T-SQL
void Main()
{
using (var dbContext = new AdventureWorks2012.EF.EntityModel.AdventureWorksDbContext())
{
var filter = dbContext.Addresses.Where(p => p.AddressID == 1);
var result = filter.AsNoTracking().FirstOrDefault();
result.Dump();
}
}
執行的結果如下圖:
放到 Select 的時候就不會了,剛開始學 EF 的時候,我以為下面的寫法會送兩段查詢出去
void Main()
{
using (var dbContext = new AdventureWorks2012.EF.EntityModel.AdventureWorksDbContext())
{
var filter = dbContext.Addresses.Where(p => p.AddressID == 1);
var selector = filter.Select(p => new
{
AddressID = p.AddressID,
p.AddressLine1,
p.AddressLine2,
p.BusinessEntityAddresses.FirstOrDefault().Address.City
});
var result = selector.AsNoTracking().Dump();
}
}
我中斷了 Dump 那一行,沒有送出 T-SQL 命令,觀察到 FirstOrDefault 沒有馬上送出 T-SQL,總共送出一次查詢
使用導覽屬性會產生 JOIN 指令
有建立關聯的資料單,對應到 Entity Model 後,可直接查詢關連資料表的屬性(非 ID),這裡用的是 AdventureWork
void Main()
{
using (var dbContext = new AdventureWorks2012.EF.EntityModel.AdventureWorksDbContext())
{
var filter = dbContext.Addresses.Where(p => p.AddressID == 1);
var selector = filter.Select(p => new
{
p.AddressID,
p.AddressLine1,
p.AddressLine2,
p.StateProvince.Name
});
var result = selector.AsNoTracking().Dump();
}
}
EF 幫我們把導覽屬性翻成了 INNER JOIN
差不多的寫法,卻變成了 OUTER JOIN
導覽屬性很方便,但結果可能不如我們預期,這次我用 Northwind 資料庫
void Main()
{
using (var dbContext = new NorthwindDbContext())
{
var filter = dbContext.Products.Where(p => p.ProductID == 1);
var joinable = filter.Select(p => new
{
p.Category.CategoryName,
p.ProductID,
p.ProductName,
});
joinable.Dump();
}
}
看起來跟上一個情境的寫法差不多,可是這裡卻變成了 OUTER JOIN
在 JOIN 的 Select 裡,用了導覽屬性,翻成 OUTER JOIN
void Main()
{
using (var dbContext = new NorthwindDbContext())
{
var joinable = from product in dbContext.Products
join category in dbContext.Categories
on product.CategoryID equals category.CategoryID
select new
{
category,
product,
};
var selector = joinable.Select(p => new
{
p.product.ProductID,
p.product.ProductName,
p.category.CategoryName,
p.product.Supplier.City
});
var result = selector.AsNoTracking().Dump();
}
}
差不多的寫法為什麼 EF 翻出來的結果會不一樣?
這和欄位的結構有關,當 FK 欄位是必填又參考別張資料表時,資料兩邊都會有,取交集 INNER JOIN 就可以拿到資料,因此 EF 就翻成了 INNER JOIN
反之,FK 欄位設計成非必填,取交集可能會沒有資料,因此 EF 這裡就翻 OUTER JOIN
Where條件若有非必填的欄位,也會變成Left OuterJoin
結論
總結一下,
- 在 Select 區段用了立即執行的方法,不會馬上執行
- 在 Select 區段用了導覽屬性,EF 會翻成 JOIN
- FK 是必填,EF 會翻成 INNER JOIN
- FK 是非必填,EF 會翻成 OUTER JOIN
- 預設的 JOIN 無法滿足需求時,應自己寫 JOIN
理解 EF 的行為之後就比較清楚翻出來的 T-SQL 命令會長怎樣,EF 的翻 T-SQL 的結果算是很聰明,已經沒看到會翻出 N+1 的查詢,如果有一定是 EF 那邊沒寫好
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET