简体   繁体   English

实体框架、LINQ:不能将 Any 与 StartsWith 一起使用?

[英]Entity Framework, LINQ: can't use Any with StartsWith?

I have a simple Entity Framework LINQ query.我有一个简单的实体框架 LINQ 查询。 The goal is to find all the Carriers that start with A, B, or C:目标是找到所有以 A、B 或 C 开头的载体:

var letters = new List<string>() { "A", "B", "C" }; // Dynamic, can be many
var results = db.Carriers.AsNoTracking()
    .Where(c => letters.Any(val => c.Name.StartsWith(val)))
    .ToList();

I get我得到

System.InvalidOperationException: 'The LINQ expression 'DbSet .Where(c => __letters_0 .Any(val => val == "" || c.Name != null && val != null && c.Name.StartsWith(val)))' could not be translated. System.InvalidOperationException: 'LINQ 表达式 'DbSet .Where(c => __letters_0 .Any(val => val == "" || c.Name != null && val != null && c.Name.StartsWith(val) ))' 无法翻译。 Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。 See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'有关详细信息,请参阅https://go.microsoft.com/fwlink/?linkid=2101038

Is there no way to do this?没有办法做到这一点吗?

In Entity Framework 6 this query would run fine.在实体框架 6 中,此查询可以正常运行。 EF6 supports SQL translation of StartsWith and it supports using local sequences ( letters ) in a query expression. EF6 支持StartsWith SQL 转换,并且支持在查询表达式中使用本地序列( letters )。

EF core 3 (the version in the question as the exception message indicates) also supports SQL translation of StartsWith . EF 核心 3(异常消息所指示的问题中的版本)还支持StartsWith SQL 转换。 The problem here (which the other answer misses completely) is that the local sequence is used in a way that's not supported.这里的问题(另一个答案完全忽略了)是本地序列的使用方式不受支持。 A query like...像这样的查询...

var results = db.Carriers.AsNoTracking()
    .Where(c => letters.Contains(c.Name))
    .ToList();

...would be supported because letters can simply be translated into an IN clause. ...将得到支持,因为letters可以简单地转换为IN子句。 But of course that's an entirely different query.但当然,这是一个完全不同的查询。

Using letters.Any requires EF to convert letters into "something" that can be joined with in SQL.使用letters.Any需要 EF 将letters转换为可以在 SQL 中连接的“东西”。 EF6 does this by building a result set in the SQL query: EF6 通过在 SQL 查询中构建结果集来做到这一点:

    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM  (SELECT 
            N'A' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
        UNION ALL
            SELECT 
            N'B' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]
        UNION ALL
            SELECT 
            N'C' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2]
        WHERE ( CAST(CHARINDEX([UnionAll2].[C1], [Extent1].[Name]) AS int)) = 1

Which works, but isn't scalable at all.哪个有效,但根本无法扩展。 EF core 3 doesn't support it and there's no easy work-around as suggested in the other answer. EF core 3 不支持它,并且没有其他答案中建议的简单解决方法。

A possible work-around is to build a predicate with ||一种可能的解决方法是使用||构建谓词(Or) predicates, for example: (或)谓词,例如:

var pred = letters.Aggregate(PredicateBuilder.False<Carrier>(), 
    (p,x) => p = p.Or(c => c.Name.StartsWith(x)));
var results = db.Carriers.AsNoTracking()
    .Where(pred)
    .ToList();

Where PredicateBuilder is a predicate builder like Linqkit or this one .其中PredicateBuilder是像 Linqkit 或this one这样的谓词构建器。 But this method isn't scalable either.但是这种方法也不是可扩展的。 EF creates a parameter for each entry in letters , so you may hit the 2100-parameters threshold in Sql Server. EF创建中的每个条目参数letters ,所以你可能会碰到在SQL Server 2100的参数阈值。

The Entity Framework doesn't allow usage of custom predicates.实体框架不允许使用自定义谓词。 That's the reason why your query is failing with a message:这就是您的查询失败并显示一条消息的原因:

... could not be translated. ……无法翻译。 Either rewrite the query in a form that can be translated ...要么以可以翻译的形式重写查询......

So, this most probably means that your EF version either doesn't support StartsWith or there is some combination, like StartsWith and Any that can't be translated.因此,这很可能意味着您的 EF 版本要么不支持StartsWith要么存在某些组合,例如无法翻译的StartsWithAny

A workaround could be:解决方法可能是:

  1. First, you might filter your query with the closest way, by using the Contains method.首先,您可以使用Contains方法以最接近的方式过滤您的查询。
var results = students.Where(s => letters.Any(l => s.name.Contains(l)));

Here is a similar example I wrote for .NET fiddle.是我为 .NET fiddle 编写的一个类似示例。


  1. Next in case if you still want to use StartsWith but your EF doesn't support that you can convert your query to IN-MEMORY collection :接下来,如果您仍想使用StartsWith但您的 EF 不支持您可以将查询转换为 IN-MEMORY 集合:
var results = filter_by_contains_query
              .ToList() // convert filtered result to in-memory collection
              .Where(i => letters.Any(l => i.StudentName.StartsWith(l)));

where filter_by_contains_query is a query from the 1st part.其中filter_by_contains_query是第一部分的查询。


More:更多的:

  • About EF supported methods check here .关于 EF 支持的方法,请查看此处
  • Discussions around the StartsWith , EndsWith and Contains here关于StartsWithEndsWithContains讨论在这里

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM