Different SQL between C# and vb.net using LINQ to SQL causes performance issues
Hi All,
I am currently working on a project and we are using VB.NET, I am using LINQ to SQL for my data access. I have just implemented my search query and thought I would check the generated SQL's execution plan and found that the subtree cost was about 7.3. I know that I get different SQL between C# and VB.NET when using LINQ to SQL, as VB.NET seems to add a WHERE COALESCE instead of the straight WHERE that C# will do. In the past I did a quick check on a small query to see if it would make a difference and found that there was not a noticable one.
Now having a larger more complex query I thought I would check the execution plan for the same query but written in C#, I now get a much smaller SQL query and the subtree cost drops from 7.3 to 0.1. A big difference IMO. Has anyone come across this before and what are your throughts on this. It baffles me that you would get such a difference in queries between the two languages.
Below I will show the LINQ to SQL query and the generated SQL for both VB.NET and C#:
VB.NET
------------
Dim query = (From menuprod In db.MenuProducts _
Let pis = menuprod.Product.ProductItems.SelectMany(Function(item) item.ProductItemSites).Where(Function(item) item.SiteID = inSiteID) _
Where menuprod.MenuID = inMenuID _
And menuprod.Product.ProductItems.First(Function(item) item.IsDefaultItem).ProductItemImages.Any(Function(image) image.ViewType = 0) _
And pis.Any() _
And Not pis.All(Function(item) item.SizeStocks.All(Function(size) size.OnlineStock = 0 And size.NationalStock = 0)) _
Order By menuprod.Product.ProductName _
Select New ProductDisplayItem With { _
.Product = menuprod.Product, _
.DefaultProductItem = menuprod.Product.ProductItems.FirstOrDefault(Function(item) item.IsDefaultItem), _
.MinWasPrice = pis.Min(Function(item) item.WasPrice), _
.MaxWasPrice = pis.Max(Function(item) item.WasPrice), _
.MinNowPrice = pis.Min(Function(item) item.NowPrice), _
.MaxNowPrice = pis.Max(Function(item) item.NowPrice) _
}).Take(6).ToList()
---
exec sp_executesql N'SELECT TOP (6) [t15].[test], [t15].[ID], [t15].[CreatedDate], [t15].[CreatedUserID], [t15].[UpdatedDate], [t15].[UpdatedUserID], [t15].[DeletedDate], [t15].[DeletedUserID], [t15].[StockCode], [t15].[ProductName], [t15].[Summary], [t15].[BrandID], [t15].[Maskname], [t15].[value] AS [MinWasPrice], [t15].[value2] AS [MaxWasPrice], [t15].[value3] AS [MinNowPrice], [t15].[value4] AS [MaxNowPrice]
FROM (
SELECT [t2].[test], [t2].[ID], [t2].[CreatedDate], [t2].[CreatedUserID], [t2].[UpdatedDate], [t2].[UpdatedUserID], [t2].[DeletedDate], [t2].[DeletedUserID], [t2].[StockCode], [t2].[ProductName], [t2].[Summary], [t2].[BrandID], [t2].[Maskname], (
SELECT MIN([t5].[WasPrice])
FROM [dbo].[tbl_Product] AS [t3], [dbo].[tbl_ProductItem] AS [t4], [dbo].[tbl_ProductItemSite] AS [t5]
WHERE ([t5].[SiteID] = @p0) AND ([t3].[ID] = [t0].[ProductID]) AND ([t4].[ProductID] = [t3].[ID]) AND ([t5].[ProductItemID] = [t4].[ID])
) AS [value], (
SELECT MAX([t8].[WasPrice])
FROM [dbo].[tbl_Product] AS [t6], [dbo].[tbl_ProductItem] AS [t7], [dbo].[tbl_ProductItemSite] AS [t8]
WHERE ([t8].[SiteID] = @p0) AND ([t6].[ID] = [t0].[ProductID]) AND ([t7].[ProductID] = [t6].[ID]) AND ([t8].[ProductItemID] = [t7].[ID])
) AS [value2], (
SELECT MIN([t11].[NowPrice])
FROM [dbo].[tbl_Product] AS [t9], [dbo].[tbl_ProductItem] AS [t10], [dbo].[tbl_ProductItemSite] AS [t11]
WHERE ([t11].[SiteID] = @p0) AND ([t9].[ID] = [t0].[ProductID]) AND ([t10].[ProductID] = [t9].[ID]) AND ([t11].[ProductItemID] = [t10].[ID])
) AS [value3], (
SELECT MAX([t14].[NowPrice])
FROM [dbo].[tbl_Product] AS [t12], [dbo].[tbl_ProductItem] AS [t13], [dbo].[tbl_ProductItemSite] AS [t14]
WHERE ([t14].[SiteID] = @p0) AND ([t12].[ID] = [t0].[ProductID]) AND ([t13].[ProductID] = [t12].[ID]) AND ([t14].[ProductItemID] = [t13].[ID])
) AS [value4], [t0].[MenuID], [t0].[ProductID]
FROM [dbo].[tbl_MenuProduct] AS [t0]
LEFT OUTER JOIN (
SELECT 1 AS [test], [t1].[ID], [t1].[CreatedDate], [t1].[CreatedUserID], [t1].[UpdatedDate], [t1].[UpdatedUserID], [t1].[DeletedDate], [t1].[DeletedUserID], [t1].[StockCode], [t1].[ProductName], [t1].[Summary], [t1].[BrandID], [t1].[Maskname]
FROM [dbo].[tbl_Product] AS [t1]
) AS [t2] ON [t2].[ID] = [t0].[ProductID]
) AS [t15]
WHERE (COALESCE(
(CASE
WHEN ([t15].[MenuID] = @p1) AND (EXISTS(
SELECT NULL AS [EMPTY]
FROM (
SELECT TOP (1) [t28].[ID]
FROM [dbo].[tbl_Product] AS [t27], [dbo].[tbl_ProductItem] AS [t28]
WHERE ([t28].[IsDefaultItem] = 1) AND ([t27].[ID] = [t15].[ProductID]) AND ([t28].[ProductID] = [t27].[ID])
) AS [t29]
CROSS JOIN [dbo].[tbl_ProductItemImages] AS [t30]
WHERE ([t30].[ViewType] = @p2) AND ([t30].[ProductItemID] = [t29].[ID])
)) AND (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[tbl_Product] AS [t31], [dbo].[tbl_ProductItem] AS [t32], [dbo].[tbl_ProductItemSite] AS [t33]
WHERE ([t33].[SiteID] = @p0) AND ([t31].[ID] = [t15].[ProductID]) AND ([t32].[ProductID] = [t31].[ID]) AND ([t33].[ProductItemID] = [t32].[ID])
)) AND (NOT NOT (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[tbl_Product] AS [t34], [dbo].[tbl_ProductItem] AS [t35], [dbo].[tbl_ProductItemSite] AS [t36]
WHERE ((
(CASE
WHEN NOT (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[tbl_SizeStock] AS [t37]
WHERE ((
(CASE
WHEN ([t37].[OnlineStock] = @p3) AND ([t37].[NationalStock] = @p4) THEN 1
ELSE 0
END)) = 0) AND ([t37].[ProductItemSiteID] = [t36].[ID])
)) THEN 1
ELSE 0
END)) = 0) AND ([t36].[SiteID] = @p0) AND ([t34].[ID] = [t15].[ProductID]) AND ([t35].[ProductID] = [t34].[ID]) AND ([t36].[ProductItemID] = [t35].[ID])
))) THEN 1
WHEN NOT (([t15].[MenuID] = @p1) AND (EXISTS(
SELECT NULL AS [EMPTY]
FROM (
SELECT TOP (1) [t28].[ID]
FROM [dbo].[tbl_Product] AS [t27], [dbo].[tbl_ProductItem] AS [t28]
WHERE ([t28].[IsDefaultItem] = 1) AND ([t27].[ID] = [t15].[ProductID]) AND ([t28].[ProductID] = [t27].[ID])
) AS [t29]
CROSS JOIN [dbo].[tbl_ProductItemImages] AS [t30]
WHERE ([t30].[ViewType] = @p2) AND ([t30].[ProductItemID] = [t29].[ID])
)) AND (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[tbl_Product] AS [t31], [dbo].[tbl_ProductItem] AS [t32], [dbo].[tbl_ProductItemSite] AS [t33]
WHERE ([t33].[SiteID] = @p0) AND ([t31].[ID] = [t15].[ProductID]) AND ([t32].[ProductID] = [t31].[ID]) AND ([t33].[ProductItemID] = [t32].[ID])
)) AND (NOT NOT (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[tbl_Product] AS [t34], [dbo].[tbl_ProductItem] AS [t35], [dbo].[tbl_ProductItemSite] AS [t36]
WHERE ((
(CASE
WHEN NOT (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[tbl_SizeStock] AS [t37]
WHERE ((
(CASE
WHEN ([t37].[OnlineStock] = @p3) AND ([t37].[NationalStock] = @p4) THEN 1
ELSE 0
END)) = 0) AND ([t37].[ProductItemSiteID] = [t36].[ID])
)) THEN 1
ELSE 0
END)) = 0) AND ([t36].[SiteID] = @p0) AND ([t34].[ID] = [t15].[ProductID]) AND ([t35].[ProductID] = [t34].[ID]) AND ([t36].[ProductItemID] = [t35].[ID])
)))) THEN 0
ELSE NULL
END),@p5)) = 1
ORDER BY [t15].[ProductName]',N'@p0 bigint,@p1 int,@p2 int,@p3 int,@p4 int,@p5 int',@p0=3,@p1=219,@p2=0,@p3=0,@p4=0,@p5=0
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
C#
-----
var query = (from menuprod in db.MenuProducts
let pis = menuprod.Product.ProductItems.SelectMany(item => item.ProductItemSites).Where(item => item.SiteID == inSideID)
where menuprod.MenuID == inMenuID
&& menuprod.Product.ProductItems.First(item => item.IsDefaultItem).ProductItemImages.Any(image => image.ViewType == 0)
&& pis.Any()
&& !pis.All(item => item.SizeStocks.All(size => size.OnlineStock == 0 && size.NationalStock == 0))
orderby menuprod.Product.ProductName
select new {
Product = menuprod.Product,
DefaultProductItem = menuprod.Product.ProductItems.FirstOrDefault(item => item.IsDefaultItem),
MinWasPrice = pis.Min(item => item.WasPrice),
MaxWasPrice = pis.Max(item => item.WasPrice),
MinNowPrice = pis.Min(item => item.NowPrice),
MaxNowPrice = pis.Max(item => item.NowPrice)
}).Take(6).ToList();
----
exec sp_executesql N'SELECT TOP (6) [t15].[test], [t15].[ID], [t15].[CreatedDate], [t15].[CreatedUserID], [t15].[UpdatedDate], [t15].[UpdatedUserID], [t15].[DeletedDate], [t15].[DeletedUserID], [t15].[StockCode], [t15].[ProductName], [t15].[Summary], [t15].[BrandID], [t15].[Maskname], [t15].[value] AS [MinWasPrice], [t15].[value2] AS [MaxWasPrice], [t15].[value3] AS [MinNowPrice], [t15].[value4] AS [MaxNowPrice]
FROM (
SELECT [t2].[test], [t2].[ID], [t2].[CreatedDate], [t2].[CreatedUserID], [t2].[UpdatedDate], [t2].[UpdatedUserID], [t2].[DeletedDate], [t2].[DeletedUserID], [t2].[StockCode], [t2].[ProductName], [t2].[Summary], [t2].[BrandID], [t2].[Maskname], (
SELECT MIN([t5].[WasPrice])
FROM [dbo].[tbl_Product] AS [t3], [dbo].[tbl_ProductItem] AS [t4], [dbo].[tbl_ProductItemSite] AS [t5]
WHERE ([t5].[SiteID] = @p0) AND ([t3].[ID] = [t0].[ProductID]) AND ([t4].[ProductID] = [t3].[ID]) AND ([t5].[ProductItemID] = [t4].[ID])
) AS [value], (
SELECT MAX([t8].[WasPrice])
FROM [dbo].[tbl_Product] AS [t6], [dbo].[tbl_ProductItem] AS [t7], [dbo].[tbl_ProductItemSite] AS [t8]
WHERE ([t8].[SiteID] = @p0) AND ([t6].[ID] = [t0].[ProductID]) AND ([t7].[ProductID] = [t6].[ID]) AND ([t8].[ProductItemID] = [t7].[ID])
) AS [value2], (
SELECT MIN([t11].[NowPrice])
FROM [dbo].[tbl_Product] AS [t9], [dbo].[tbl_ProductItem] AS [t10], [dbo].[tbl_ProductItemSite] AS [t11]
WHERE ([t11].[SiteID] = @p0) AND ([t9].[ID] = [t0].[ProductID]) AND ([t10].[ProductID] = [t9].[ID]) AND ([t11].[ProductItemID] = [t10].[ID])
) AS [value3], (
SELECT MAX([t14].[NowPrice])
FROM [dbo].[tbl_Product] AS [t12], [dbo].[tbl_ProductItem] AS [t13], [dbo].[tbl_ProductItemSite] AS [t14]
WHERE ([t14].[SiteID] = @p0) AND ([t12].[ID] = [t0].[ProductID]) AND ([t13].[ProductID] = [t12].[ID]) AND ([t14].[ProductItemID] = [t13].[ID])
) AS [value4], [t0].[MenuID], [t0].[ProductID]
FROM [dbo].[tbl_MenuProduct] AS [t0]
LEFT OUTER JOIN (
SELECT 1 AS [test], [t1].[ID], [t1].[CreatedDate], [t1].[CreatedUserID], [t1].[UpdatedDate], [t1].[UpdatedUserID], [t1].[DeletedDate], [t1].[DeletedUserID], [t1].[StockCode], [t1].[ProductName], [t1].[Summary], [t1].[BrandID], [t1].[Maskname]
FROM [dbo].[tbl_Product] AS [t1]
) AS [t2] ON [t2].[ID] = [t0].[ProductID]
) AS [t15]
WHERE ([t15].[MenuID] = @p1) AND (EXISTS(
SELECT NULL AS [EMPTY]
FROM (
SELECT TOP (1) [t17].[ID]
FROM [dbo].[tbl_Product] AS [t16], [dbo].[tbl_ProductItem] AS [t17]
WHERE ([t17].[IsDefaultItem] = 1) AND ([t16].[ID] = [t15].[ProductID]) AND ([t17].[ProductID] = [t16].[ID])
) AS [t18]
CROSS JOIN [dbo].[tbl_ProductItemImages] AS [t19]
WHERE ([t19].[ViewType] = @p2) AND ([t19].[ProductItemID] = [t18].[ID])
)) AND (NOT NOT (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[tbl_Product] AS [t20], [dbo].[tbl_ProductItem] AS [t21], [dbo].[tbl_ProductItemSite] AS [t22]
WHERE ((
(CASE
WHEN NOT (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[tbl_SizeStock] AS [t23]
WHERE ((
(CASE
WHEN ([t23].[OnlineStock] = @p3) AND ([t23].[NationalStock] = @p4) THEN 1
ELSE 0
END)) = 0) AND ([t23].[ProductItemSiteID] = [t22].[ID])
)) THEN 1
ELSE 0
END)) = 0) AND ([t22].[SiteID] = @p0) AND ([t20].[ID] = [t15].[ProductID]) AND ([t21].[ProductID] = [t20].[ID]) AND ([t22].[ProductItemID] = [t21].[ID])
))) AND (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[tbl_Product] AS [t24], [dbo].[tbl_ProductItem] AS [t25], [dbo].[tbl_ProductItemSite] AS [t26]
WHERE ([t26].[SiteID] = @p0) AND ([t24].[ID] = [t15].[ProductID]) AND ([t25].[ProductID] = [t24].[ID]) AND ([t26].[ProductItemID] = [t25].[ID])
))
ORDER BY [t15].[ProductName]',N'@p0 bigint,@p1 int,@p2 int,@p3 int,@p4 int',@p0=3,@p1=219,@p2=0,@p3=0,@p4=0
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Probably a bit hard to see without knowing my table structure but you can see the SQL difference here.....
BTW This was not aimed as a support question but it is the findings of an investigation into the difference between the generated SQL which is created by LINQ to SQL between C# and VB.NET.
UPDATE 20080207:
I have inclulded a screenshot of my SQL execution plan. Top query is VB and bottom is C# you can see the C# query is much much much much quicker if you look at the relative % of the batch. I need to investiage this alot more but is looking grim... Might have to look into converting DAL to C# for this project.
Also look at the CPU and Reads for the two queries. Top is Vb and bottom C# again yet again a big difference...
Thanks
Stefan