I'm trying to make my Linq-to-SQL query more efficient by including child properties in one trip to the DB. I started by trying various linq queries to accomplish this. The queries were getting complex, so I tried the LoadWith() option:
The constructor of my DAL class sets the LoadWith() settings:
public TrackerJobData()
{
dataLoadOptions = new DataLoadOptions();
dataLoadOptions.LoadWith<TrackerJobRecord>(x => x.SpecBarcodeRecords);
dataLoadOptions.LoadWith<TrackerJobRecord>(x => x.TrackerJobEquipmentTriggerRecords);
dataLoadOptions.LoadWith<TrackerJobRecord>(x => x.EtaRecord);
this.Database.LoadOptions = dataLoadOptions;
}
And here is the query I'm using:
public TrackerJob GetItem(int trackerJobId)
{
TrackerJobRecord record =
(from trackerJob in this.Database.TrackerJobRecords
where trackerJob.TrackerJobId == trackerJobId
select trackerJob).FirstOrDefault();
return record.Map();
}
When I debug and F10 on just the linq query (not the return), I get this output in SQL Profiler:
Pardon my ignorance of SQL Profiler, but do the three highlighted lines mean there were three round trips from the client (my code) to the server? If so, why? Will SQL Server ever execute multiple sp_executesql calls in one trip?
And since I thought LoadWith() would eliminate multiple calls, what am I doing incorrectly?
EDIT
Here are the three statements within SQL Profiler:
exec sp_executesql N'SELECT TOP (1) [t0].[TrackerJobId], [t0].[Name], [t0].[EtaId], [t0].[SamplingProcessorTypeId], [t0].[Description], [t0].[LastModifiedUser], [t0].[LastModifiedTime], [t0].[VersionNumber], [t0].[Active], [t0].[Archived], [t1].[EtaId] AS [EtaId2], [t1].[EtaNumber], [t1].[Title], [t1].[State], [t1].[DateInitialized], [t1].[EtaOriginatorId], [t1].[Quantity], [t1].[Ehs], [t1].[Ship], [t1].[InternalUse], [t1].[DateClosed], [t1].[ExperimentId], [t1].[Disposition], [t1].[TestType], [t1].[LastModifiedUser] AS [LastModifiedUser2], [t1].[LastModifiedTime] AS [LastModifiedTime2], [t1].[VersionNumber] AS [VersionNumber2]
FROM [AutoTracker].[TrackerJob] AS [t0]
INNER JOIN [Global].[Eta] AS [t1] ON [t1].[EtaId] = [t0].[EtaId]
WHERE [t0].[TrackerJobId] = #p0',N'#p0 int',#p0=17
exec sp_executesql N'SELECT [t0].[SpecBarcodeId], [t0].[TrackerJobId], [t0].[EquipmentId], [t0].[StartTime], [t0].[EndTime], [t0].[LastModifiedUser], [t0].[LastModifiedTime], [t0].[VersionNumber]
FROM [AutoTracker].[SpecBarcode] AS [t0]
WHERE [t0].[TrackerJobId] = #x1',N'#x1 int',#x1=17
exec sp_executesql N'SELECT [t0].[TrackerJobId], [t0].[EquipmentId], [t0].[LastModifiedUser], [t0].[LastModifiedTime], [t0].[VersionNumber]
FROM [AutoTracker].[TrackerJobEquipmentTrigger] AS [t0]
WHERE [t0].[TrackerJobId] = #x1',N'#x1 int',#x1=17
Linq-2-sql LoadWith does not support multiple 1:N relationships.
http://weblogs.asp.net/zeeshanhirani/archive/2008/08/11/constraints-with-loadwith-when-loading-multiple-1-n-relationships.aspx
Linq2SQl eager load with multiple DataLoadOptions
Each of those SQL Profiler calls represents a single roundtrip from client to DB server instance. SQL Server does support returning data sets in a single roundtrip, but I'm not sure how you would do that with LINQ to SQL.
Related
I create a query in linq which returns a table of most active salesmen in my shop:
ProjectDB3Context db = new ProjectDB3Context();
db.Database.Log = message => Trace.WriteLine(message);
var result = db.tblUsers.Join(db.tblSales,
u => u.ID,
sl => sl.tblUserId,
(u, sl) => new { u, sl })
.Select(o => new
{
UserId = o.u.ID,
Login = o.u.UserLogin,
FullName = o.u.Name + " " + o.u.Surname,
ItemsToSell = db.tblSales.Where(x => x.tblUserId == o.u.ID).Count()
})
.Distinct()
.OrderByDescending(x => x.ItemsToSell)
.ToList();
The henerated SQL query looks like:
SELECT
[Distinct1].[C1] AS [C1],
[Distinct1].[ID] AS [ID],
[Distinct1].[UserLogin] AS [UserLogin],
[Distinct1].[C2] AS [C2],
[Distinct1].[C3] AS [C3]
FROM ( SELECT DISTINCT
[Project1].[ID] AS [ID],
[Project1].[UserLogin] AS [UserLogin],
1 AS [C1],
[Project1].[Name] + N' ' + [Project1].[Surname] AS [C2],
[Project1].[C1] AS [C3]
FROM ( SELECT
[Extent1].[ID] AS [ID],
[Extent1].[UserLogin] AS [UserLogin],
[Extent1].[Name] AS [Name],
[Extent1].[Surname] AS [Surname],
(SELECT
COUNT(1) AS [A1]
FROM [dbo].[tblSale] AS [Extent3]
WHERE [Extent3].[tblUserId] = [Extent1].[ID]) AS [C1]
FROM [dbo].[tblUser] AS [Extent1]
INNER JOIN [dbo].[tblSale] AS [Extent2] ON [Extent1].[ID] = [Extent2].[tblUserId]
) AS [Project1]
) AS [Distinct1]
ORDER BY [Distinct1].[C3] DESC
Statistics:
SQL Server Execution Times:
CPU time = 359 ms, elapsed time = 529 ms.
Execution plan screen shot
I want to optimize the generated SQL query and insert optimized query into a stored procedure. SQL Server Management Studio gives me a tip to create a nonclustered index (tblUserId) on tblSale (you can see this tip in image that I included).
When I create it using command:
CREATE NONCLUSTERED INDEX IX_ProductVendor_tblUserId
ON tblSale (tblUserId);
and then run the SQL query in SQL Server Management Studio I get:
SQL Server Execution Times:
CPU time = 328 ms, elapsed time = 631 ms.
So it takes much longer after I used index to optimize my SQL query.
Can anybody help me with optimize this query in SQL Server using indexes?
Can anybody help me with optimize this query in SQL Server using indexes?
First off, before trying to optimize the SQL query in database, make sure your LINQ query is optimal. Which is not the case with yours. There is unnecessary join which in turn requires distinct etc. And tblSales is accessed twice (see the generated SQL).
What you are trying to achieve is to get users with sales ordered by sales count descending. The following simple query should produce the desired result
var result = db.tblUsers
.Select(u => new
{
UserId = u.ID,
Login = u.UserLogin,
FullName = u.Name + " " + u.Surname,
ItemsToSell = db.tblSales.Count(s => s.tblUserId == u.ID)
})
.Where(x => x.ItemsToSel > 0)
.OrderByDescending(x => x.ItemsToSell)
.ToList();
Try and see the new execution plan/time.
I want to optimize the generated SQL query and insert optimized query into a stored procedure.
Bzzt. Wrong.
Your query is already "optimized" - in that there isn't anything you can do to the query itself to improve its runtime performance.
Stored procedures in SQL Server do not have any kind of magic-optimization or other real advantages over immediately-executed queries. Stored procedures do benefit from cached execution plans, but so do immediate-queries after their first execution, and execution-plan generation isn't that expensive an operation.
Anyway, using stored procedures for read-only SELECT operations is inadvisble, it is better to use an UDF (CREATE FUNCTION) so you can take advantage of function composition which can be optimized and runtime far better than nested stored procedure calls.
If SQL Server's Show Execution Plan feature tells you to create an index, that is outside of EF's responsibility, it is also outside a stored procedure's responsibility too. Just define the index in your database and include it in your setup script. Your EF-generated query will run much faster without it being a stored procedure.
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.
I have a following query in nHibernate. The idea is to get first and last measurement time of certain data group.
var measurements = _session.Query<Measurement>()
.Where(x => categories.Contains(x.CategoryId));
first = measurements.Min(o => o.StartTime);
last = measurements.Max(o => o.StartTime);
The SQL Server Profiler gives following output:
exec sp_executesql N'select cast(min(measuremen0_.StartTime) as DATETIME) as col_0_0_ from Measurement measuremen0_ where measuremen0_.Category in (#p0 , #p1)',N'#p0 int,#p1 int',#p0=7654321,#p1=3324673
exec sp_executesql N'select cast(max(measuremen0_.StartTime) as DATETIME) as col_0_0_ from Measurement measuremen0_ where measuremen0_.Category in (#p0 , #p1)',N'#p0 int,#p1 int',#p0=7654321,#p1=3324673
Can I somehow optimize this without using HQL, so that it would create only one request to the database server?
Did you take a look at the Future Queries? I think it works for linq queries as well.
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?
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.