【问题标题】:Check a column with Contain list in EF Core 3.1在 EF Core 3.1 中检查包含列表的列
【发布时间】:2021-04-08 13:21:39
【问题描述】:

我使用 .Net Core 和 EF Core 3.1

我有一个类似 Under 的表达式,但运行时抛出此异常

无法翻译。要么重写查询的形式,可以 被翻译,或通过插入显式切换到客户端评估 调用 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync()。见https://go.microsoft.com/fwlink/?linkid=2101038 更多信息。

我的代码是

var years = new List<string> { "1390","1391" };
queryable = queryable.Where(x =>
     x.ProdYears != null &&
     years.Any(z => x.ProdYears.Contains(z))
 );

为什么这段代码不起作用以及如何解决这个问题?

【问题讨论】:

  • 您查看过错误消息中链接的页面吗?我认为这可以解释你的问题。
  • 是的,我检查过但没用,它解释了我不想使用它的“客户评估”
  • 您正在使用客户端评估(这里的客户端是您的应用程序,服务器是数据库)。例如,EF Core 会将您的查询转换为 sql。如果它无法将您的查询转换为 SQL,它将引发这样的错误。你要么需要重构一些更容易转换的东西,要么检查内存。
  • “客户评估”的意思是从数据库中获取所有记录然后对其进行过滤,所以性能很差,我不想使用它
  • 没错,最好是重写查询,以便将其转换为 sql。

标签: c# database linq entity-framework-core


【解决方案1】:

您可以使用我在以下位置找到并修改的扩展方法从列表中返回所有匹配项:Reference

这不是通用的,因此您必须将“ContainTest”更改为您的实体类型。

    public static class EntityHelper
    {
        public static IQueryable<ContainTest> SqlLikeInList(this IQueryable<ContainTest> products, List<string> containsList)
        {
            if (!containsList.Any()) return null;

            var patterns = containsList.Select(t => $"%{t}%").ToList();

            ParameterExpression parameter = Expression.Parameter(typeof(ContainTest));
            Expression body = patterns.Select(word => Expression.Call(typeof(DbFunctionsExtensions).GetMethod(nameof(DbFunctionsExtensions.Like),
                                                                      new[]{typeof(DbFunctions), typeof(string), typeof(string)}),
                                                                      Expression.Constant(EF.Functions),
                                                                      Expression.Property(parameter, typeof(ContainTest).GetProperty(nameof(ContainTest.ProdYears))),
                                                                      Expression.Constant(word)))
                                      .Aggregate<MethodCallExpression, Expression>(null, (current, call) => current != null ? Expression.OrElse(current, call) : (Expression)call);

            return products.Where(Expression.Lambda<Func<ContainTest, bool>>(body, parameter));
        }

然后像这样从您的 DBSet 中调用它:

var years = new List<string> { "1390", "1391" };
IQueryable<ContainTest> queryable = context.ContainTests.SqlLikeInList(years);

通过测试,我发现如果您正在寻找“1390”或“1391”的完全匹配,所有这些都可以避免

years.Any(z => x.ProdYears == z)

...所以是 .contains 语句迫使您使用客户端评估。 但是,使用此扩展方法,您可以使用单个调用进行服务器端评估。

使用 SQL Server Profiler,我的示例生成了这个 SQL

SELECT [c].[ContainTestId], [c].[ProdYears], [c].[RequiredContentColumn]
FROM [ContainTests] AS [c]
WHERE ([c].[ProdYears] LIKE N'%1390%') OR ([c].[ProdYears] LIKE N'%1391%')

作为参考,我创建了一个实体来重新创建您的场景(这是 SQL 引用的内容)

    public class ContainTest
    {
        public long ContainTestId { get; set; }
        public string? ProdYears { get; set; }
        public string RequiredContentColumn { get; set; }
    }

我用这个示例数据填充了表格:

SET IDENTITY_INSERT [dbo].[ContainTests] ON
INSERT INTO [dbo].[ContainTests] ([ContainTestId], [ProdYears], [RequiredContentColumn]) VALUES (1, N'aa1389zz', N'dont get me')
INSERT INTO [dbo].[ContainTests] ([ContainTestId], [ProdYears], [RequiredContentColumn]) VALUES (2, N'zz1390aa', N'get me')
INSERT INTO [dbo].[ContainTests] ([ContainTestId], [ProdYears], [RequiredContentColumn]) VALUES (3, N'aa1391zz', N'get me too')
INSERT INTO [dbo].[ContainTests] ([ContainTestId], [ProdYears], [RequiredContentColumn]) VALUES (4, NULL, N'dont get me')
SET IDENTITY_INSERT [dbo].[ContainTests] OFF

编码愉快!!!

【讨论】:

    【解决方案2】:

    如果您愿意使用LINQKit,您可以创建扩展方法来处理您的查询:

    public static class LinqKitExt { // using LINQKit
        // searchTerms - IEnumerable<TSearch> where one must match for a row
        // testFne(row,searchTerm) - test one of searchTerms against a row
        // r => searchTerms.All(s => testFne(r,s))
        public static Expression<Func<T, bool>> AnyIs<T, TSearch>(this IEnumerable<TSearch> searchTerms, Expression<Func<T, TSearch, bool>> testFne) {
            var pred = PredicateBuilder.New<T>();
            foreach (var s in searchTerms)
                pred = pred.Or(r => testFne.Invoke(r, s));
        
            return pred;
        }
    
        // searchTerms - IEnumerable<TSearch> where one must match for a row
        // testFne(row,searchTerm) - test one of searchTerms against a row
        // dbq.Where(r => searchTerms.Any(s => testFne(r,s)))
        public static IQueryable<T> WhereAny<T,TSearch>(this IQueryable<T> dbq, IEnumerable<TSearch> searchTerms, Expression<Func<T, TSearch, bool>> testFne) =>
            dbq.AsExpandable().Where(searchTerms.AnyIs(testFne));
        public static IQueryable<T> WhereAny<T,TSearch>(this IQueryable<T> dbq, Expression<Func<T, TSearch, bool>> testFne, IEnumerable<TSearch> searchTerms) =>
            dbq.AsExpandable().Where(searchTerms.AnyIs(testFne));
        public static IQueryable<T> WhereAny<T,TSearch>(this IQueryable<T> dbq, Expression<Func<T, TSearch, bool>> testFne, params TSearch[] searchTerms) =>
            dbq.AsExpandable().Where(searchTerms.AnyIs(testFne));
    }
    

    然后你可以使用方法:

    var years = new List<string> { "1390","1391" };
    queryable = queryable.WhereAny(years, (qr, y) => qr.ProdYears.Contains(y));
    

    注意:您不需要针对 null 进行测试,因为 SQL 会自动处理它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-10
      • 2021-01-20
      • 2022-02-27
      相关资源
      最近更新 更多