【问题标题】:How to optimize a LINQ Where condition with Any(x => DbFunctions.Like())如何使用 Any(x => DbFunctions.Like()) 优化 LINQ Where 条件
【发布时间】:2020-10-09 12:34:35
【问题描述】:

此 LINQ 语句:

var entities = SomeEntities.Where(se => se.SomeProperty == "SomeValue");

var stringList = new List<string> { "string1", "string2", "string3" }
var startsWith = stringList.Select(x => x + "%");
entities = entities.Where(e => startsWith.Any(sw => DbFunctions.Like(e.StringProperty, sw))).Select(e => e.Id);

产生类似的 SQL 查询,如下所示:

SELECT 
[Project8].[Id] AS [Id]
FROM ( SELECT 
    [Extent1].[Id] AS [Id], 
    FROM  [SomeEntities] AS [Extent1]
    WHERE ([Extent1].[SomeProperty] == 'SomeValue') AND( EXISTS (SELECT 
        1 AS [C1]
        FROM  (SELECT 
            N'string1%' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
        UNION ALL
            SELECT 
            N'string2%' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]
        UNION ALL
            SELECT 
            N'string3%' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2]
        WHERE [Extent1].[StringProperty] LIKE [UnionAll2].[C1]
    )))
)  AS [Project8]
ORDER BY [Project8].[Status] DESC

如何实现类似于以下的查询:

SELECT [Id]
FROM [SomeEntities]
WHERE [SomeProperty] == 'SomeValue'
AND (StringProperty] LIKE 'string1%'
    OR StringProperty] LIKE 'string2%'
    OR StringProperty] LIKE 'string3%')

【问题讨论】:

  • Where(se =&gt; se.SomeProperty == "SomeValue" &amp;&amp; startsWith.Any( sw =&gt; se.StringProperty.StartsWith( sw ))) ?
  • 要获得准确的 SQL 或非常接近它的东西,您必须手动创建表达式。但是,我不会太担心生成的确切 SQL。只需担心正确性,然后如果性能存在问题,请考虑修复。
  • 如果您关心 SQL 和性能,您一开始就不应该使用 LINQ-to-SQL。该项目主要是作为 LINQ 的演示而创建的。它有很多性能问题,包括没有警告的客户端评估。如果您改用 EF Core,会发生什么情况?

标签: c# performance linq linq-to-sql


【解决方案1】:

首先,我建议使用PredicateBuilder 进行复杂查询。它有助于轻松创建和组合表达式,然后使用 EF 对其进行评估。

然后,read the difference between EF.Functions.LikeContains, StartWith, etc 并决定你需要什么。

代码示例:

List<string> searchStrings=new List<string>(){"name1","name2","name3"};
var predicate = searchStrings
            .Select<string, Expression<Func<Person, bool>>>(search => item => EF.Functions.Like(item.Name, $"{search}%"))
            .DefaultIfEmpty(patient => false) //or whatever else you want to do if there are no search strings
            .Aggregate(PredicateBuilder.Or);

predicate = predicate.And(item => item.Deleted == null);        
var filteredPatients = await _context.Persons.Where(predicate).ToListAsync();

来自 sql server profiler 的查询:

exec sp_executesql N'SELECT [p].[Id], [p].[B], [p].[Deleted], [p].[Email], 
[p].[Name] FROM [Persons] AS [p]
WHERE [p].[Deleted] IS NULL AND (((([p].[Name] LIKE @__Format_1) OR ([p]. 
[Name] LIKE @__Format_2)) OR ([p].[Name] LIKE @__Format_3)) AND [p].[Deleted] 
IS NULL)',N'@__Format_1 nvarchar(4000),@__Format_2 nvarchar(4000),@__Format_3 
nvarchar(4000)',@__Format_1=N'name1%',@__Format_2=N'name2%',@__Format_3=N'name3%'

【讨论】:

    【解决方案2】:

    你可以试试:

    var entityIds = SomeEntities
                      .Where(se => se.SomeProperty == "SomeValue" 
                         && stringList.Any(sw => se.StringProperty.StartsWith(sw)))
                      .Select(se=>se.Id);
    

    【讨论】:

    • 如果您使用的是StartsWith,那么您需要使用stringList 列表,而不是在每个字符串末尾添加了% 的startsWith 列表。我也非常怀疑使用 Any 会导致 OR 语句而不是另一个 EXISTS 语句
    • @juharr 哎呀,我把变量弄混了。
    • 使用 DBFunctions.Like 或 StartsWith 不是重点。它们都会导致丑陋的 SQL 语句。 @Ярослав-Виталиевич 提供了改进结果陈述的答案。
    • @NielsR。如果您关心高性能 SQL,则首先不应该使用 LINQ-to-SQL。它只是作为 LINQ 的一个演示项目,并且在很长一段时间内都没有得到改进。我假设您使用的是 EF 或 EF Core 并使用了错误的标签。
    • @NielsR。例如,在这种情况下,L2S 尝试在该查询中创建一个包含所有 (SELECT 1 AS X) 的表。如果 EF Core 想要这样做,它将使用表行构造函数(这样做是为了在单个 INSERT 查询中批处理多个 INSERT)
    【解决方案3】:
    entities = entities.Where(e => startsWith.Any(sw => e.StringProperty.Contains("string1") || e.StringProperty.Contains("string2") || e.StringProperty.Contains("string3") ))).Select(e => e.Id);
    

    【讨论】:

    • Contains 生成LIKE '%abc%'
    • 如果您要对每个字符串进行比较,则使用startsWith 毫无意义。您还想使用StartsWith("string1") 而不是Contains
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-17
    • 1970-01-01
    • 1970-01-01
    • 2023-01-25
    相关资源
    最近更新 更多