[C#.NET][Entity Framework] 在 Select 區段需要注意的幾個事項

這裡列出我對 Select 區段要注意的筆記,希望能幫到你

開發環境

範例會用下圖地的資料表來演練,關係圖如下:

 

在導覽屬性調用立即執行的方法不會送 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

Image result for microsoft+mvp+logo