對於linq to entity 生成的復雜sql語句相信已經困擾大家很久了,本人也是。從接觸實體框架到現在,一直都是邊學邊用,用啥學啥,沒有系統的學習過。同時所接觸項目也對性能方面沒什么要求,所以本人雖然對於EF生成的又臭又長的SQL相當不爽,但也沒花時間去優化過。
今天難得有空,便嘗試着做了小小的優化,略有收獲,分享出來,希望能對大家也有幫助。環境是EF4.0+SQL2008.
先說說我的思路
首先我們知道,從我們寫的LINQ或LAMBDA語句到生成的SQL 是有一個翻譯的過程的,這個翻譯是程序進行的,它必定按照已定的某種規則去翻譯。同樣的一個結果的查詢語句,我們選擇不同的寫法就會出現不同的SQL語句。所以同一個查詢語句嘗試不同的寫法,對比生成的SQL語句,也許能讓我們摸出EF的翻譯規則。
優化過程
我在項目中找了一段並不十分復雜也不算十分簡單的查詢語句,它涉及了5張表,4次join 。先來看看最初版本的代碼和生成的SQL吧
1 var baseResult = from p in Database.CreditAuditDS 2 join p2 in Database.RoleDS on p.ToCreditFlow.RoleId equals p2.ROLESID 3 join p3 in Database.VEnterpriseDS on p.ToCreditDeclare.EnterpriseCode equals p3.Code 4 where p2.ROLESID == roleId && p.ToCreditFlow.Level == level && p.ToCreditDeclare.AuditState == p.ToCreditFlow.Id 5 select new ViewCreditAudit 6 { 7 Id = p.Id, 8 DeclareId = p.ToCreditDeclare.Id, 9 DeclareCode = p.ToCreditDeclare.DeclareCode, 10 Content = p.ToCreditDeclare.Content, 11 CreatedAt = p.CreatedAt, 12 EnterpriseName = p3.Name, 13 Result = p.Result, 14 Title = p.ToCreditDeclare.Title 15 };
1 exec sp_executesql N'SELECT 2 [Extent1].[Id] AS [Id], 3 [Extent1].[DeclareId] AS [DeclareId], 4 [Extent10].[DeclareCode] AS [DeclareCode], 5 [Extent10].[Content] AS [Content], 6 [Extent1].[CreatedAt] AS [CreatedAt], 7 [Extent5].[Name] AS [Name], 8 [Extent1].[Result] AS [Result], 9 [Extent10].[Title] AS [Title] 10 FROM [dbo].[T_Credit_Audit] AS [Extent1] 11 INNER JOIN [dbo].[M_ROLES] AS [Extent2] ON EXISTS (SELECT 12 1 AS [C1] 13 FROM ( SELECT 1 AS X ) AS [SingleRowTable1] 14 LEFT OUTER JOIN (SELECT 15 [Extent3].[Id] AS [Id], 16 [Extent3].[RoleId] AS [RoleId] 17 FROM [dbo].[T_Credit_Flow] AS [Extent3] 18 WHERE [Extent1].[FlowId] = [Extent3].[Id] ) AS [Project1] ON 1 = 1 19 LEFT OUTER JOIN (SELECT 20 [Extent4].[Id] AS [Id] 21 FROM [dbo].[T_Credit_Flow] AS [Extent4] 22 WHERE [Extent1].[FlowId] = [Extent4].[Id] ) AS [Project2] ON 1 = 1 23 WHERE [Project1].[RoleId] = [Extent2].[ROLESID] 24 ) 25 INNER JOIN (SELECT 26 [V_Enterprise].[Id] AS [Id], 27 [V_Enterprise].[Code] AS [Code], 28 [V_Enterprise].[AreaCode] AS [AreaCode], 29 [V_Enterprise].[Name] AS [Name], 30 [V_Enterprise].[Type] AS [Type] 31 FROM [dbo].[V_Enterprise] AS [V_Enterprise]) AS [Extent5] ON EXISTS (SELECT 32 1 AS [C1] 33 FROM ( SELECT 1 AS X ) AS [SingleRowTable2] 34 LEFT OUTER JOIN (SELECT 35 [Extent6].[Id] AS [Id], 36 [Extent6].[EnterpriseCode] AS [EnterpriseCode] 37 FROM [dbo].[T_Credit_Declare] AS [Extent6] 38 WHERE [Extent1].[DeclareId] = [Extent6].[Id] ) AS [Project4] ON 1 = 1 39 LEFT OUTER JOIN (SELECT 40 [Extent7].[Id] AS [Id], 41 [Extent7].[EnterpriseCode] AS [EnterpriseCode] 42 FROM [dbo].[T_Credit_Declare] AS [Extent7] 43 WHERE [Extent1].[DeclareId] = [Extent7].[Id] ) AS [Project5] ON 1 = 1 44 WHERE ([Project4].[EnterpriseCode] = [Extent5].[Code]) OR (([Project5].[EnterpriseCode] IS NULL) AND ([Extent5].[Code] IS NULL)) 45 ) 46 LEFT OUTER JOIN [dbo].[T_Credit_Flow] AS [Extent8] ON [Extent1].[FlowId] = [Extent8].[Id] 47 INNER JOIN [dbo].[T_Credit_Declare] AS [Extent9] ON ([Extent1].[FlowId] = [Extent9].[AuditState]) AND ([Extent1].[DeclareId] = [Extent9].[Id]) 48 LEFT OUTER JOIN [dbo].[T_Credit_Declare] AS [Extent10] ON [Extent1].[DeclareId] = [Extent10].[Id] 49 WHERE ([Extent2].[ROLESID] = @p__linq__0) AND ([Extent8].[Level] = @p__linq__1)',N'@p__linq__0 int,@p__linq__1 int',@p__linq__0=58,@p__linq__1=2
很長吧,這樣的SQL語句我已經在SQL SERVER PROFILER中見過無數次了。剛才說過,這個查詢只是對5個表已經連接查詢,理想狀態,也就是我們自己手寫代碼,4個inner join 就可以了。但是我們看看EF為我們生成的代碼吧,復雜的對於我這個SQL菜鳥都看不大懂了,反正不管是inner join 還是left outer join ,JOIN這個關鍵詞出現了一共9次。
接下來看看這個查詢語句的第二個版本和生成的SQL吧
1 var baseResult = from p in Database.CreditDeclareDS 2 from p2 in p.ToCreditAudit 3 join p3 in Database.RoleDS on p2.ToCreditFlow.RoleId equals p3.ROLESID 4 join p4 in Database.VEnterpriseDS on p.EnterpriseCode equals p4.Code 5 where p3.ROLESID == roleId && p2.ToCreditFlow.Level == level && p.AuditState == p2.ToCreditFlow.Id 6 select new ViewCreditAudit 7 { 8 Id = p2.Id, 9 DeclareId = p.Id, 10 DeclareCode = p.DeclareCode, 11 Content = p.Content, 12 CreatedAt = p2.CreatedAt, 13 EnterpriseName = p4.Name, 14 Result = p2.Result, 15 Title = p.Title 16 };
1 exec sp_executesql N'SELECT 2 [Extent1].[Id] AS [Id], 3 [Extent2].[Id] AS [Id1], 4 [Extent1].[DeclareCode] AS [DeclareCode], 5 [Extent1].[Content] AS [Content], 6 [Extent2].[CreatedAt] AS [CreatedAt], 7 [Extent6].[Name] AS [Name], 8 [Extent2].[Result] AS [Result], 9 [Extent1].[Title] AS [Title] 10 FROM [dbo].[T_Credit_Declare] AS [Extent1] 11 INNER JOIN [dbo].[T_Credit_Audit] AS [Extent2] ON ([Extent1].[Id] = [Extent2].[DeclareId]) AND ([Extent1].[AuditState] = [Extent2].[FlowId]) 12 INNER JOIN [dbo].[M_ROLES] AS [Extent3] ON EXISTS (SELECT 13 1 AS [C1] 14 FROM ( SELECT 1 AS X ) AS [SingleRowTable1] 15 LEFT OUTER JOIN (SELECT 16 [Extent4].[Id] AS [Id], 17 [Extent4].[RoleId] AS [RoleId] 18 FROM [dbo].[T_Credit_Flow] AS [Extent4] 19 WHERE [Extent2].[FlowId] = [Extent4].[Id] ) AS [Project1] ON 1 = 1 20 LEFT OUTER JOIN (SELECT 21 [Extent5].[Id] AS [Id] 22 FROM [dbo].[T_Credit_Flow] AS [Extent5] 23 WHERE [Extent2].[FlowId] = [Extent5].[Id] ) AS [Project2] ON 1 = 1 24 WHERE [Project1].[RoleId] = [Extent3].[ROLESID] 25 ) 26 INNER JOIN (SELECT 27 [V_Enterprise].[Id] AS [Id], 28 [V_Enterprise].[Code] AS [Code], 29 [V_Enterprise].[AreaCode] AS [AreaCode], 30 [V_Enterprise].[Name] AS [Name], 31 [V_Enterprise].[Type] AS [Type] 32 FROM [dbo].[V_Enterprise] AS [V_Enterprise]) AS [Extent6] ON [Extent1].[EnterpriseCode] = [Extent6].[Code] 33 INNER JOIN [dbo].[T_Credit_Flow] AS [Extent7] ON [Extent2].[FlowId] = [Extent7].[Id] 34 WHERE ([Extent3].[ROLESID] = @p__linq__0) AND ([Extent7].[Level] = @p__linq__1)',N'@p__linq__0 int,@p__linq__1 int',@p__linq__0=58,@p__linq__1=2
當我看到這次生成的SQL時,我心中不由一喜:貌似看到曙光了。。。
首先生成的SQL語句縮短了很多字符,JOIN這個關鍵字這次只出現了6次。經過反復的對比,我發現了一些門道,於是按着這個門道,我再次修改。以下是最后的代碼和生成的SQL。
1 var baseResult = from p in Database.CreditDeclareDS 2 from p2 in p.ToCreditAudit 3 join p5 in Database .CreditFlowDS on p2.FlowId equals p5 .Id 4 join p3 in Database.RoleDS on p5.RoleId equals p3.ROLESID 5 join p4 in Database.VEnterpriseDS on p.EnterpriseCode equals p4.Code 6 where p3.ROLESID == roleId && p5.Level == level && p.AuditState == p5.Id 7 select new ViewCreditAudit 8 { 9 Id = p2.Id, 10 DeclareId = p.Id, 11 DeclareCode = p.DeclareCode, 12 Content = p.Content, 13 CreatedAt = p2.CreatedAt, 14 EnterpriseName = p4.Name, 15 Result = p2.Result, 16 Title = p.Title 17 };
1 exec sp_executesql N'SELECT 2 [Extent1].[Id] AS [Id], 3 [Extent2].[Id] AS [Id1], 4 [Extent1].[DeclareCode] AS [DeclareCode], 5 [Extent1].[Content] AS [Content], 6 [Extent2].[CreatedAt] AS [CreatedAt], 7 [Extent5].[Name] AS [Name], 8 [Extent2].[Result] AS [Result], 9 [Extent1].[Title] AS [Title] 10 FROM [dbo].[T_Credit_Declare] AS [Extent1] 11 INNER JOIN [dbo].[T_Credit_Audit] AS [Extent2] ON [Extent1].[Id] = [Extent2].[DeclareId] 12 INNER JOIN [dbo].[T_Credit_Flow] AS [Extent3] ON ([Extent2].[FlowId] = [Extent3].[Id]) AND ([Extent1].[AuditState] = [Extent3].[Id]) 13 INNER JOIN [dbo].[M_ROLES] AS [Extent4] ON [Extent3].[RoleId] = [Extent4].[ROLESID] 14 INNER JOIN (SELECT 15 [V_Enterprise].[Id] AS [Id], 16 [V_Enterprise].[Code] AS [Code], 17 [V_Enterprise].[AreaCode] AS [AreaCode], 18 [V_Enterprise].[Name] AS [Name], 19 [V_Enterprise].[Type] AS [Type] 20 FROM [dbo].[V_Enterprise] AS [V_Enterprise]) AS [Extent5] ON [Extent1].[EnterpriseCode] = [Extent5].[Code] 21 WHERE ([Extent4].[ROLESID] = @p__linq__0) AND ([Extent3].[Level] = @p__linq__1)',N'@p__linq__0 int,@p__linq__1 int',@p__linq__0=58,@p__linq__1=2
這才是我想要生成的SQL,4次inner join ,除了視圖V_Enterprise這里有點瑕疵,基本符合要求了。
說說我的分析
與其說是分析,不如說是觀察結果。
在我的第一個代碼版本中,我只用了兩次join,但是卻在from 和where 中用了兩個關聯屬性。
在我的第二個代碼版本中,我用了兩次join 和額外的from ,相當於三次join 用了一個關聯屬性。
在我的第三個代碼版本中,我相當於用了四次join,但沒有再使用關聯屬性。
那么,我們可以簡單的得出這樣的一個結果:盡量使用join語句,盡量不用關聯屬性。。。
以上就是我的一個簡單粗糙的優化,也許得出的結論並不正確,但相信啟發還是有的。希望大牛們能拿出更全面的優化方案。