Нравится Оператор в Entity Framework?

93

Мы пытаемся реализовать оператор LIKE в Entity Framework для наших сущностей со строковыми полями, но, похоже, он не поддерживается. Кто-нибудь еще пробовал что-то подобное сделать?

В этом сообщении блога кратко описывается возникшая у нас проблема. Мы могли бы использовать contains, но это соответствует только самому тривиальному случаю LIKE. Объединение содержит, начинается с, заканчивается с и indexof приводит нас к этому, но требует перевода между стандартными подстановочными знаками и кодом Linq в Entities.

Brien
источник
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 Сборка: System.Data.Entity (в System.Data.Entity.dll)

Небольшое объяснение также появляется в этой ветке форума .

Янн Дюран
источник
59
как принятый ответ на тот, который ссылается на форум MSDN, который ссылается на этот вопрос на ответ ниже ?
Eonasdan
Ответ заключался в использовании метода SqlFunctions.PatIndex. Связанная ветка форума должна была предоставить немного больше "справочной" информации.
Янн Дюран
Приведенный ниже ответ хорош для простых шаблонов, но если я хочу сказать «WHERE Name LIKE 'abc [0-9]%'» или какой-либо другой более сложный шаблон, простое использование Contains () не совсем подходит.
HotN
1
Дубликат этого старого ответа на этот вопрос. (Не первой части, а альтернативного решения.)
Фредерик,
154

На самом деле я ничего не знаю об EF, но в LINQ to SQL вы обычно выражаете предложение LIKE с помощью String.Contains:

where entity.Name.Contains("xyz")

переводится на

WHERE Name LIKE '%xyz%'

(Используйте StartsWithи EndsWithдля другого поведения.)

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

Джон Скит
источник
4
обратите внимание, что «WHERE Name LIKE '% xyz%'» не сможет использовать индекс, поэтому, если таблица огромна, она может не работать так хорошо ...
Митч Уит,
1
Что ж, мы хотели бы иметь возможность сопоставить бла * бла фу бар фу? Бар? Фу бар? и другие сложные узоры. Наш текущий подход аналогичен тому, что вы упомянули, мы преобразуем эти запросы в операции, используя contains, indexof, startwith, endwith и т. Д. Я просто надеялся, что существует более универсальное решение.
brien
2
Не то, чтобы я знал об этом - я подозреваю, что сложные шаблоны в конечном итоге более специфичны для БД и их трудно выразить в общем виде.
Джон Скит,
4
@Jon Skeet: насколько мне известно, функциональность LIKE соответствует стандарту ANSI и почти такая же в SQL Server, Oracle и DB2.
AK
2
Одна вещь, которую я видел при использовании этих операторов и MS SQL, заключается в том, что EF добавляет их как экранированные параметры «Name 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-Server, поскольку оно использует нестандартную функцию PATINDEX.

серфить
источник
Пока PatIndex «работает», он вернется, чтобы укусить вас, PatIndex в предложении where не использует индексы в столбце, по которому вы хотите выполнить фильтрацию.
BlackICE
@BlackICE это ожидаемо. При поиске по внутреннему тексту (% CD% BLUE%) сервер не сможет использовать индексы. По возможности поиск текста с начала (CD% BLUE%) более эффективен.
surfen
@surfen patindex хуже этого, он не будет использовать индекс даже без% впереди, поиск (BLUE CD%) с patindex не будет использовать индекс столбца.
BlackICE
23

Обновление: в EF 6.2 есть оператор Like

Where(obj => DbFunctions.Like(obj.Column , "%expression%")
Lode Vlaeminck
источник
Разве это не был бы более ясный пример Where(obj => DbFunctions.Like(obj.Column , "%expression%"):?
DCD
Это точно. Изменено
Лоде Влаеминк
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
Похоже, Contains () - это ваш билет. Но, как указал Джон Скит, вам, возможно, придется перейти к некоторому фактическому 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 ='% Hi% ', @ p__linq_2 ='% Active '

Стивен Чонг
источник
спасибо за ваш комментарий, Ронель, я могу чем-нибудь помочь? что за сообщение об ошибке?
Стивен Чонг,
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: среда выполнения / edmx: концептуальные модели / схема

Также запомните пространство имен в <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

brechtvhb
источник