【问题标题】:Performing a wildcard search执行通配符搜索
【发布时间】:2022-01-11 10:38:57
【问题描述】:

我搜索了将 LINQ 与 EF 结合使用的地方。当搜索条件为空或为空时,我需要返回所有内容。目前我已经使用 if 条件作为解决方案。然后我转向了这样的解决方案。

data = data
.Where(p => !string.IsNullOrEmpty(searchriteria1)? p.field1.Contains(searchriteria1) : true)
.Where(p => !string.IsNullOrEmpty(searchriteria2)? p.field2.Contains(searchriteria2) : true);

有没有更好的方法来做到这一点?也许使用扩展或任何更好的方法?

【问题讨论】:

  • 标准是什么?这很重要。如果不使用全文搜索索引,这样的搜索将不得不扫描整个表,即使在删除了Where 中的不必要检查之后也是如此。全文搜索只能使用完整的单词和句子,以及它们的各种形式。如果你想通过搜索ta 来检索potato,你需要一些不同的东西。

标签: c# linq search wildcard


【解决方案1】:

您可以以前检查搜索条件字段并以这种方式构建查询:

IQueryable<Foo> data  = context.Foo.AsQueryable();

if(!string.IsNullOrEmpty(searchriteria1))
{
    data = data.Where(p => p.field1.Contains(searchriteria1));
}
if (!string.IsNullOrEmpty(searchriteria2))
{
    data = data.Where(p => p.field2.Contains(searchriteria2));
}

【讨论】:

    【解决方案2】:

    这个问题有两个部分。如何动态过滤,如何高效过滤。

    动态标准

    对于第一个问题,使用 LINQ 时无需进行全面查询。 Catch-all queries result in inefficient execution plans,所以最好避开它们。

    虽然 LINQ 不是 SQL。您可以逐部分构建查询。只有当您尝试枚举最终查询时,它才会被转换为 SQL。这意味着你可以写:

    if(!String.IsNullOrEmpty(searchCriteria1))
    {
        query=query=.Where(p=>p.Field1.Contains(searchCriteria1);
    }
    

    您可以链接多个Where 调用以获得多个AND 条件的等效项。

    要使用例如OR 生成更复杂的查询,您必须构造正确的Expression&lt;Func&lt;...,bool&gt;&gt; 对象,或者使用LINQKit 之类的库来使这个可以忍受。

    效率

    能否编写高效的查询取决于搜索条件。子句field LIKE '%potato%' 不能使用任何索引,最终会扫描整个表。

    另一方面,field LIKE 'potato% 可以利用覆盖field 的索引,因为它将转换为像field &gt;='potato' and field&lt;='potatp 这样的范围搜索。

    如果您想实现自动完成或拼写检查,您通常希望找到与标准差异最小的文本。

    全文搜索

    您可以使用Full-Text Search 索引和FTS 函数(如CONTAINSFREETEXT)有效地搜索单词、单词变体甚至完整短语。

    FTS 类似于 Google 或 ... StackOverflow 搜索单词或句子的方式。

    引用文档:

    CONTAINS 可以搜索:

    • 一个词或短语。
    • 单词或短语的前缀。
    • 一个词靠近另一个词。
    • 从另一个词屈折产生的词(例如,单词 drive 是drives、driven、driving 和driven 的屈折词干)。
    • 使用同义词库作为另一个词的同义词的词(例如,“金属”一词可以有“铝”和“钢”等同义词)。

    另一方面,FREETEXT 更接近 Google/SO 的工作方式,通过搜索整个短语,返回紧密匹配,而不仅仅是精确匹配。

    通过DbFunctions.ContainsDbFunctions.FreeText 函数,EF Core 5 及更高版本中都可以使用 CONTAINS 和 FREETEXT。

    这意味着如果你想搜索一个词或短语,你可以构造一个适当的 FTS 参数并使用:

    var searchCriteria1="' Mountain OR Road '";
    if(!String.IsNullOrEmpty(searchCriteria1))
    {
        query=query=.Where(p=>DbFunctions.Contains(p.Field1.Contains(searchCriteria1));
    }
    

    这比使用 LINQKit 容易得多。

    或搜索ride, ride, ridded with :

    var searchCriteria1="' FORMSOF (INFLECTIONAL, ride) '";
    

    【讨论】:

    • query=query=.Where(p=&gt;DbFunctions.Contains(p.Field1.Contains(,searchCriteria1)); 无法编译
    • 糟糕。删除了多余的逗号
    • 不错,还有 2 个错误需要编译
    【解决方案3】:

    更短的语法

    data.Where(p => (string.IsNullOrEmpty(searchriteria1) || p.field1.Contains(searchriteria1)) 
                 && (string.IsNullOrEmpty(searchriteria2) || p.field2.Contains(searchriteria2)));
    

    【讨论】:

    • Where 内部的检查只会导致错误的查询执行计划。 LINQ 根本不需要它。这是可以投票的临界点,尤其是因为正确的语法已经作为答案发布了
    • Where(x =&gt; true) 不会导致错误的查询执行计划
    • 这不是你写的。整个 表达式 将被翻译成 SQL。您发布的内容将被翻译成类似WHERE @criteria is not null and len(@criteria)&gt;1 and field LIKE @criteria)=1 的内容。即使这被简化为WHEN (@criteria is not null and field LIKE @criteria),你也会有一半的时间得到一个糟糕的执行计划。那是因为第一次调用生成的执行计划,即使不合适,也会被缓存并重用。 problems with catch-all queries 众所周知
    【解决方案4】:
     public static List<Test> getAll(Expression<Func<Test, bool>> filter = null)
        { 
            return filter == null ? context.Set<Test>().ToList() : context.Set<Test>().Where(filter).ToList();
          
        }
    

    如果要过滤

    var l=getAll(p => p.field1.Contains(searchriteria1)&&p.field2.Contains(searchriteria2));
    

    没有过滤器

    var l=getAll();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-10
      • 1970-01-01
      • 2022-12-15
      • 2014-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多