使用工具追蹤Entity Framework生成的SQL

學習entity framework期間收集的文章,轉自http://www.cnblogs.com/hiteddy/archive/2011/10/01/Difference_among_IQueryable_IEnumeralb_IList_in_Entity_Framework.htmlhtml

使用Entity Framework等ORM框架的時候,SQL對於使用者來講是透明的,每每不少人也不關心ORM所生成的SQL,然而系統出現性能問題的時候就必須關注生成的SQL以發現問題所在。數據庫

使用過Toplink的朋友知道很只要設置日誌打印級別=FINE就能夠配置使之生成的SQL在服務器中打印出來,Entiry Framework沒有那麼幸運,在之前要檢測生成SQL的惟一方法是SQL Server Profiler,但使用起來並不方便,結果也不能自動保存到文件中。express

Tracing and Caching Provider Wrappers for Entity Framework是Entity Framework Team新推出的開源SQL追蹤和二級緩存的解決方案。原理是在負責執行具體SQL語句的data provider(SqlClient或者其餘Client)之上插入了一層WrappingProvider,用於監控DbCommand.ExecuteReader(), ExecuteScalar() and ExecuteNonQuery(),將Sql命令輸出到指定介質或者將查詢結果緩存起來以重用。緩存

使用方法很簡單,下載源代碼編譯後將dll添加到項目中,新加一個類WrappedNorthWindEntities繼承原有的Entities便可,詳見源代碼中的示例。服務器

測試IQueryable, IEnumerable, IList的區別

下面咱們使用EF Wrapper來監測Entify Framework中IQueryable, IEnumerable和IList所生成的SQL。app

TestIQueryable框架

private static void TestIQueryable()
{
using (var ctx = new WrappedNorthWindEntities())
{
IQueryable<Product> expression = ctx.Products.Take(5);
IQueryable<Product> products = expression.Take(2); // A 不執行SQL
Console.WriteLine(products.Count());          // B SELECT COUNT(1) FROM ( SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products] ))
Console.WriteLine(products.Count());          // C SELECT COUNT(1) FROM ( SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products] ))
foreach (Product p in products)             // D SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products]
{
Console.WriteLine(p.ProductName);
}
foreach (Product p in products)             // E SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products] )
{
Console.WriteLine(p.ProductName);
}
}
}ide

 

TestIEnumerable性能

private static void TestIEnumerable()
{
using (var ctx = new WrappedNorthWindEntities())
{
IEnumerable<Product> expression = ctx.Products.Take(5).AsEnumerable();
IEnumerable<Product> products = expression.Take(2); // A 不執行SQL
Console.WriteLine(products.Count());          // B SELECT TOP (5) * FROM [dbo].[Products]
Console.WriteLine(products.Count());          // C SELECT TOP (5) * FROM [dbo].[Products]
foreach (Product p in products)             // D SELECT TOP (5) * FROM [dbo].[Products]
{
Console.WriteLine(p.ProductName);
}
foreach (Product p in products)             // E SELECT TOP (5) * FROM [dbo].[Products]
{
Console.WriteLine(p.ProductName);
}
}
}學習


複製代碼
private static void TestIEnumerable()
{
using (var ctx = new WrappedNorthWindEntities())
{
IEnumerable<Product> expression = ctx.Products.Take(5).AsEnumerable();
IEnumerable<Product> products = expression.Take(2); // A 不執行SQL
Console.WriteLine(products.Count());          // B SELECT TOP (5) * FROM [dbo].[Products]
Console.WriteLine(products.Count());          // C SELECT TOP (5) * FROM [dbo].[Products]
foreach (Product p in products)             // D SELECT TOP (5) * FROM [dbo].[Products]
{
Console.WriteLine(p.ProductName);
}
foreach (Product p in products)             // E SELECT TOP (5) * FROM [dbo].[Products]
{
Console.WriteLine(p.ProductName);
}
}
}
複製代碼

 

TestIList

private static void TestIList()
{
using (var ctx = new WrappedNorthWindEntities())
{
var expression = ctx.Products.Take(5);
IList<Product> products = expression.Take(2).ToList(); // A SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products]

Console.WriteLine(products.Count());           // B 不執行SQL
Console.WriteLine(products.Count());           // C 不執行SQL
foreach (Product p in products)              // D 不執行SQL
{
Console.WriteLine(p.ProductName);
}
foreach (Product p in products)             // E 不執行SQL
{
Console.WriteLine(p.ProductName);
}
}
}


複製代碼
private static void TestIList()
{
using (var ctx = new WrappedNorthWindEntities())
{
var expression = ctx.Products.Take(5);
IList<Product> products = expression.Take(2).ToList(); // A SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products]

Console.WriteLine(products.Count());           // B 不執行SQL
Console.WriteLine(products.Count());           // C 不執行SQL
foreach (Product p in products)              // D 不執行SQL
{
Console.WriteLine(p.ProductName);
}
foreach (Product p in products)             // E 不執行SQL
{
Console.WriteLine(p.ProductName);
}
}
}
複製代碼

測試結果

  1. IQueryable和IEnumerable都是延時執行(Deferred Execution)的,而IList是即時執行(Eager Execution)
  2. IQueryable和IEnumerable在每次執行時都必須鏈接數據庫讀取,而IList讀取一次後,之後各次都不需鏈接數據庫。前二者很容易形成重複讀取,性能低下,而且可能引起數據不一致性
  3. IQueryable和IEnumerable的區別:IEnumberalb使用的是LINQ to Object方式,它會將AsEnumerable()時對應的全部記錄都先加載到內存,而後在此基礎上再執行後來的Query。因此上述TestIEnumerable例子中執行的SQL是"select top(5) ...",而後在內存中選擇前兩條記錄返回。

如下是一個IQueryable引起數據不一致性的例子:記錄總數和記錄詳情二者本應一致,但因爲IQueryable先後兩次讀取數據庫,結果是現實有10條記錄,卻輸出11條詳情。

IQueryable Data Inconsistancy

IQueryable<Product> products = ctx.Products.All();
//開始的時候數據庫product表中有10條記錄, count = 10
int count = products.Count();
Console.WriteLine("Count of products:"+count);

//此時另外一進程添加一個產品進數據庫
//會從新讀取數據庫並輸出11個產品名稱
foreach (Product p in products)
{
Console.WriteLine(p.ProductName);
}


複製代碼
    IQueryable<Product> products = ctx.Products.All();
//開始的時候數據庫product表中有10條記錄, count = 10
int count = products.Count();
Console.WriteLine("Count of products:"+count);
        
//此時另外一進程添加一個產品進數據庫
    //會從新讀取數據庫並輸出11個產品名稱
foreach (Product p in products)
{
Console.WriteLine(p.ProductName);
}
複製代碼

結論

基於性能和數據一致性這兩點,咱們使用IQueryable時必須謹慎,而在大多數狀況下咱們應使用IList。

  • 當你打算立刻使用查詢後的結果(好比循環做邏輯處理或者填充到一個table/grid中),而且你不介意該查詢會即時執行,使用ToList()
  • 當你但願查詢後的結果能夠供調用者(Consummer)做後續查詢(好比這是一個"GetAll"的方法),或者你但願該查詢延時執行,使用AsQueryable()
相關文章
相關標籤/搜索