Pytanie w sprawie linq-to-entities, entity-framework, sql-server, .net – Podobnie jak operator w Entity Framework?

79

Próbujemy zaimplementować operator „LIKE” w Entity Framework dla naszych jednostek za pomocą pól ciągów, ale nie wydaje się, aby był obsługiwany. Czy ktoś próbował coś takiego zrobić?

Topost na blogu podsumowuje problem, który mamy. Moglibyśmy użyć zawiera, ale to pasuje tylko do najbardziej trywialnego przypadku LIKE. Łączenie zawiera, startswith, endswith i indexof dostaje nas tam, ale wymaga tłumaczenia między standardowymi symbolami wieloznacznymi a kodem Linq do Entities.

Iść dota odpowiedź  jeśli korzystasz już z EF 6.2.x. Dota odpowiedź jeśli używasz EF Core 2.x CodeNotFound

Twoja odpowiedź

9   odpowiedzi
5

Jest to szczególnie wspomniane w dokumentacji jako część Entity SQL. Czy otrzymujesz komunikat o błędzie?

<code>// 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%'
</code>

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

To dobrze się kompiluje, ale nie działa w czasie wykonywania. brien
Zmodyfikowałem pytanie za pomocą linku do wpisu na blogu opisującego ten sam problem, który mamy. brien
Kod, który wysłałem, nie działa w czasie wykonywania? Pochodzi z linku Microsoft. Robert Harvey
Wygląda na to, że Contains () to Twój bilet. Ale jak zauważył Jon Skeet, być może trzeba będzie przejść do jakiegoś rzeczywistego SQL manipulującego bazą danych bezpośrednio, jeśli Contains nie spełnia twoich potrzeb. Robert Harvey
0

bla foobar foo? bar? foo * bar? i inne złożone wzorce. ”Nie próbowałem tego jeszcze (nie musiałem jeszcze), ale czy próbowałeś użyć System.Text.RegularExpressions.RegEx?

Wyrażenie regularne będzie uruchamiane na serwerze WWW, a nie na DB. Zasadniczo wymagałoby to zwrotu całej tabeli z SQL Server, a następnie filtrowania. To działałoby w niektórych małych przypadkach, ale musimy przeszukać duże (izh) ilości danych. Dobra sugestia, to była również moja pierwsza myśl. brien
144

Naprawdę nie wiem nic o EF, ale w LINQ to SQL zwykle wyrażasz klauzulę LIKE przy użyciu String.Contains:

<code>where entity.Name.Contains("xyz")
</code>

przetłumaczyć na

<code>WHERE Name LIKE '%xyz%'
</code>

(Posługiwać sięStartsWith iEndsWith dla innych zachowań.)

Nie jestem do końca pewien, czy to jest pomocne, ponieważ nie rozumiem, co masz na myśli, kiedy mówisz, że próbujeszwprowadzić w życie LUBIĆ. Jeśli źle zrozumiałem, daj mi znać, a usunę tę odpowiedź :)

@landsteven: To nie odpowiada na moje pytanie. Z pewnością, jeśli wartość rekordu zaczyna się od danych wprowadzonych przez użytkownika, to równieżzawiera dane wejściowe użytkownika. „ABC Dealer X” zawiera „ABC Dealer”, podobnie jak „X ABC Dealer”. Czy możesz podać przykład, w którym Contains zwróci false, ale StartsWith lub EndsWith zwróci true? Jon Skeet
@AlexKuznetsov: Wydaje mi się, że pamiętam pewne problemy z kompatybilnością SQL Server i Oracle w przeszłości, szczególnie jeśli chodzi o ucieczkę. Musiałem napisać operator Hibernate, aby poradzić sobie z% będącym w ciągu zapytania, a implementacje Oracle i SQL Server były różne. Być może brakowało mi sztuczki lub że świat się poprawił od tego czasu. Jon Skeet
@John Skeet: Kiedy mój użytkownik wprowadza „ABC Dealer” jako łańcuch do zapytania do mojej bazy danych w celu zwrócenia wyników dla AutoCompleteBox.ItemsSource. Wyniki muszą zawierać listę wszystkich rekordów względnych znalezionych w tabeli źródłowej, nawet jeśli rzeczywisty rekord, którego szukają, może zostać uruchomiony LUB zawierać koniec OR z podanym wejściem. landsteven
+1 Właśnie wypróbowałem to na EF 3.5 na SQL Server i działa przynajmniej w tej kombinacji. Mark Seemann
14

Aktualizacja: w EF 6.2 istnieje podobny operator

<code>Where(i => DbFunctions.Like(searchstring ,like expression)
</code>
28

ten link powinno pomóc. Iść dota odpowiedź jeśli korzystasz już z EF 6.2.x. Dota odpowiedź jeśli używasz EF Core 2.x

Krótka wersja:

SqlFunctions.PatIndex metoda - zwraca pozycję początkową pierwszego wystąpienia wzorca w określonym wyrażeniu lub zera, jeśli wzorzec nie został znaleziony, we wszystkich prawidłowych typach tekstu i danych znakowych

Namespace: System.Data.Objects.SqlClient Assembly: System.Data.Entity (w System.Data.Entity.dll)

W tym miejscu pojawia się też trochę wyjaśnieniawątek na forum.

Dup tego starszegoodpowiedź do tego pytania. (Nie z jego pierwszej części, ale z alternatywnego rozwiązania.) Frédéric
Odpowiedzią było użycie metody SqlFunctions.PatIndex. Powiązany wątek forum miał zapewnić nieco więcej informacji „w tle”. Yann Duran
w jaki sposób zaakceptowana odpowiedź odpowiada tej, która prowadzi do forum MSDN, które prowadzi do tego pytaniaodpowiedź poniżej? Eonasdan
Odpowiedź poniżej jest przyjemna dla prostych wzorców, ale jeśli chcę powiedzieć „GDZIE nazwa LIKE” abc [0-9]% ”lub jakiś inny bardziej złożony wzór, po prostu użycie Contains () nie całkiem go odcina. HotN
32

Na razie ustaliłem na podstawie filtrowania Wildcard / Regex po stronie klientahttp://www.codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes?msg=1423024#xx1423024xx - To proste i działa zgodnie z oczekiwaniami.

Znalazłem kolejną dyskusję na ten temat:http://forums.asp.net/t/1654093.aspx/2/10
Ten post wygląda obiecująco, jeśli używasz Entity Framework> = 4.0:

Użyj SqlFunctions.PatIndex:

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

Lubię to:

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

Uwaga: to rozwiązanie jest przeznaczone tylko dla SQL-Server, ponieważ używa niestandardowej funkcji PATINDEX.

@surfen patindex jest gorszy niż to, nie użyje indeksu nawet bez% z przodu, wyszukiwanie (NIEBIESKI CD%) z patindex nie użyje indeksu kolumny. BlackICE
Podczas gdy PatIndex „działa”, wróci do gryzienia, PatIndex w klauzuli where nie używa indeksów w kolumnie, w której chcesz filtrować. BlackICE
@BlackICE jest to oczekiwane. Podczas wyszukiwania tekstu wewnętrznego (% CD% NIEBIESKI%) serwer nie będzie mógł korzystać z indeksów. W miarę możliwości wyszukiwanie tekstu od początku (CD% BLUE%) jest bardziej efektywne. surfen
przepraszam, dzięki za znalezienie tego! Tom Carver
9

JestLIKE operator jest dodawanyEntity Framework Core 2.0:

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

W porównaniu do... where e.Title.Contains("developer") ... to jest naprawdę przetłumaczone naSQL LIKE zamiastCHARINDEX widzimy dlaContains metoda.

2

jeśli używasz MS Sql, napisałem 2 metody rozszerzeń, aby obsługiwać znak% w poszukiwaniu symboli wieloznacznych. (Wymagany jest LinqKit)

<code>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;
    }
}
</code>

stosowanie

<code>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();    
</code>

w ef6 i powinno się tłumaczyć na

<code>....
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 '~'
</code>

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

dzięki za komentarz Ronel, czy mogę coś pomóc? jaki jest komunikat o błędzie? Steven Chong
mam błąd w PredicateBuilder Ronel Gonzales
0

Dodaj

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

do twojego EDMX w tym tagu:

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

Pamiętaj także o przestrzeni nazw w<schema namespace="" /> atrybut

Następnie dodaj klasę rozszerzenia w powyższej przestrzeni nazw:

<code>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");
    }
}
</code>

Ta metoda rozszerzenia zostanie teraz odwzorowana na funkcję EDMX.

Więcej informacji tutaj:http://jendaperl.blogspot.be/2011/02/like-in-linq-to-entities.html

Powiązane pytania