C# 我需要加快以下Linq查询的速度

C# 我需要加快以下Linq查询的速度,c#,sql,linq,entity-framework,C#,Sql,Linq,Entity Framework,我有一个旧的存储过程,我正在重写成一个EF Linq查询,但是这个过程几乎快了3倍 以下是查询语法的示例: public string GetStringByID(long ID) { return dataContext.Table2.FirstOrDefault(x => x.Table2ID == ID).Table1.StringValue; } 下面是我正在使用的存储过程代码以及调用它的方法 存储过程是: PROCEDURE [dbo].[MyQ

我有一个旧的存储过程,我正在重写成一个EF Linq查询,但是这个过程几乎快了3倍

以下是查询语法的示例:

public string GetStringByID(long ID)
    {
        return dataContext.Table2.FirstOrDefault(x => x.Table2ID == ID).Table1.StringValue;
    }
下面是我正在使用的存储过程代码以及调用它的方法

存储过程是:

PROCEDURE [dbo].[MyQuickerProc]
@ID bigint
AS
BEGIN
SET NOCOUNT ON;

IF EXISTS(SELECT TOP 1 ID FROM Table2 WHERE Table2ID = @Id)
    BEGIN
        SELECT TOP 1 t1.StringValue
        FROM Table2  t2
            INNER JOIN Table1 t1 ON t1.Table1ID= Table2.Table1ID
        WHERE Table2ID = @ID
    END
ELSE
    BEGIN
        SELECT TOP 1 t1.StringValue
        FROM Table2 t2
            INNER JOIN Table1 t1 ON t1.Table1Id = Table2.Table1ID
        WHERE Table2ID IS NULL
    END

END
我这样称呼proc:

string myString = context.MyQuickerProc(127).FirstOrDefault();
我使用单元测试和秒表发现Linq调用需要1.3秒,存储过程调用需要0.5秒,时间长得惊人!我正在调查我们谈话时失踪的FK,因为我只能假设这就是这些电话花了这么长时间的原因

在任何情况下,我都需要加快此Linq查询的速度,并添加存储过程中缺少的功能,而当前Linq查询不包含这些功能(if/else逻辑)


在此方面的任何帮助都将不胜感激。提前感谢:)

我建议您做的第一件事是在linq查询中调用ToString(),以查看正在生成的SQL。根据您的查询和配置,您可能要两次访问数据库,一次是获取Table2,然后再次通过延迟加载获取关联的Table1实体。您应该尝试使用SQL事件探查器或使用调试器验证是否存在这种情况。查看按照以下方式重写查询是否会增加任何性能增强,从而急切地加载相关实体:

var result = dataContext.Table2.
             .include("Table1")
             .FirstOrDefault(x => x.Table2ID == ID);

if(result != null){
    return result..Table1.StringValue;
}else{....}
注意,我还添加了一些逻辑检查结果是否为null。您正在使用FirstOrDefault,如果找不到结果,这将导致.Table1引发异常。如果不希望结果为null,我将更改对First()的调用,或者处理null情况

您应该了解的另一件事是如何配置EF以匹配空情况,这可能会降低查询速度。查看此帖子(不是链接到我自己的帖子,而是相关的):

这应该会产生正确的结果,但我不知道它的效率有多高;您必须对其进行分析。请注意,查询实际上只从数据库中获取单个字符串,不需要实体框架进行任何客户端处理

dataContext.Table2
           .Where(x => (x.Table2ID == ID) || (x.Table2ID == null))
           .OrderByDescending(x => x.Table2ID) // This will place ID before NULL.
           .Select(x => x.Table1.StringValue)
           .First()
使用LINQPad,我或多或少得到了预期的SQL语句,但我没有尝试实体框架是否会生成相同的查询。但是,由于这是一个单一查询,实体框架甚至有一点可能在其条件第二个查询中优于存储过程,但这显然只是因为重新格式化的查询

 SELECT TOP (1) [t1].[StringValue]
           FROM [Table2] AS [t2]
LEFT OUTER JOIN [Table1] AS [t1]
             ON [t1].[Table1ID] = [t2].[Table1ID]
          WHERE ([t2].[Table2ID] = @ID) OR ([t2].[Table2ID] IS NULL)
       ORDER BY [t2].[Table2ID] DESC
步骤1:建立商业案例 我们需要做的第一件事是问“它需要多快?”,因为如果我们不知道它需要多快,我们就无法知道何时完成。这不是技术决定,而是商业决定。你需要一个以利益相关者为中心的“足够快”的衡量标准来实现目标,并且你需要记住足够快就是足够快。我们不寻求“尽可能快”,除非有商业理由。即便如此,我们通常还是希望“在预算范围内尽可能快”

既然您是我的利益相关者,而且您似乎对存储过程的性能没有太大的不满,那么让我们将其作为基准

步骤2:测量 我们需要做的下一件事是测量我们的系统,看看我们是否足够快

谢天谢地,您已经进行了测量(尽管我们稍后将对此进行更多讨论)您的存储过程在0.5秒内运行!够快吗?是的

没有理由继续花你的时间(和你老板的钱)去修理没有坏的东西。你可能有更好的事情要做,所以去做吧D


还在这儿吗?那好吧接受挑战

步骤3:检查 发生了什么事?为什么我们的查询这么慢

为了回答这个问题,我需要对你的模型做一些假设:-

public class Foo
{
    public int Id { get; set; }

    public int BarId { get; set; }

    public virtual Bar Bar { get; set; }
}

public class Bar
{
    public int Id { get; set; }

    public string Value { get; set; }

    public virtual ICollection<Foo> Foos { get; set; }
}
我可以从日志中看到正在运行两个查询:-

SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent1].[BarId] AS [BarId]
FROM [dbo].[Foos] AS [Extent1]
WHERE 1 = [Extent1].[Id]

SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Value] AS [Value]
FROM [dbo].[Bars] AS [Extent1]
WHERE [Extent1].[Id] = @EntityKeyValue1
等等,什么?当我们只需要一个字符串时,为什么愚蠢的实体框架要对数据库进行两次往返

步骤4:分析 让我们后退一步,再次查看我们的查询:-

var query = context.Foos.FirstOrDefault(x => x.Id == 1).Bar.Value;
根据我们所知道的,我们能推断出这里发生了什么

延迟执行的基本意思是,只要您使用
IQueryable
,实际上什么也不会发生-查询建立在内存中,直到稍后才真正执行。这非常有用,原因有很多——特别是它让我们以模块化的方式构建查询,然后运行组合查询一次。如果
context.Foos
立即将整个
Foo
表加载到内存中,实体框架将非常无用

我们的查询只有在我们请求除
IQueryable
以外的其他内容时才会运行,例如使用
.AsEnumerable()
.ToList()
,或者特别是
.GetEnumerator()
等。在这种情况下
.FirstOrDefault()
不会返回
IQueryable
,因此,这会比我们预期的更早触发数据库调用

我们提出的问题基本上是:-

  • 获取第一个
    Foo
    ,Id==1(如果没有则为
    null
  • 现在
    Foo
    Bar
  • 现在告诉我
哇!因此,我们不仅要对数据库进行两次往返访问,而且还要通过网络发送整个
Foo
Bar
!当我们的实体像这里的人造实体一样小的时候,这并不是很糟糕,但是如果它们是更大的现实实体呢

步骤5:优化 正如您希望从上面收集到的,优化的前两条规则是1)“不要”和2“首先测量”第三条规则是“避免不必要的工作”。一次额外的往返和一大堆虚假数据肯定是“不必要的”,所以让我们做点什么:-

var query = context.Foos.FirstOrDefault(x => x.Id == 1).Bar.Value;
var query = context.Bars.Where(x => x.Foos.Any(y => y.Id == 1))
                        .Select(x => x.Value)
                        .FirstOrDefault();

SELECT TOP (1)
[Extent1].[Value] AS [Value]
FROM [dbo].[Bars] AS [Extent1]
WHERE  EXISTS (SELECT
    1 AS [C1]
    FROM [dbo].[Foos] AS [Extent2]
    WHERE ([Extent1].[Id] = [Extent2].[BarId]) AND (1 = [Extent2].[Id])
)
var query = context.Foos.Where(x => x.Id == 1)
                        .Select(x => x.Bar.Value)
                        .FirstOrDefault();

SELECT TOP (1)
[Extent2].[Value] AS [Value]
FROM  [dbo].[Foos] AS [Extent1]
INNER JOIN [dbo].[Bars] AS [Extent2] ON [Extent1].[BarId] = [Extent2].[Id]
WHERE 1 = [Extent1].[Id]