【问题标题】:Write a search query which is case insensitive in EF Core?在 EF Core 中编写一个不区分大小写的搜索查询?
【发布时间】:2020-03-24 12:44:05
【问题描述】:

我想问一个关于 SQL Server 和 EF Core 的问题。数据库中的排序规则是Latin1_CI_AS,我想编写一个包含土耳其字符的搜索查询。

在数据库中,'personnel' 表中有一条名为“SELİM”的记录。当我在 EF Core 中编写这样的查询时:

    public async Task<IList<PersonnelGetDto>> Get(PersonnelGetPayload payload)
        {
           if (payload.Name != null)
                query = query.Where(x => x.Name.Contains(payload.Name)); 
        }

如果我的搜索条件是“selim”,则列表为空。

我没有机会将数据库中的排序规则更改为土耳其语,因为我们的应用程序是多语言的。我认为其他语言会有问题。还是我错了?

我还写了字符串扩展。但是,在将 LINQ 查询转换为 SQL 时,所有记录都会到达服务层,因为 LIKE 运算符没有分配 WHERE 子句。在 sql 端运行这个条件非常重要。如果我把所有的数据集都拿到服务层去查询,那会花费我很多。

当我像这样在数据库中键入查询时,我可以解决问题:

SELECT * FROM Personnel WHERE Name LIKE 'selim' COLLATE Turkish_CI_AS

我想如果我可以在 EF Core 上操作整理,我会解决这个问题。

【问题讨论】:

  • 不,不是这个。因为 İ 和 i 是不同的字母。我尝试了这个解决方案,但没有奏效。
  • 是否使用indexOf 选项? query = query.Where(x =&gt; x.Name.IndexOf(payload.Name, StringComparison.OrdinalIgnoreCase));
  • 当我实现这个时,我得到了这个错误:“无法将类型'int'隐式转换为'bool'”和“无法将lambda表达式转换为预期的委托类型,因为块中的一些返回类型不能隐式转换为委托返回类型”
  • ...Where(x =&gt;x.Name.IndexOf(payload.Name, StringComparison.OrdinalIgnoreCase) &gt; -1)

标签: c# sql-server .net-core entity-framework-core case-insensitive


【解决方案1】:

我已经测试了类似的功能,但结果并不如操作所述正确。所以只剩下一个选项了。即创建一个拦截器并实现自定义逻辑。我创建了一个如下示例:

   public class Suffixes
    {
        public const string Collate = "--Collate";
    }

    public class CollationDbCommandInterceptor : DbCommandInterceptor
    {
        private const string CollateSyntax = " collate turkish_ci_as";

        public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
        {
            var args = command.Parameters.OfType<DbParameter>()
                           .Where(t => t.DbType == DbType.String && t.Value.ToString().EndsWith(Suffixes.Collate)).ToList();
            if (args.Count <= 0)
                return base.ReaderExecuting(command, eventData, result);

            foreach (var parameter in args)
            {
                parameter.Value = parameter.Value.ToString().Replace(Suffixes.Collate, "");
                var equality = $"= {parameter.ParameterName}";

                var ixs = AllIndexesOf(command.CommandText, equality);

#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
                foreach (var eq in ixs)
                {
                    command.CommandText = command.CommandText.Insert(eq+equality.Length,CollateSyntax);

                }
#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities

            }



            return base.ReaderExecuting(command, eventData, result);
        }

        private static IEnumerable<int> AllIndexesOf(string str, string value)
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentException("the string to find may not be empty", nameof(value));
            var indexes = new List<int>();
            for (var index = 0; ; index += value.Length)
            {
                index = str.IndexOf(value, index);
                if (index == -1)
                    return indexes;
                indexes.Insert(0,index);
            }
        }
    }

配置:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
               ....  
                optionsBuilder.AddInterceptors(new CollationDbCommandInterceptor());
                ....
            }
        }

用法:

var kadayif = $"kadayıf{Suffixes.Collate}";
var william = $"Wİlliam{Suffixes.Collate}";            
var auths = ctx.Authors.Where(t =>   t.FirstName == william ||t.LastName == kadayif).ToList(); 
// returns William Shakespeare and Abuzer Kadayıf

逻辑是创建一个拦截器,在查询中传递的sql参数中寻找特定的后缀。将查询特定的排序规则注入到最终的 sql 命令文本中。我试图涵盖一些高级场景,例如参数重用。它可能需要更多改进。

请注意,此示例适用于 Entity Framework Core 3.0,即拦截器引入的版本。早期 ef 核心版本中的拦截有点技巧。您可以参考this链接了解更多信息。

【讨论】:

  • 非常感谢!!这对我有很大帮助:) 项目基于 Core 2.1,但 that like 在这种情况下对我有帮助
【解决方案2】:

如果您还没有添加using Microsoft.EntityFrameworkCore;,您需要添加EF.Functions.Like。然后,您的查询将类似于:

query.Where(x => EF.Functions.Like(x.Name, $"%{payload.Name}%"))

这直接转换为生成的 SQL 语句中的 LIKE 运算符。它并非适用于所有 DBMS,但只要您添加了 Microsoft.EntityFrameworkCore.SqlServer,就可以开始使用(假设您的问题被正确标记)。

【讨论】:

  • 您好 Tieson,感谢您的关注。在实施您的解决方案时,我启动了 sql profiler 并观察了对数据库的查询。但我已经看到查询中不包含“喜欢”。在 sql 端运行这个条件非常重要。如果我把所有的数据集都拿到服务层去查询,那会花费我很多。
  • @SametÖz 只要您没有枚举您的值,这转换为生成的 SQL 中的 LIKE 子句。您的问题不足以显示 query 来自何处或它是什么,因此我无法真正说出运行分析器时发生了什么。如果您正在尝试构建一个复杂的查询,您可能想要查看 LinqKit,特别是 PredicateBuilder
  • 让我为此添加另一个方面,让我们也为列添加排序规则:query.Where(x =&gt; EF.Functions.Like(EF.Functions.Collate(x.Name,"Turkish_CI_AS"), $"%{payload.Name}%"))
猜你喜欢
  • 1970-01-01
  • 2019-03-15
  • 1970-01-01
  • 2011-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 1970-01-01
相关资源
最近更新 更多