【问题标题】:EF Core cannot achieve OR translation dynamically by IQueryable.WhereEF Core 无法通过 IQueryable.Where 动态实现 OR 转换
【发布时间】:2020-10-15 21:36:00
【问题描述】:
using (var ctx = new StockContext())
{
    var syms = new string[] { "foo", "bar" };

    return ctx.Assets.Select(x => x.Symbol)
    .Where(symbol => syms.Any(sym => symbol.Contains(sym)))
    .ToArray();  // EXCEPTION: InvalidOperationException: The LINQ expression 'DbSet<Asset>() .Where(a => __syms_0 .Any(sym => a.Symbol.Contains(sym)))' could not be translated. 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(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
}
using (var ctx = new StockContext())
{
    var syms = new string[] { "foo", "bar" };

    return ctx.Assets.Select(x => x.Symbol)
    .Where(symbol => s.Contains(syms[0]))
    .ToArray();  // WORKS FINE
}

请查看上面的代码。我认为这是一种很常见的做法,但我什至无法使第一个示例有效。

我想要实现的是第一个示例中的代码可以翻译成: SELECT Symbol FROM Assets WHERE Symbol LIKE '%foo%' OR Symbol LIKE '%bar%'。怎么会异常运行?

附言我知道 PredicateBuilder 可以解决这个问题。但是,我可以通过原生 EF 代码实现如此简单的操作吗?提前致谢!


    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.0-preview.4.20220.10" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.0-preview.4.20220.10" />

【问题讨论】:

  • .Where(symbol =&gt; s.Contains(syms[0]) || s.Contains(syms[1])) 是一个选项吗?
  • @mjwills 是的,您的解决方案适用于静态尺寸IEnumerable
  • @mannok 在 LINQ to Entities 或 Line to Objects 中都不是OR。这是一个带有ContainsAny 操作。在 C# 或 SQL 中,它不会被视为多个 ORs。
  • 您确定不应该改用全文搜索吗? ORM 旨在将对象映射到关系结果,而不是充当 SQL 的替代品。 LIKE '%abc%' 操作的开销非常,因为它不能使用任何索引来查找其他字符串中间的字符串。它必须扫描整个表。这就是 FTS 在各种数据库中提供的内容
  • @PanagiotisKanavos 我们应该考虑搜索的成本。不过,在此之前,我想我们应该知道为什么EF不能先编译这么常见的操作了。

标签: c# entity-framework .net-core orm ef-core-3.0


【解决方案1】:

最终,编写表达式树解析器困难,有时您需要手动展开它来帮助它。考虑:

var syms = new string[] { "foo", "bar" };

var p = Expression.Parameter(typeof(Asset), "x");
var symbol = Expression.PropertyOrField(p, nameof(Asset.Symbol));
Expression body = null;
var contains = typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) });
foreach (var sym in syms)
{
    var test = Expression.Call(symbol, contains, Expression.Constant(sym, typeof(string)));
    if (body == null) // first
    {
        body = test;
    }
    else
    {
        body = Expression.OrElse(body, test);
    }
}
var lambda = Expression.Lambda<Func<Asset, bool>>(body, p);
var results = ctx.Assets.Where(lambda).Select(x => x.Symbol).ToArray();

它的作用是根据输入手动构建表达式树;它相当于:

x => (x.Symbol.Contains("foo") || x.Symbol.Contains("bar"))

在这种情况下,但应该扩展到任意数量的输入。

【讨论】:

  • 感谢您的努力。我需要一些时间来消化它!
  • 在 SQL 中使用 FTS 并使用 Dapper 映射结果可能是更好的选择 - 它肯定更快
【解决方案2】:

请测试一下...

 var syms = new string[] { "foo", "bar" };

 return ctx.Assets.Select(x => x.Symbol)
    .Where(symbol => syms.Any(x => EF.Functions.Like(symbol, x)))
    .ToArray(); 
罢工>

            var syms = new string[] { "foo", "bar" };

            var select = ctx.Assets.Select(x => x.Symbol);

            foreach (var item in syms)
                select = select.Where(x => EF.Functions.Like(x, item));

            var result = select.ToArray();

【讨论】:

  • 我以前试过这个但没有运气:( ctx.Assets.Select(x =&gt; x.Symbol).Where(symbol =&gt; syms.Any(sym =&gt; EF.Functions.Like(symbol, sym))).ToArray(); throw InvalidOperationException: The LINQ expression 'DbSet&lt;Asset&gt;() .Where(a =&gt; __syms_0 .Any(sym =&gt; __Functions_1 .Like( matchExpression: a.Symbol, pattern: sym)))' could not be translated. 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().
  • 嗯,我还有一个想法……给我一分钟
  • 感谢您的努力。似乎问题来自syms.Any() in IQueryable().Where()
  • hmmm ...我认为我的最后一次编辑将不起作用...它将是 AND 而不是 OR...我认为 @Marc_Gravell 是正确的。
  • 似乎更新没有达到我们想要的效果。您正在链接 Where 子句,换句话说,它是 AND 操作而不是 OR。您的代码可以用 SQL 表示,例如 SELECT Symbol FROM Assets WHERE Symbol LIKE 'foo' AND Symbol Like 'bar'
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-22
  • 1970-01-01
  • 1970-01-01
  • 2020-07-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-16
相关资源
最近更新 更多