如何使 LINQ 执行(SQL)LIKE 范围搜索

Posted

技术标签:

【中文标题】如何使 LINQ 执行(SQL)LIKE 范围搜索【英文标题】:How to make LINQ execute a (SQL) LIKE range search 【发布时间】:2018-04-07 17:53:26 【问题描述】:

我非常需要帮助,我已经尝试了一段时间了。

所以我有这个查询:

Select name from BlaBlaBla

order by 

case when name like '9%' then 1 end,
case when name like '8%' then 1 end,
case when name like '7%' then 1 end,
case when name like '6%' then 1 end,
case when name like '5%' then 1 end,
case when name like '4%' then 1 end,
case when name like '3%' then 1 end,
case when name like '2%' then 1 end,
case when name like '1%' then 1 end,
case when name like '0%' then 1 end,

name

我想在我的解决方案中的一个新的 C#、Asp.Net 类中实现它到域项目,所以它将是一个 OrderType 过滤器,用于某些功能......

现在我有这个:

var param = Expression.Parameter(typeof(T), "item");

var paramName = Expression.Property(param, "Name");
var regexMatch = Expression.Constant("^[0-9]");
var startsWithDigit = Expression.Call(typeof(Regex), "IsMatch", 
                                             null, paramName);

var lambda = Expression.Lambda<Func<T, bool>>(startsWithDigit, 
                                              param);

return namesList.OrderBy(lambda)
           .ThenBy(BlaBla1())
           .ThenByDescending(BlaBla2())
           .ThenByDescending(BlaBla3())
           .ThenBy(BlaBla4());

但它告诉我,Expression 不包含“IsMatch”方法。

你能帮帮我吗? 谢谢!!!

【问题讨论】:

看起来您正在使用一些支持 LINQ 的 ORM。哪一个?另外,为什么要手动构造表达式而不是使用 LINQ 语法? 您希望在数据库中使用一些 ORM 还是在内存中执行此排序? 嗯,这里使用的ORM,是Linq to Sql 问题是:我有一个名称列表,其中一些以数字开头,我想在从 0 到 9 排序的列表末尾过滤它们(以数字开头的那些) ...而且我希望所有这些都发生在 Linq 中,ORM 使用 ENTITY 或 ADO(没关系)。 【参考方案1】:

这里的问题是包含Regex 的表达式无法转换为SQL,因此即使您成功构建了正确的表达式,也无法在LINQ to SQL 后端使用它。但是,SQL 的 LIKE 方法也支持范围通配符,例如 [0-9],因此诀窍是让您的 LINQ 转换为包含 LIKE 语句的 SQL。

LINQ-to-SQL 提供了使用 SQL LIKE 语句的可能性显式

return namesList.OrderBy(r => SqlMethods.Like(r.Name, "[0-9]%")) ...

这个SqlMethods 类只能在 LINQ-to-SQL 中使用。在实体框架中有字符串函数可以转换为LIKE隐式,但它们都没有启用范围通配符([x-y])。在 EF 中,类似 ...

的语句
return namesList.OrderBy(r => r.Name.StartsWith("[0-9]")) ...

... 会变成废话:

[Name] LIKE '~[0-9]%' ESCAPE '~'

即它徒劳地寻找以文字字符串“[0-9]”开头的名称。所以只要你继续使用 LINQ-to-SQL SqlMethods.Like 就是要走的路。

在 Entity Framework 6.1.3(及更低版本)中,我们必须使用稍微不同的方式来获得相同的结果...

return namesList.OrderBy(r => SqlFunctions.PatIndex("[0-9]%", c.Name) == 1) ...

...因为SqlFunctions 中的PatIndex 也支持范围模式匹配。

但在实体框架 6.2 中,由于新的 DbFunctions.Like 函数,我们回到了使用 LINQ-to-SQL 的轨道上:

return namesList.OrderBy(r => DbFunctions.Like(r.Name, "[0-9]%")) ...

最后,Entity Framework 核心也有一个Like 函数:

return namesList.OrderBy(r => EF.Functions.Like(r.Name, "[0-9]%")) ...

【讨论】:

谢谢@Gert。这确实帮助我解决了我的问题,并且现在正在运行 感谢您提供完整而完整的答案。 ef版本之类的细节对我帮助很大。 在 EF 6.4.4 中使用 SqlMethods.Like -> LINQ to Entities 无法识别方法 'Boolean Like(System.String, System.String)' @Omid-RH 这个 SqlMethods 类只能在 LINQ-to-SQL 中使用 感谢@Gert-Arnold【参考方案2】:

您可以在下面看到这种处理订单情况的示例。

    static void Main(string[] args)
    
        List<Obvious> list = new List<Obvious>();
        for (int i = 0; i < 100; i++)
        
            list.Add(new Obvious(i.ToString(), i));
        

        string name = list[30].name;
        switch (name)
        
            case "9":
                list.OrderBy(o => o.perc)
                    .ThenByDescending(o => o.name);
                break;
            default:
                list.OrderByDescending(o => o.name)
                    .ThenBy(o => o.perc);
                break;
        
    

    public class Obvious
    
        public string name  get; set; 
        public int perc  get; set; 
        public Obvious(string _name, int _perc)
        
            this.name = _name;
            this.perc = _perc;
        

    

【讨论】:

我认为你没有理解这个问题。他想构建一个可以发送到支持 LINQ 的 ORM 的表达式。或者至少,看起来是这样。 你好。很抱歉这么晚才回答。是的,这正是我想要的!因为,这正是其他人的制作方式。我希望以数字开头的名称位于列表的末尾,按 asc 排序 是的,ORM 是 LINQ “你到底想要什么”?我的解决方案还是@TsahiAsher 建议的解决方案? 好吧,@Gert,刚刚为我回答了我的问题,我几乎完成了我的语法(算法)。谢谢@Kadir!【参考方案3】:

如果我是你,我不会尝试使用表达式来解决这个问题,因为它会带来很多复杂性。

我看到您希望有一个通用方法,以便它可以与不同的域实体一起使用,但您希望每个实体都有一个 Name 属性。 您可以通过定义包含Name 属性的接口以更简单的方式解决此问题。像这样:

    public static void Main()
    
        var test = new List<YourDomainEntity>()
        
            new YourDomainEntity()  Name = "1test", OtherProperty = "1",
            new YourDomainEntity()  Name = "2test",  OtherProperty = "2" ,
            new YourDomainEntity()  Name = "2test", OtherProperty = "1"   
        ;

        var k = Foo(test).ToList();
    

    public interface INameOrderable
    
        string Name  get; set; 
    

    public interface IOtherPropertyOrderable
    
        string OtherProperty  get; set; 
    

    public static IEnumerable<T> Foo<T>(IEnumerable<T> list) where T : INameOrderable, IOtherPropertyOrderable
    
        return list.OrderBy(a => a.Name, new NamesDescComparer()).ThenBy(b => b.OtherProperty);
    

    public class NamesDescComparer : IComparer<string>
    
        public int Compare(string x, string y) => -String.CompareOrdinal(x, y);
    

    class YourDomainEntity : INameOrderable, IOtherPropertyOrderable
    
        public string OtherProperty  get; set; 
        public string Name  get; set; 
    

我相信Foo 方法正是您要找的。​​p>

注意where T : INameOrderable 部分。它将此方法的使用限制为实现INameOrderable 接口的实体

【讨论】:

以上是关于如何使 LINQ 执行(SQL)LIKE 范围搜索的主要内容,如果未能解决你的问题,请参考以下文章

MySql 模糊查询范围查询

如何提高sql2000全文检索搜索效率

改进 sql 中的文本搜索

LINQ中的SQL Like子句[重复]

Linq 中的 LIKE 和 OR 等内容

使 SQL Server 中的正则表达式搜索更高效