像实体框架中的运算符?

Posted

技术标签:

【中文标题】像实体框架中的运算符?【英文标题】:Like Operator in Entity Framework? 【发布时间】:2010-11-05 04:52:24 【问题描述】:

我们正在尝试在实体框架中为具有字符串字段的实体实现“LIKE”运算符,但似乎不受支持。有没有其他人尝试过这样的事情?

blog post 总结了我们遇到的问题。我们可以使用包含,但这仅匹配 LIKE 的最简单的情况。将 contains、startswith、endswith 和 indexof 组合在一起可以实现,但需要在标准通配符和 Linq to Entities 代码之间进行转换。

【问题讨论】:

如果您已经在使用 EF 6.2.x,请转到 this answer。如果您使用的是 EF Core 2.x,请致 this answer 【参考方案1】:

我对 EF 真的一无所知,但在 LINQ to SQL 中,您通常使用 String.Contains 来表达 LIKE 子句:

where entity.Name.Contains("xyz")

翻译成

WHERE Name LIKE '%xyz%'

(将StartsWithEndsWith 用于其他行为。)

我不完全确定这是否有帮助,因为当您说您尝试实施 LIKE 时,我不明白您的意思。如果我完全误解了,请告诉我,我会删除这个答案:)

【讨论】:

请注意“WHERE Name LIKE '%xyz%'”将无法使用索引,所以如果表很大,它可能不会表现得那么好... 好吧,我们希望能够匹配 blah *blah foobar foo?bar ?foobar?和其他复杂的模式。我们目前的方法与您提到的类似,我们将使用 contains、indexof、startswith、endswith 等将这些查询转换为操作。我只是希望有一个更通用的解决方案。 我不知道 - 我怀疑复杂的模式最终会更加特定于数据库,并且难以以一般方式表达。 @Jon Skeet:据我所知,LIKE 功能在 ANSI 标准中,在 SQL Server、Oracle 和 DB2 中几乎相同。 我看到使用这些运算符和 MS SQL 的一件事是 EF 将它们添加为转义参数 "Name LIKE @p__linq__1 ESCAPE N''~''" 在我非常有限的用例中执行如果搜索字符串只是在查询“名称如'%xyz%'中,则速度要慢得多。对于我所拥有的场景,我仍在使用 StartsWith 和 Contains 但我通过动态 linq 进行,因为这会将参数注入 SQL在我的场景中产生更有效查询的语句。不确定这是否是 EF 4.0 的事情。您也可以使用 ObjectQueryParameters 来实现同样的事情...【参考方案2】:

这是一篇旧帖子,但对于任何正在寻找答案的人,this link 应该会有所帮助。如果您已经在使用 EF 6.2.x,请转至 this answer。如果您使用的是 EF Core 2.x,请致 this answer

短版:

SqlFunctions.PatIndex 方法 - 在所有有效的文本和字符数据类型上返回指定表达式中模式第一次出现的起始位置,如果未找到该模式,则返回零 p>

命名空间:System.Data.Objects.SqlClient 程序集:System.Data.Entity(在 System.Data.Entity.dll 中)

这个forum thread中也出现了一点解释。

【讨论】:

公认的答案是如何链接到 MSDN 论坛的,该论坛将这个问题链接回answer below? 答案是使用 SqlFunctions.PatIndex 方法。链接的论坛主题是为了提供更多“背景”信息。 下面的答案很适合简单的模式,但如果我想说“WHERE Name LIKE 'abc[0-9]%'”或其他更复杂的模式,只需使用 Contains() ' ' t 完全削减它。 这个旧的answer 复制到这个问题。 (不是它的第一部分,而是它的替代解决方案。)【参考方案3】:

我遇到了同样的问题。

目前,我已经解决了基于 http://www.codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes?msg=1423024#xx1423024xx 的客户端通配符/正则表达式过滤 - 它很简单并且可以按预期工作。

我找到了关于这个主题的另一个讨论:http://forums.asp.net/t/1654093.aspx/2/10 如果您使用 Entity Framework >= 4.0,这篇文章看起来很有希望:

使用 SqlFunctions.PatIndex:

http://msdn.microsoft.com/en-us/library/system.data.objects.sqlclient.sqlfunctions.patindex.aspx

像这样:

var q = EFContext.Products.Where(x =>
SqlFunctions.PatIndex("%CD%BLUE%", x.ProductName) > 0);

注意:此解决方案仅适用于 SQL-Server,因为它使用非标准 PATINDEX 函数。

【讨论】:

当 PatIndex “工作”时,它会回来咬你,where 子句中的 PatIndex 不使用您要过滤的列上的索引。 @BlackICE 这是意料之中的。当您搜索内部文本 (%CD%BLUE%) 时,服务器将无法使用索引。只要有可能,从头开始搜索文本 (CD%BLUE%) 会更有效。 @surfen patindex 比这更糟糕,即使前面没有 % 也不会使用索引,使用 patindex 搜索 (BLUE CD%) 不会使用列索引。【参考方案4】:

更新:在 EF 6.2 中有一个 like 运算符

Where(obj => DbFunctions.Like(obj.Column , "%expression%"))

【讨论】:

DbFunctions 是 System.Data.Entity 命名空间的静态类【参考方案5】:

Entity Framework Core 2.0中添加了LIKE运算符:

var query = from e in _context.Employees
                    where EF.Functions.Like(e.Title, "%developer%")
                    select e;

... where e.Title.Contains("developer") ... 相比,它实际上被转换为SQL LIKE 而不是CHARINDEX 我们看到的Contains 方法。

【讨论】:

【参考方案6】:

在文档中特别提到它是 Entity SQL 的一部分。您收到错误消息了吗?

// LIKE and ESCAPE
// If an AdventureWorksEntities.Product contained a Name 
// with the value 'Down_Tube', the following query would find that 
// value.
Select value P.Name FROM AdventureWorksEntities.Product 
    as P where P.Name LIKE 'DownA_%' ESCAPE 'A'

// LIKE
Select value P.Name FROM AdventureWorksEntities.Product 
    as P where P.Name like 'BB%'

http://msdn.microsoft.com/en-us/library/bb399359.aspx

【讨论】:

我很想远离 Entity SQL,以防您将来想离开 EF。谨慎行事并坚持使用原始响应中的 Contains()、StartsWith() 和 EndsWith() 选项。 编译正常,但运行时失败。 我发布的代码在运行时失败?它来自微软的链接。 我编辑了这个问题,其中包含一个博客文章的链接,描述了我们遇到的相同问题。 看起来 Contains() 是你的票。但正如 Jon Skeet 指出的那样,如果 Contains 不能满足您的需求,您可能不得不直接使用一些实际的 SQL 来操作数据库。【参考方案7】:

如果您使用的是 MS Sql,我已经编写了 2 个扩展方法来支持通配符搜索的 % 字符。 (需要 LinqKit)

public static class ExpressionExtension

    public static Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> expr, string likeValue)
    
        var paramExpr = expr.Parameters.First();
        var memExpr = expr.Body;

        if (likeValue == null || likeValue.Contains('%') != true)
        
            Expression<Func<string>> valExpr = () => likeValue;
            var eqExpr = Expression.Equal(memExpr, valExpr.Body);
            return Expression.Lambda<Func<T, bool>>(eqExpr, paramExpr);
        

        if (likeValue.Replace("%", string.Empty).Length == 0)
        
            return PredicateBuilder.True<T>();
        

        likeValue = Regex.Replace(likeValue, "%+", "%");

        if (likeValue.Length > 2 && likeValue.Substring(1, likeValue.Length - 2).Contains('%'))
        
            likeValue = likeValue.Replace("[", "[[]").Replace("_", "[_]");
            Expression<Func<string>> valExpr = () => likeValue;
            var patExpr = Expression.Call(typeof(SqlFunctions).GetMethod("PatIndex",
                new[]  typeof(string), typeof(string) ), valExpr.Body, memExpr);
            var neExpr = Expression.NotEqual(patExpr, Expression.Convert(Expression.Constant(0), typeof(int?)));
            return Expression.Lambda<Func<T, bool>>(neExpr, paramExpr);
        

        if (likeValue.StartsWith("%"))
        
            if (likeValue.EndsWith("%") == true)
            
                likeValue = likeValue.Substring(1, likeValue.Length - 2);
                Expression<Func<string>> valExpr = () => likeValue;
                var containsExpr = Expression.Call(memExpr, typeof(String).GetMethod("Contains",
                    new[]  typeof(string) ), valExpr.Body);
                return Expression.Lambda<Func<T, bool>>(containsExpr, paramExpr);
            
            else
            
                likeValue = likeValue.Substring(1);
                Expression<Func<string>> valExpr = () => likeValue;
                var endsExpr = Expression.Call(memExpr, typeof(String).GetMethod("EndsWith",
                    new[]  typeof(string) ), valExpr.Body);
                return Expression.Lambda<Func<T, bool>>(endsExpr, paramExpr);
            
        
        else
        
            likeValue = likeValue.Remove(likeValue.Length - 1);
            Expression<Func<string>> valExpr = () => likeValue;
            var startsExpr = Expression.Call(memExpr, typeof(String).GetMethod("StartsWith",
                new[]  typeof(string) ), valExpr.Body);
            return Expression.Lambda<Func<T, bool>>(startsExpr, paramExpr);
        
    

    public static Expression<Func<T, bool>> AndLike<T>(this Expression<Func<T, bool>> predicate, Expression<Func<T, string>> expr, string likeValue)
    
        var andPredicate = Like(expr, likeValue);
        if (andPredicate != null)
        
            predicate = predicate.And(andPredicate.Expand());
        
        return predicate;
    

    public static Expression<Func<T, bool>> OrLike<T>(this Expression<Func<T, bool>> predicate, Expression<Func<T, string>> expr, string likeValue)
    
        var orPredicate = Like(expr, likeValue);
        if (orPredicate != null)
        
            predicate = predicate.Or(orPredicate.Expand());
        
        return predicate;
    

用法

var orPredicate = PredicateBuilder.False<People>();
orPredicate = orPredicate.OrLike(per => per.Name, "He%llo%");
orPredicate = orPredicate.OrLike(per => per.Name, "%Hi%");

var predicate = PredicateBuilder.True<People>();
predicate = predicate.And(orPredicate.Expand());
predicate = predicate.AndLike(per => per.Status, "%Active");

var list = dbContext.Set<People>().Where(predicate.Expand()).ToList();    

在 ef6 中,它应该转换为

....
from People per
where (
    patindex(@p__linq__0, per.Name) <> 0
    or per.Name like @p__linq__1 escape '~'
) and per.Status like @p__linq__2 escape '~'

',@p__linq__0 = '%He%llo%',@p__linq__1 = '%Hi%',@p__linq_2 = '%Active'

【讨论】:

【参考方案8】:

对于 EfCore,这里是构建 LIKE 表达式的示例

protected override Expression<Func<YourEntiry, bool>> BuildLikeExpression(string searchText)
    
        var likeSearch = $"%searchText%";

        return t => EF.Functions.Like(t.Code, likeSearch)
                    || EF.Functions.Like(t.FirstName, likeSearch)
                    || EF.Functions.Like(t.LastName, likeSearch);
    

//Calling method

var query = dbContext.Set<YourEntity>().Where(BuildLikeExpression("Text"));

【讨论】:

【参考方案9】:

你可以很容易地在 Link to Entities 中使用真实的like

添加

    <Function Name="String_Like" ReturnType="Edm.Boolean">
      <Parameter Name="searchingIn" Type="Edm.String" />
      <Parameter Name="lookingFor" Type="Edm.String" />
      <DefiningExpression>
        searchingIn LIKE lookingFor
      </DefiningExpression>
    </Function>

到你的 EDMX 在这个标签中:

edmx:Edmx/edmx:Runtime/edmx:ConceptualModels/Schema

还要记住&lt;schema namespace="" /&gt; 属性中的命名空间

然后在上面的命名空间中添加一个扩展类:

public static class Extensions

    [EdmFunction("DocTrails3.Net.Database.Models", "String_Like")]
    public static Boolean Like(this String searchingIn, String lookingFor)
    
        throw new Exception("Not implemented");
    

此扩展方法现在将映射到 EDMX 函数。

更多信息在这里:http://jendaperl.blogspot.be/2011/02/like-in-linq-to-entities.html

【讨论】:

以上是关于像实体框架中的运算符?的主要内容,如果未能解决你的问题,请参考以下文章

在带有 Linq 的实体框架中使用 LIKE "%%%"? [复制]

实体框架核心 - 包含区分大小写还是不区分大小写?

实体框架使用 PatIndex 生成的查询不起作用

Expression.Invoke 在实体框架中?

实体框架和类似[重复]的地方

实体框架 4 Single() vs First() vs FirstOrDefault()