這裡列出我對 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