Подобається оператору в Entity Framework?


92

Ми намагаємось реалізувати оператор "LIKE" в Entity Framework для наших сутностей із рядковими полями, але, схоже, він не підтримується. Хтось ще намагався зробити щось подібне?

Цей допис у блозі підсумовує проблему, яку ми маємо. Ми могли б використовувати вміст, але це відповідає лише найбільш тривіальному випадку для LIKE. Поєднання містить, startwith, endwith та indexof потрапляє туди, але вимагає перекладу між стандартними символами підстановки та кодом Linq в Entities.


1
Перейдіть до цієї відповіді, якщо ви вже використовуєте EF 6.2.x. На цю відповідь, якщо ви використовуєте EF Core 2.x
CodeNotFound

Відповіді:


36

Зараз це стара публікація, але для тих, хто шукає відповідь, це посилання має допомогти. Перейдіть до цієї відповіді, якщо ви вже використовуєте EF 6.2.x. На цю відповідь, якщо ви використовуєте EF Core 2.x

Коротка версія:

Метод SqlFunctions.PatIndex - повертає початкову позицію першого входження шаблону у вказаному виразі або нулі, якщо шаблон не знайдено, для всіх дійсних типів тексту та символів

Простір імен: System.Data.Objects.SqlClient Assembly: System.Data.Entity (у System.Data.Entity.dll)

У цій темі форуму також є трохи пояснень .


59
як прийнята відповідь та, яка посилається на форум MSDN, який посилається назад на це питання, на відповідь нижче ?
Eonasdan,

Відповідь полягала у використанні методу SqlFunctions.PatIndex. Пов’язана тема форуму мала надати трохи більше «фонової» інформації.
Янн Дюран,

Відповідь нижче приємний для простих зразків, але якщо я хочу сказати "ДЕ Ім'я, як" abc [0-9]% '"або інший більш складний шаблон, просто використання Contains () не зовсім вирізає це.
HotN

1
Повтор цієї давньої відповіді на це питання. (Не першої частини, а альтернативного рішення.)
Фредерік,

154

Я насправді нічого не знаю про EF, але в LINQ to SQL ви зазвичай висловлюєте речення LIKE, використовуючи String.Contens:

where entity.Name.Contains("xyz")

перекладає на

WHERE Name LIKE '%xyz%'

(Використовуйте StartsWithта EndsWithдля іншої поведінки.)

Я не зовсім впевнений, чи це корисно, тому що я не розумію, що ви маєте на увазі, коли говорите, що намагаєтесь реалізувати LIKE. Якщо я неправильно зрозумів, дайте мені знати, і я видалю цю відповідь :)


4
будь ласка, зверніть увагу, що "WHERE Name LIKE '% xyz%'" не зможе використовувати індекс, тому, якщо таблиця величезна, вона може не працювати так добре ...
Mitch Wheat

1
Ну, ми хотіли б мати можливість зіставляти на бла * бла бла foo bar foo? Bar? Foo bar? та інші складні візерунки. Наш поточний підхід подібний до того, що ви згадали, ми б перетворили ці запити на операції, використовуючи містить, indexof, startwith, endwith тощо. Я просто сподівався, що є більш загальне рішення.
brien

2
Не те, що я знаю: я підозрюю, що в результаті складні шаблони є більш специфічними для баз даних і їх важко виразити загалом.
Джон Скіт,

4
@Jon Skeet: наскільки мені відомо, функція LIKE відповідає стандарту ANSI, і вона майже однакова в SQL Server, Oracle та DB2.
AK

2
Одне, що я бачив із використанням цих операторів та MS SQL, це те, що EF додає їх як екрановані параметри "Ім'я LIKE @ p__linq__1 ESCAPE N '' ~ ''", що в моєму дуже обмеженому випадку використання виконує набагато повільніше, ніж якщо рядок пошуку просто в запиті "Ім'я, як '% xyz%'. Для сценаріїв, які у мене є, я все ще використовую StartsWith і Contains, але я роблю це за допомогою динамічного linq, оскільки це вводить параметр у оператор SQL, який у моєму сценарії створює більш ефективний запит. Не впевнений, це річ EF 4.0 чи ні. Ви також можете використовувати ObjectQueryParameters, щоб досягти того самого ...
Шейн Новіль

35

У мене була та сама проблема.

На даний момент я вирішив фільтрацію підстановок / регулярних виражень на базі клієнта на основі 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-сервера, оскільки воно використовує нестандартну функцію PATINDEX.


Поки PatIndex "працює", він повернеться, щоб вас вкусити, PatIndex у реченні where не використовує індекси в стовпці, за яким ви хочете фільтрувати.
BlackICE

@BlackICE це очікується. Під час пошуку за внутрішнім текстом (% CD% BLUE%) сервер не зможе використовувати індекси. По можливості пошук тексту з самого початку (CD% BLUE%) є більш ефективним.
surfen

@surfen patindex гірший за це, хоча він не використовуватиме індекс навіть без% спереду, пошук (СИНИЙ CD%) за допомогою patindex не використовуватиме індекс стовпця.
BlackICE


20

Є LIKEоператор, доданий в Entity Framework Core 2.0:

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спосіб, який ми бачимо .


5

Це спеціально згадується в документації як частина 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


1
У мене виникне спокуса триматися подалі від Entity SQL, якщо ви захочете відійти від EF у майбутньому. Забезпечте безпеку та дотримуйтесь опцій Contains (), StartsWith () та EndsWith () у вихідній відповіді.
Стівен Ньюман,

1
Це добре компілюється, але не вдається виконати.
brien

Код, який я розмістив, не працює під час виконання? Це походить із посилання Microsoft.
Роберт Харві,

Я відредагував запитання, посилаючись на допис у блозі, описуючи ту саму проблему, яка є у нас.
brien

Схоже, Contain () - це ваш квиток. Але, як зазначив Джон Скіт, можливо, вам доведеться скористатися деяким фактичним SQL, який безпосередньо маніпулює базою даних, якщо Contains не відповідає вашим потребам.
Роберт Харві,

2

якщо ви використовуєте 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 ='% Привіт% ', @ p__linq_2 ='% Активний '


дякую за ваш коментар, Ронель, чи можу я чим-небудь допомогти? яке повідомлення про помилку?
Steven Chong

2

Для 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"));

0

Ви можете досить просто використовувати справжній, як у Link to Entities

Додати

    <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

Також пам’ятайте про простір імен в <schema namespace="" />атрибуті

Потім додайте клас розширення у наведений простір імен:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.