Improve query generated from entity framework - c#

I have a query linq like this:
var query = from c in Context.Customers select c;
var result = query.ToList();
Linq query generate this tsql code:
exec sp_executesql N'SELECT
[Project1].[Id] AS [Id],
[Project1].[Name] AS [Name],
[Project1].[Email] AS [Email]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Email] AS [Email]
FROM [dbo].[Customers] AS [Extent1] ) AS [Project1]
Is there a way for not generate subquery?

Do you have any evidence that that query is causing performance problems? I imagine the query-optimizer would easily recognize that.
If you're certain after profiling that the query is a performance problem (doubtful) - and only then - you could simply turn the query into a stored procedure, and call that instead.

You use a tool like Linq because you don't want to write SQL, before abandoning that you should at least compare the query plan of your proposed SQL vs that generated by the tool. I don't have access to SQL Studio at the moment, but I would be a bit surprised if the query plans aren't identical...
EDIT: having had a chance to check out the query plans, they are in fact identical.

Short answer: No you cannot modify that query.
Long answer: If you want to reimplement Linq provider and query generator then perhaps there is a way but I doubt you want to do that. You can also implement custom EF provider wrapper which will take query passed from EF and reformat it but that will be hard as well - and slow. Are you going to write custom interpreter for SQL queries?

Related

Count(x => ...) vs Where(x => ...).Count()

Is there any difference between these two LINQ-to-Entities queries:
context.Table.Count(x => ...)
and
context.Table.Where(x => ...).Count()
in terms of performance and generated SQL?
I tried to look generated SQL myself, but I only know how to get the SQL from IQueryable, but Count returns the value directly.
I have managed to see the SQL (thanks to #dasblinkenlight), the answer is - no, both LINQ queries generate exactly the same SQL query, at least for a simple query without a grouping:
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[Table] AS [Extent1]
WHERE <condition>
) AS [GroupBy1]

SQL equivalent of Count extension method for LINQ isn't obvious

I'm doing LINQ to entity framework (EF) to get count of records in my table using below code:
using (var db = new StackOverflowEntities())
{
var empLevelCount = db.employeeLevels.Count();
}
I captured the query fired by EF towards database using SQL Server Profiler. I got the following query :
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[employeeLevels] AS [Extent1]
) AS [GroupBy1]
This query remains exactly the same even for LongCount extension method except for the fact that COUNT SQL function gets replaced by COUNT_BIG in the SQL query being created by EF. The query created by LINQ to EF provider looks very weird to me. Why it is not simply doing something like below to return the scalar count value?
SELECT
COUNT(1) AS [A1]
FROM [dbo].[employeeLevels] AS [Extent1]
It will be really helpful if someone can help me understand the additional logistics being taken care of by EF internally which is why LINQ to EF provider is creating such a query? It seems EF is trying to deal with some additional use cases as well through some common algorithm which results in some sort of generic query as the one created above.
Testing both queries (suitably changing the table) in a DB of mine reveals that they both generate exactly the same query plan. So, the structure shouldn't concern you overly much. In SQL, you tell the system what you want, and it works out how best to do it, and here the optimizer is able to generate the optimal plan given either sample.
As to why LINQ generates code like this, I'd suspect it's just a generalized pattern in its code generator that lets it generate similar code for any aggregation and subsequent transformations, not just for unfiltered counts.

Slow SQL query using "SELECT TOP" generated by Linq Take()

I'm having a performance problem with a simple linq query :
var query = ctx.Set<AdministrativeProfile>().OrderBy(x => x.User.Lastname).Skip(9000).Take(1);
The generated SQL is as follow:
SELECT TOP (1)
[Join1].[ProfileID] AS [ProfileID],
[Join1].[Address] AS [Address],
[Join1].[BankAccountNumber] AS [BankAccountNumber],
[Join1].[BankAccountType] AS [BankAccountType],
[Join1].[BankBIC] AS [BankBIC],
[Join1].[BankIBAN] AS [BankIBAN],
[Join1].[BankName] AS [BankName],
[Join1].[City] AS [City],
[Join1].[CountryISO] AS [CountryISO],
[Join1].[IdentifiedUserID1] AS [IdentifiedUserID],
[Join1].[Phone] AS [Phone],
[Join1].[SocialSecurityID] AS [SocialSecurityID],
[Join1].[WithHoldingTaxRate] AS [WithHoldingTaxRate],
[Join1].[Zip] AS [Zip]
FROM ( SELECT [Extent1].[ProfileID] AS [ProfileID], [Extent1].[Address] AS [Address], [Extent1].[BankAccountNumber] AS [BankAccountNumber], [Extent1].[BankAccountType] AS [BankAccountType], [Extent1].[BankBIC] AS [BankBIC], [Extent1].[BankIBAN] AS [BankIBAN], [Extent1].[BankName] AS [BankName], [Extent1].[City] AS [City], [Extent1].[CountryISO] AS [CountryISO], [Extent1].[IdentifiedUserID] AS [IdentifiedUserID1], [Extent1].[Phone] AS [Phone], [Extent1].[SocialSecurityID] AS [SocialSecurityID], [Extent1].[WithHoldingTaxRate] AS [WithHoldingTaxRate], [Extent1].[Zip] AS [Zip], [Extent2].[Lastname] AS [Lastname], row_number() OVER (ORDER BY [Extent2].[Lastname] ASC) AS [row_number]
FROM [dbo].[AdministrativeProfile] AS [Extent1]
INNER JOIN [dbo].[vUsers] AS [Extent2] ON [Extent1].[IdentifiedUserID] = [Extent2].[IdentifiedUserId]
) AS [Join1]
WHERE [Join1].[row_number] > 9000
ORDER BY [Join1].[Lastname] ASC
The running time for this query is approximately 15 seconds.
I read a few things about SELECT TOP being slow because of some kind of sort, but I couldn't manage to find a solution to my problem.
Here's the execution plan
Few things to note :
1) The .Skip(n).Take(x) is added generically by a paging system, so the only part that I could modify without breaking the generic paging is this :
ctx.Set<AdministrativeProfile>().OrderBy(x => x.User.Lastname)
2) I found a few ways to fix the SQL statement and make it lightning fast (like use INNER HASH JOIN in the sub query, or add an additional where clause checking if [Join1].[row_number] < x), but since it is generated by a linq query, it doesn't help me much
3) When doing Skip(x) with a small number for x, it runs much quicker. The execution time increase with how big x is.
4) The tables I use do not have many rows. About 9000 each.
So basically, I know how to fix the SQL, but I don't know how to change the linq query to optimize it.
You should profile the query and see where the bottleneck is. However, I have dealt with a similar problem, and the slowness should be alleviated by adding a non-clustered index to the property by which you sort ([Lastname] in your case).
Basically speaking: the more members you have to skip, the more sorting has to be performed: the sorting becomes much quicker when using an index.

Execution plan of query from profiler

I use SQL profiler to know how Entity Framework convert LINQ expression to sql use database. When query is 'heavy' then I try to optimalize it by examine execution plan.
Profiler (I use Profiler Express) give my query in this format
exec sp_executesql N'SELECT
[Project2].[Id] AS [Id], (... rest od query ... )
',#p__linq__0=N'test#mypage.com'
To see execution plan I have to convert (copy, paste code wrrr) to this format
DELARE #p__linq__0 NVARCHAR(100) =N'test#mypage.com'
SELECT
[Project2].[Id] AS [Id], (... rest od query ... )
It is boring ,irritating etc. Does someony know page or somethig which do it for me? Or I can just set it in options?
Enable "Show Actual Execution Plan" and run the query.

Retrieve LINQ to sql statement (IQueryable) WITH parameters

I'm trying to figure out if there's a way to retrieve the (full) sql statement that gets executed on the database server.
I found something already, but it does not exactly what I would like:
IQueryable<SomeType> someQuery = ...
string command = dataContext.GetCommand(query).CommandText;
In my case this gives me a command string something like:
SELECT TOP (50) [t0].[ID], ....
FROM [dbo].[someTable] AS [t0]
WHERE ([t0].[someColumn] IS NOT NULL) AND (([t0].[someColumn]) IN (#p0))
On database there's executed:
exec sp_executesql N'SELECT TOP (50) [t0].[ID], ...
FROM [dbo].[someTable] AS [t0]
WHERE ([t0].[someColumn] IS NOT NULL) AND (([t0].[someColumn]) IN (#p0, #p1))',N'#p0 int,#p1 int',#p0=401,#p1=201
Is there a way to retrieve this 'full' statement (so also the parameter values) from C# code?
You can also see the generated sql query if you have an instance of
IQueryable<T> and call the .ToString() method.
For Example:
var db = new DbContext();
IQueryable<Blog> query = db.Blog.Where(tt=> tt.Id > 100).OrderByDescending(tt=>tt.Id);
var sqlString = query.ToString();
Console.WriteLine(sqlString);
This will generate an output of:
SELECT [Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title],
[Extent1].[Author] AS [Author],
[Extent1].[Text] AS [Text],
[Extent1].[CreatedAt] AS [CreatedAt],
[Extent1].[UpdatedAt] AS [UpdatedAt]
FROM [dbo].[Blogs] AS [Extent1]
WHERE [Extent1].[Id] > 100
ORDER BY [Extent1].[Id] DESC
Once you get the Command you can print the CommandText and then loop through the Parameters collection and print all the individual parameters.
Also there is the linq-to-sql debug visualizer which does the same in debug mode.
A really nice tool to view the queries as they are happening is the Linq-to-sql profiler
In the latest version of EF Core 5 ToQueryString,
query.ToQueryString()
(SqlCommand)dataContext.GetCommand(query)
will give you access to Parameters collection.
I'm using Datacontext.Log property to get the generated SQL Statement (it includes the statement text, and parameters).
Just set YourDataContext.Log = SomeTextWriter.
It can be written to a file (Log = new StreamWriter(#"c:\temp\linq.log")) or to debug window, see this post
When viewing the IQueryable in the Locals pane, you can access DebugView > Query, which will contain the SQL statement.

Categories