Вопрос по entity-framework, .net, linq-to-entities, sql-server – Как оператор в Entity Framework?

79

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

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

Идти кthis answer  если вы уже используете EF 6.2.x. кthis answer если вы используете EF Core 2.x CodeNotFound

Ваш Ответ

9   ответов
9

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 метод.

32

На данный момент я согласился с фильтрацией Wildcard / Regex на стороне клиента на основеhttp://www.codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes?msg=1423024#xx1423024xx - это просто и работает, как и ожидалось.

Я нашел другое обсуждение на эту тему:http://forums.asp.net/t/1654093.aspx/2/10
Этот пост выглядит многообещающе, если вы используете Entity Framework & gt; = 4.0:

Use SqlFunctions.PatIndex:

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

Like this:

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

Note: this solution is for SQL-Server only, because it uses non-standard PATINDEX function.

работал удовольствие, спасибо за это!
Хотя PatIndex & quot; работает & quot ;, он вернется, чтобы укусить вас, PatIndex в предложении where не использует индексы для столбца, по которому вы хотите фильтровать.
@ BlackICE это ожидается. При поиске по внутреннему тексту (% CD% BLUE%) сервер не сможет использовать индексы. По возможности, поиск текста с самого начала (CD% BLUE%) более эффективен.
@surfen patindex хуже, чем этот, хотя он не будет использовать индекс даже без% впереди, поиск (СИНИЙ CD%) с patindex не будет использовать индекс столбцов.
28

эта ссылка должно помочь Идти кэтот ответ если вы уже используете EF 6.2.x. кэтот ответ если вы используете EF Core 2.x

Укороченная версия:

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

Пространство имен: System.Data.Objects.SqlClient Сборка: System.Data.Entity (в System.Data.Entity.dll)

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

Dup этого старшеanswer на этот вопрос. (Не своей первой части, а альтернативного решения.)
как принятый ответ тот, который ссылается на форум MSDN, который ссылается на этот вопросanswer below?
Ответ заключался в использовании метода SqlFunctions.PatIndex. Связанная ветка форума должна была предоставить немного больше «фона» Информация.
Приведенный ниже ответ хорош для простых шаблонов, но если я хочу сказать «ГДЕ ИМЯ НРАВИТСЯ» abc [0-9]% »& quot; или какой-либо другой более сложный шаблон, просто использование Contains () не совсем его обрезает.
5

учаете сообщение об ошибке?

// 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

Я отредактировал вопрос со ссылкой на сообщение в блоге, описывающее ту же проблему, что и у нас. brien
Я бы хотел остаться в стороне от Entity SQL на случай, если в будущем вы захотите отойти от EF. Будьте осторожны и придерживайтесь опций Contains (), StartsWith () и EndsWith () в исходном ответе.
Код, который я разместил, не работает во время выполнения? Это происходит по ссылке Microsoft.
Сообщение об ошибке?..
Это хорошо компилируется, но не работает во время выполнения. brien
0

добавлять

    <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

2

я написал 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 ".

спасибо за ваш комментарий Ронель, я могу чем-нибудь помочь? что такое сообщение об ошибке?
у меня есть ошибка в PredicateBuilder
14

Where(i => DbFunctions.Like(searchstring ,like expression)
0

"мы" хотели бы иметь возможность совпадать на блаblah foobar foo? bar? foo * bar? и другие сложные шаблоны. & quot; Я на самом деле не пробовал это (пока не нужно), но пытались ли вы использовать System.Text.RegularExpssions.RegEx?

Регулярное выражение будет выполняться на веб-сервере, а не в БД. По сути, для этого потребуется вернуть всю таблицу из SQL Server, а затем выполнить фильтрацию. Это будет работать для некоторых небольших случаев, но нам нужно искать большие (ish) объемы данных. Хорошее предложение, тем не менее, это была моя первая мысль тоже. brien
144

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

where entity.Name.Contains("xyz")

переводит на

WHERE Name LIKE '%xyz%'

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

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

обратите внимание, что "WHERE Name LIKE"% xyz% "" не сможет использовать индекс, поэтому, если таблица огромна, она может работать не так хорошо ...
Ну, мы хотели бы иметь возможность соответствоватьblah *blah Foobar foo?bar ?fooбар? и другие сложные модели. Наш нынешний подход аналогичен тому, что вы упомянули, мы конвертировали бы эти запросы в операции, используя has, indexof, начальные с, концевые и т. Д. Я просто надеялся, что найдется более универсальное решение. brien
Одна вещь, которую я видел при использовании этих операторов и MS SQL, состоит в том, что EF добавляет их в качестве экранированных параметров «Имя LIKE @ p__linq__1 ESCAPE N» «~». который в моем очень ограниченном сценарии использования работает намного медленнее, чем если строка поиска находится просто в запросе «Имя как»% xyz% ». Для сценариев, которые у меня есть, я все еще использую StartsWith и Contains, но я делаю это с помощью динамического linq, потому что это вводит параметр в оператор SQL, который в моем сценарии создает более эффективный запрос. Не уверен, что это EF 4.0 или нет. Вы также можете использовать ObjectQueryParameters для достижения того же самого ...
@Jon Skeet: насколько мне известно, функциональность LIKE соответствует стандарту ANSI и почти одинакова в SQL Server, Oracle и DB2.
Не то, чтобы я знал - я подозреваю, что сложные шаблоны в конечном итоге становятся более специфичными для БД и их трудно выразить в общем виде.

Похожие вопросы