Suka Operator di Entity Framework?

93

Kami mencoba mengimplementasikan operator "LIKE" di Entity Framework untuk entitas kami dengan bidang string, tetapi tampaknya tidak didukung. Adakah orang lain yang mencoba melakukan hal seperti ini?

Entri blog ini merangkum masalah yang kami alami. Kita bisa menggunakan berisi, tapi itu hanya cocok dengan kasus paling sepele untuk LIKE. Menggabungkan contains, startswith, endswith, dan indexof membawa kita ke sana, tetapi membutuhkan terjemahan antara wildcard standar dan kode Linq ke Entitas.

brien
sumber
1
Lanjutkan ke jawaban ini jika Anda sudah menggunakan EF 6.2.x. Untuk jawaban ini jika Anda menggunakan EF Core 2.x
CodeNotFound

Jawaban:

36

Ini adalah posting lama sekarang, tetapi bagi siapa pun yang mencari jawabannya, tautan ini akan membantu. Lanjutkan ke jawaban ini jika Anda sudah menggunakan EF 6.2.x. Untuk jawaban ini jika Anda menggunakan EF Core 2.x

Versi pendek:

SqlFunctions.PatIndexMetode - mengembalikan posisi awal kemunculan pertama pola dalam ekspresi yang ditentukan, atau nol jika pola tidak ditemukan, pada semua tipe data teks dan karakter yang valid

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

Sedikit penjelasan juga muncul di utas forum ini .

Yann Duran
sumber
59
bagaimana jawaban yang diterima adalah jawaban yang tertaut ke forum MSDN yang menautkan kembali ke pertanyaan ini ke jawaban di bawah ini ?
Eonasdan
Jawabannya adalah dengan menggunakan metode SqlFunctions.PatIndex. Untaian forum yang ditautkan harus memberikan sedikit lebih banyak info "latar belakang".
Yann Duran
Jawaban di bawah ini bagus untuk pola sederhana, tetapi jika saya ingin mengatakan "WHERE Name LIKE 'abc [0-9]%'" atau pola lain yang lebih kompleks, cukup menggunakan Contains () tidak cukup.
HotN
1
Dup dari jawaban lama untuk pertanyaan ini. (Bukan dari bagian pertamanya, tetapi dari solusi alternatifnya.)
Frédéric
154

Saya tidak tahu apa-apa tentang EF, tetapi dalam LINQ ke SQL Anda biasanya mengekspresikan klausa LIKE menggunakan String. Berisi:

where entity.Name.Contains("xyz")

diterjemahkan menjadi

WHERE Name LIKE '%xyz%'

(Gunakan StartsWithdan EndsWithuntuk perilaku lainnya.)

Saya tidak sepenuhnya yakin apakah itu membantu, karena saya tidak mengerti apa yang Anda maksud ketika Anda mengatakan Anda mencoba menerapkan LIKE. Jika saya benar-benar salah paham, beri tahu saya dan saya akan menghapus jawaban ini :)

Jon Skeet
sumber
4
harap dicatat bahwa "WHERE Name LIKE '% xyz%'" tidak akan dapat menggunakan indeks, jadi jika tabel besar mungkin tidak akan bekerja sebaik itu ...
Mitch Wheat
1
Nah, kami ingin bisa mencocokkan di blah * blah foo bar foo? Bar? Foo bar? dan pola kompleks lainnya. Pendekatan kami saat ini mirip dengan yang Anda sebutkan, kami akan mengonversi kueri tersebut menjadi operasi menggunakan contains, indexof, startswith, endswith, dll. Saya hanya berharap ada solusi yang lebih umum.
brien
2
Saya tidak menyadarinya - Saya curiga bahwa pola kompleks akhirnya menjadi lebih spesifik db, dan sulit untuk diungkapkan secara umum.
Jon Skeet
4
@ Jon Skeet: menurut pengetahuan terbaik saya, fungsi LIKE ada dalam standar ANSI dan hampir sama di SQL Server, Oracle, dan DB2.
AK
2
Satu hal yang saya lihat dengan menggunakan operator ini dan MS SQL adalah bahwa EF menambahkannya sebagai parameter yang lolos "Name LIKE @ p__linq__1 ESCAPE N '' ~ ''" yang dalam kasus penggunaan saya yang sangat terbatas bekerja jauh lebih lambat dibandingkan jika string pencarian hanya di kueri "Nama seperti '% xyz%'. Untuk skenario yang saya miliki, saya masih menggunakan StartsWith dan Contains tetapi saya melakukannya melalui linq dinamis karena itu menyuntikkan parameter ke dalam pernyataan SQL yang dalam skenario saya menghasilkan kueri yang lebih efisien. Tidak yakin apakah ini adalah hal EF 4.0 atau bukan. Anda juga dapat menggunakan ObjectQueryParameters untuk mencapai hal yang sama ...
Shane Neuville
35

Saya memiliki masalah yang sama.

Untuk saat ini, saya telah menyelesaikan pemfilteran Wildcard / Regex sisi klien berdasarkan http://www.codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes?msg=1423024#xx1423024xx - sederhana dan berfungsi sebagai diharapkan.

Saya telah menemukan diskusi lain tentang topik ini: http://forums.asp.net/t/1654093.aspx/2/10
Posting ini terlihat menjanjikan jika Anda menggunakan Entity Framework> = 4.0:

Gunakan SqlFunctions.PatIndex:

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

Seperti ini:

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

Catatan: solusi ini hanya untuk SQL-Server, karena menggunakan fungsi PATINDEX non-standar.

surfen
sumber
Sementara PatIndex "bekerja", itu akan kembali menggigit Anda, PatIndex di mana klausa tidak menggunakan indeks pada kolom yang ingin Anda filter.
BlackICE
@BlackICE ini diharapkan. Saat Anda mencari teks bagian dalam (% CD% BLUE%) server tidak akan dapat menggunakan indeks. Jika memungkinkan, mencari teks dari awal (CD% BLUE%) lebih efisien.
berselancar
@surfen patindex lebih buruk dari itu, ia tidak akan menggunakan indeks bahkan tanpa% di depan, mencari (BLUE CD%) dengan patindex tidak akan menggunakan indeks kolom.
BlackICE
23

Pembaruan: Di EF 6.2 ada operator sejenis

Where(obj => DbFunctions.Like(obj.Column , "%expression%")
Lode Vlaeminck
sumber
Bukankah ini menjadi contoh yang lebih jelas: Where(obj => DbFunctions.Like(obj.Column , "%expression%")?
DCD
Pastilah itu. Mengubahnya
Lode Vlaeminck
20

Ada LIKEoperator yang ditambahkan di Entity Framework Core 2.0:

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

Membandingkannya ... where e.Title.Contains("developer") ...benar-benar diterjemahkan ke SQL LIKEdaripada CHARINDEXkita melihat Containsmetode.

Dmitry Pavlov
sumber
5

Ini secara khusus disebutkan dalam dokumentasi sebagai bagian dari Entity SQL. Apakah Anda mendapatkan pesan kesalahan?

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

Robert Harvey
sumber
1
Saya akan tergoda untuk menjauh dari Entity SQL jika Anda ingin beralih dari EF di masa mendatang. Mainkan dengan aman dan tetap dengan opsi Contains (), StartsWith () dan EndsWith () di respons asli sebagai gantinya.
Stephen Newman
1
Itu dapat dikompilasi dengan baik, tetapi gagal saat runtime.
Brien
Kode yang saya posting gagal saat runtime? Itu berasal dari tautan Microsoft.
Robert Harvey
Saya mengedit pertanyaan dengan tautan ke entri blog yang menjelaskan masalah yang sama yang kami alami.
Brien
Sepertinya Contains () adalah tiket Anda. Tetapi seperti yang ditunjukkan Jon Skeet, Anda mungkin harus turun ke beberapa SQL aktual yang memanipulasi database secara langsung, jika Contains tidak memenuhi kebutuhan Anda.
Robert Harvey
2

jika Anda menggunakan MS Sql, saya telah menulis 2 metode ekstensi untuk mendukung karakter% untuk pencarian wildcard. (LinqKit diperlukan)

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

pemakaian

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

di ef6 dan itu harus diterjemahkan ke

....
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 ='% Aktif '

Steven Chong
sumber
terima kasih atas komentar Anda Ronel, adakah yang bisa saya bantu? apa pesan kesalahannya?
Steven Chong
2

Untuk EfCore, berikut adalah contoh untuk membangun ekspresi 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"));
Duy Hoang
sumber
0

Anda dapat menggunakan real like di Tautkan ke Entitas dengan cukup mudah

Menambahkan

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

ke EDMX Anda di tag ini:

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

Ingat juga namespace di <schema namespace="" />atribut

Kemudian tambahkan kelas ekstensi di namespace di atas:

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

Metode ekstensi ini sekarang akan dipetakan ke fungsi EDMX.

Info lebih lanjut di sini: http://jendaperl.blogspot.be/2011/02/like-in-linq-to-entities.html

brechtvhb.dll
sumber