LINQ 或实体框架创建无界 SQL 语句
我致力于应用程序的速度优化,我发现 LINQ(或 EF)正在为我创建一些运行缓慢的奇怪 SQL。
下面是一些代码:
SomeList.AddRange(_databaseView
.Select(l=> new SomeViewModel
{
Date = l.Date,
Details = l.Details,
Level = l.LevelName,
Id = l.ViewID,
Message = l.Message,
ProjectName = l.projectName,
StatusId = l.StatusID,
StatusName = l.StatusName
})
.Skip(50)
.Take(25));
理论上它应该创建需要 25 条记录的 SQL 语句,但探查器显示以下 SQL:
SELECT [Extent1].[Date] AS [Date],
[Extent1].[ID] AS [ID],
[Extent1].[LevelID] AS [LevelID],
[Extent1].[StatusID] AS [StatusID],
[Extent1].[projectName] AS [projectName],
[Extent1].[LevelName] AS [LevelName],
[Extent1].[StatusName] AS [StatusName],
[Extent1].[Message] AS [Message],
[Extent1].[Details] AS [Details],
[Extent1].[LogViewID] AS [LogViewID]
FROM (SELECT [v_MyView].[Date] AS [Date],
[v_MyView].[ProjectID] AS [ProjectID],
[v_MyView].[LevelID] AS [LevelID],
[v_MyView].[StatusID] AS [StatusID],
[v_MyView].[projectName] AS [projectName],
[v_MyView].[LevelName] AS [LevelName],
[v_MyView].[StatusName] AS [StatusName],
[v_MyView].[Message] AS [Message],
[v_MyView].[Details] AS [Details],
[v_MyView].[ViewID] AS [ID]
FROM [dbo].[v_MyView] AS [v_MyView]) AS [Extent1]
_databaseView 是 IQueryable 对象,我的所有排序和过滤逻辑都在该对象上完成。
这里是我想到的: 如果我不做任何过滤 SQL 是正常的 SELECT TOP (25) 就可以了。但每当我进行过滤时,就会出现一些混乱。这是我的过滤器之一的代码:
if (Filters.ProjectName != null && Filters.ProjectName[0] != 0) // check if "all" is not checked
_databaseView = Filters.ProjectName
.Join(_databaseView, f => f, l => l.ProjectID, (f,l) => new SomeViewModel
{
Date = l.Date,
Details = l.Details,
LevelName = l.LevelName,
ViewID = l.ViewID,
Message = l.Message,
projectName = l.projectName,
StatusID = l.StatusID,
StatusName = l.StatusName
})
.AsQueryable();
并且它没有任何限制。我如何让这个 LINQ-EF 东西产生一些好的 SQL ?
提前谢谢!
I work on speed optimization of my application, and what I have found is that LINQ (or EF) is creating some strange SQL for me that works slow.
Here is some code :
SomeList.AddRange(_databaseView
.Select(l=> new SomeViewModel
{
Date = l.Date,
Details = l.Details,
Level = l.LevelName,
Id = l.ViewID,
Message = l.Message,
ProjectName = l.projectName,
StatusId = l.StatusID,
StatusName = l.StatusName
})
.Skip(50)
.Take(25));
And in theory it should've created SQL statement that takes 25 record but profiler showes following SQL for it :
SELECT [Extent1].[Date] AS [Date],
[Extent1].[ID] AS [ID],
[Extent1].[LevelID] AS [LevelID],
[Extent1].[StatusID] AS [StatusID],
[Extent1].[projectName] AS [projectName],
[Extent1].[LevelName] AS [LevelName],
[Extent1].[StatusName] AS [StatusName],
[Extent1].[Message] AS [Message],
[Extent1].[Details] AS [Details],
[Extent1].[LogViewID] AS [LogViewID]
FROM (SELECT [v_MyView].[Date] AS [Date],
[v_MyView].[ProjectID] AS [ProjectID],
[v_MyView].[LevelID] AS [LevelID],
[v_MyView].[StatusID] AS [StatusID],
[v_MyView].[projectName] AS [projectName],
[v_MyView].[LevelName] AS [LevelName],
[v_MyView].[StatusName] AS [StatusName],
[v_MyView].[Message] AS [Message],
[v_MyView].[Details] AS [Details],
[v_MyView].[ViewID] AS [ID]
FROM [dbo].[v_MyView] AS [v_MyView]) AS [Extent1]
_databaseView is IQueryable object on which all my sorting and filtering logic is done.
Here is something I figured :
if I don't do any filtering SQL is normal with SELECT TOP (25) on it. But whenever I do filtering something gets messed up. Here is the code to one of my filters:
if (Filters.ProjectName != null && Filters.ProjectName[0] != 0) // check if "all" is not checked
_databaseView = Filters.ProjectName
.Join(_databaseView, f => f, l => l.ProjectID, (f,l) => new SomeViewModel
{
Date = l.Date,
Details = l.Details,
LevelName = l.LevelName,
ViewID = l.ViewID,
Message = l.Message,
projectName = l.projectName,
StatusID = l.StatusID,
StatusName = l.StatusName
})
.AsQueryable();
And it is without any constraint. How do I make this LINQ-EF thing to produce some good SQL ?
Thx in advance!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您没有说明
_DatabaseView
是什么,但根据您的结果我的大胆猜测是它不是ObjectQuery
。这可以解释你的问题。ObjectQuery
将转换为 SQL;IEnumerable.Skip()
不会。对可枚举对象调用AsQueryable()
不足以实现这一点。例如,这样:
...不会将
TOP
放入 SQL 中。但这:
……将会。
再说一遍:您还没有说出
_DatabaseView
是什么。直接在ObjectContext
上尝试此操作,您将看到它有效。该错误存在于您用来分配_DatabaseView
的代码中,但您尚未向我们展示该代码。You don't say what
_DatabaseView
is, but my wild guess based on your results is that it's not anObjectQuery<T>
. Which would explain your problem.ObjectQuery
will convert to SQL;IEnumerable<T>.Skip()
won't. CallingAsQueryable()
on an enumerable is not enough to make this happen.For example, this:
...won't put the
TOP
in the SQL.But this:
... will.
Once again: You haven't said what
_DatabaseView
is. Try this operation directly on yourObjectContext
and you will see that it works. The bug is in the code you use to assign_DatabaseView
, which you haven't shown us.实际更改正在使用的 SQL 的唯一方法是编写自己的 SQL 并使用它,而不是使用生成的 SQL。
您的意思是 LINQ 的 Skip 和 Take 部分未转换为 SQL。我认为这是因为你使用 LINQ 的方式。
尝试类似
Replace 的方法,看看它是否会对生成的代码产生影响。
编辑 不知怎的,我错过了你所说的应该是 SQL 需要 25 条记录的部分。
The only way you can actually change the SQL that is being used would be to write your own and use that instead of using the generated SQL.
You mean the Skip and Take portions of the LINQ not being converted to SQL. I think it is because of the way that you are doing the LINQ.
Try something like
Instead and see if it makes a difference in the generated code.
Edit Somehow I missed the portion where you said it should be SQL that takes 25 records.
LINQ 解析器肯定会考虑 LINQ to Entities 查询中的
Skip
和Take
方法,并生成正确的表达式树,然后对象服务转换表达式树命令树将被传递给数据库提供程序以生成特定的 SQL 查询。在本例中,这 2 个方法通过
WHERE [Extent1].[row_number] > 影响生成的 SQL。 50
和SELECT TOP (25)
分别用于Skip
和Take
。现在,您确定在 Profiler 中查看的是正确的跟踪吗?我建议看看ObjectQuery.ToTraceString 方法,在进入 Profiler 之前编写以下代码,然后调试代码并检查 sql 的值em> 变量:
LINQ Parser definitely consider the
Skip
andTake
methods in your LINQ to Entities query and produces the correct expression tree and then Object Services converts the expression tree to a command tree which will be passed to the database provider for the specific SQL query generation.In this case this 2 methods affecting the generated SQL with a
WHERE [Extent1].[row_number] > 50
andSELECT TOP (25)
forSkip
andTake
respectively.Now, are you sure that you are looking at the right trace in the Profiler? I suggest take a look at ObjectQuery.ToTraceString method by writing the below code before going to Profiler and then debug through your code and examine the value of sql variable:
如果您无法使用适当的索引使 SQL 执行得足够好,那么您始终可以尝试编写存储过程并从 LINQ 调用它。
If you can't get the SQL to perform well enough with the proper indexes then you could always try writing a stored procedure and just calling that from LINQ.
尝试将“跳过”和“获取”移到“选择”之前。
Try moving the Skip and Take before the Select.