在运行时修改LINQ查询

3

问题陈述

假设我有一个查询,用于搜索人名:

var result = (from person in container.people select person)
             .Where(p => p.Name.Contains(some_criterion)

这将被翻译为一个包含以下like子句的SQL查询:
WHERE NAME LIKE '%some_criterion%'

这会对性能产生一些影响,因为数据库无法有效地使用名称列上的索引(如果我没有记错的话,索引扫描与索引搜索相对应)。

为了解决这个问题,我可以决定仅使用StartsWith(),生成一个带有like子句的查询,如下:

WHERE NAME LIKE 'some_criterion%'

这允许SQL服务器使用索引查找并在一些功能的代价下实现更好的性能。

我希望能够为用户提供选择:将行为设定为使用StartsWith作为默认值,但如果用户想要使用Contains()进行搜索并获得“增加的灵活性”,则应该使用它。

我已经尝试过什么

我认为这很简单,就去实现了一个string的扩展方法。但是,LINQ不接受此方法,并会引发异常。

当然,现在我可以使用if或switch语句并为每种情况创建一个查询,但我更愿意“在更高级别”或更通用地解决这个问题。简而言之:由于实际应用程序的复杂性,使用if语句区分用例是不可行的。这将导致很多重复和混乱。我真的很想能够以某种方式封装各种行为(Contains、StartsWith、EndsWith)。

问题

我应该在哪里寻找或者应该寻找什么?这是否需要通过IQueryables进行组合?我很困惑!


标准。criteria的单数形式。 - Oxymoron
那应该是“标准”。 - Grant Thomas
啊哈,好的。我不知道。我把适用于我的语言的过度规则应用到了英语上 ;) - Oxymoron
我认为你正在寻找在运行时生成lambda表达式。我必须说,这需要更多的工作、更多的测试等。请参考这个相关的问题:https://dev59.com/NXE95IYBdhLWcg3wd9xK - TalentTuner
1
那么这些评论就值得一些东西了。(: - Grant Thomas
@Oxymoron 看看我的更新,我演示了如何根据你的更新封装字符串函数(我真的很想以某种方式封装不同的行为(包含、以...开头、以...结尾))。 - Doctor Jones
3个回答

8
不要过于复杂化,怎么样只使用 if 语句呢?
var query = from person in container.people 
            select person;

if (userWantsStartsWith)
{
    query = from p in query
            where p.Name.Contains(some_criterion)
            select p;
}
else
{
    query = from p in query
            where p.Name.StartsWith(some_criterion)
            select p;
}

更新

如果您需要更复杂的内容,可以尝试查看LinqKit。它可以让您完成以下操作。

var stringFunction = Lambda.Expression((string s1, string s2) => s1.Contains(s2));

if (userWantsStartsWith)
{
    stringFunction = Lambda.Expression((string s1, string s2) => s1.StartsWith(s2));
}

var query = from p in container.people.AsExpandable()
            where stringFunction.Invoke(p.Name, some_criterion)
            select p;

我相信这符合您的要求

我真的很想以某种方式封装不同的行为(包含、以…开始、以…结束)。


1
哇,我也正想写同样的话...是啊,我同意 - 保持简单。 - Dimitar Dimitrov
实际的查询非常庞大,并与其他几个联合在一起。正如我的问题所述,这并不是我正在寻找的解决方案。 - Oxymoron
你的评论说你不想为每种情况构建新查询。但实际上并不是这样,它是基于你的条件构建查询。你不需要全新的查询,只需根据条件语句添加 where 过滤器。比使用 lambda 表达式要简单得多。 - Doctor Jones
我目前正在研究一种可能的解决方案,与OP中陈述的不太相同,而是通过扩展IQueryable和Expression.Lambda来实现。它还没有完全完成并且存在一些问题,但已经达到了我所需的目的。 - Oxymoron
问题已经解决,合并多个谓词时遇到了一些麻烦,但幸好通过这篇文章 http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx 也解决了 :) - Oxymoron
显示剩余4条评论

2
你可以在枚举查询之前动态地修改查询。
var query = container.people.AsQueryable();

if (contains)
{
    query = query.Where(p => p.Name.Contains(filter));
}
else
{
    query = query.Where(p => p.Name.StartsWith(filter));
}

0

试试这个:

var result = (from person in container.people select person)
                .Where(p => some_bool_variable ? p.Name.Contains(some_criterium) : p.Name.StartsWith(some_criterium));

实际查询非常庞大,并与其他几个联合。正如我的问题所述,这不是我正在寻找的解决方案

由于您的查询很大:您是否可以定义处理所有内容的存储过程,并使用特定的查询参数调用它(可能有几个由主函数调用的存储过程,例如一个按名称搜索,另一个按年龄搜索,具有不同的排序顺序等,以保持代码清晰)?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接