ตัวดำเนินการ LIKE ใน LINQ


90

มีวิธีใดบ้างในการเปรียบเทียบสตริงในนิพจน์ C # LINQ ที่คล้ายกับLIKEตัวดำเนินการของ SQL

สมมติว่าฉันมีรายการสตริง ในรายการนี้ฉันต้องการค้นหาสตริง ใน SQL ฉันสามารถเขียน:

SELECT * FROM DischargePort WHERE PortName LIKE '%BALTIMORE%'

แทนที่จะเป็นข้างต้นแบบสอบถามต้องการไวยากรณ์ linq

using System.Text.RegularExpressions;
…

var regex = new Regex(sDischargePort, RegexOptions.IgnoreCase);
var sPortCode = Database.DischargePorts
                .Where(p => regex.IsMatch(p.PortName))
                .Single().PortCode;

ไวยากรณ์ LINQ ข้างต้นของฉันไม่ทำงาน ฉันผิดอะไร


1
แบบสอบถามนี้ใช้ได้ผลสำหรับฉันเป็นหลักเมื่อคุณวางไว้ในสถานที่ แต่ฉันใช้ไดรเวอร์ MongoDb Linq และมีความแตกต่างในการใช้งานในผู้ให้บริการ Linq แต่ละราย ... อย่างไรก็ตามขอบคุณ
Mark Ewer

นี่เป็นทางออกที่ดีที่สุดที่ฉันพบใน LINQ ขอบคุณ. - @ Pranay-Rana
Abhishek Tomar

ยังไม่ชัดเจนว่าคุณต้องการอะไรและอะไร "ใช้ไม่ได้" คุณต้องการเทียบเท่า LINQ-to-objects Likeหรือฟังก์ชันที่แปลLikeเป็น ORM ที่คุณใช้อยู่หรือไม่? ถ้าหลังไหน ORM? นอกจากนี้โปรดยอมรับหนึ่งในคำตอบหากคำตอบนี้ช่วยคุณได้ ผู้คนยังคงรวบรวมคำตอบโดยไม่มีอะไรเลยนอกจากสมมติฐานที่คลุมเครือเกี่ยวกับสิ่งที่คุณกำลังถาม
Gert Arnold

คำตอบ:


144

โดยทั่วไปคุณใช้String.StartsWith/ EndsWith/ Contains. ตัวอย่างเช่น:

var portCode = Database.DischargePorts
                       .Where(p => p.PortName.Contains("BALTIMORE"))
                       .Single()
                       .PortCode;

ฉันไม่รู้ว่ามีวิธีทำนิพจน์ทั่วไปที่เหมาะสมผ่าน LINQ ไปยัง SQL หรือไม่ (โปรดทราบว่าจริงๆแล้วมันขึ้นอยู่กับผู้ให้บริการที่คุณใช้ - มันจะดีใน LINQ เป็น Objects มันขึ้นอยู่กับว่าผู้ให้บริการสามารถแปลงการโทรเป็นรูปแบบการสืบค้นดั้งเดิมหรือไม่เช่น SQL)

แก้ไข: ตามที่ BitKFu กล่าวไว้Singleควรใช้เมื่อคุณคาดหวังผลลัพธ์เดียว - เมื่อมีข้อผิดพลาดที่ไม่เป็นเช่นนั้น ตัวเลือกของSingleOrDefault, FirstOrDefaultหรือFirstควรจะใช้ขึ้นอยู่กับว่าสิ่งที่คาดหวัง


เพื่อน แต่มีปัญหาอย่างหนึ่งคือรายการของฉันมี "BALTIMORE" และพารามิเตอร์เปรียบเทียบของฉันคือ "BALTIMORE [MD], US" ไวยากรณ์ด้านบนไม่สามารถเลือกได้
shamim

2
ดูคำสั่งของฉันด้านล่างอาจมาจากวิธี Single () ควรใช้ FirstOrDefault ()
BitKFu

3
@shamim: ข้อมูลของคุณไม่มีสตริงที่คุณกำลังมองหา? คุณคาดหวังว่าจะทำงานได้อย่างไรแม้ใน SQL
Jon Skeet

ใน SQL คุณอาจไม่ได้รับชุดผลลัพธ์ - ใน C # คุณจะได้รับข้อยกเว้น ซึ่งแตกต่างกันเล็กน้อยแทนที่จะไม่มีผลลัพธ์ นั่นเป็นเหตุผลที่ฉันแนะนำให้ใช้ FirstOrDefault
BitKFu

@BitKFu จากจุดเริ่มต้นของSingle(), SingleOrDefault()จะเป็นขั้นตอนต่อไปของฉันถ้าเราเข้าใจบริบทเต็ม ...
Marc Gravell

34

Regex? ไม่. แต่สำหรับแบบสอบถามนั้นคุณสามารถใช้:

 string filter = "BALTIMORE";
 (blah) .Where(row => row.PortName.Contains(filter)) (blah)

ถ้าคุณต้องการ SQL จริงๆLIKEคุณสามารถใช้System.Data.Linq.SqlClient.SqlMethods.Like(...)LINQ-to-SQL ที่แมปกับLIKESQL Server ได้


@ Maslow - ไม่ใช่สาขาความเชี่ยวชาญของฉันฉันกลัว - แต่ฉันไม่เชื่อว่าจะมีวิธีการทำแผนที่ที่ดีสำหรับการใช้งาน EF ทั้งหมดดังนั้น ... ไม่
Marc Gravell

2
สิ่งนี้อาจใช้ได้กับการใช้งาน SQL แต่ใช้ไม่ได้กับคอลเล็กชันอ็อบเจ็กต์มาตรฐาน
Chris McGrath

13

ดี ... บางครั้งมันอาจจะอึดอัดที่จะใช้Contains, StartsWithหรือEndsWithโดยเฉพาะอย่างยิ่งเมื่อค้นหาค่าตรวจสอบLIKEงบเช่นผ่าน 'ค่า%' ต้องการจากนักพัฒนาในการใช้งานStartsWithฟังก์ชั่นในการแสดงออก ดังนั้นฉันจึงตัดสินใจเขียนส่วนขยายสำหรับIQueryableวัตถุ

การใช้งาน

// numbers: 11-000-00, 00-111-00, 00-000-11

var data1 = parts.Like(p => p.Number, "%11%");
// result: 11-000-00, 00-111-00, 00-000-11

var data2 = parts.Like(p => p.Number, "11%");
// result: 11-000-00

var data3 = parts.Like(p => p.Number, "%11");
// result: 00-000-11

รหัส

public static class LinqEx
{
    private static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains");
    private static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
    private static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });

    public static Expression<Func<TSource, bool>> LikeExpression<TSource, TMember>(Expression<Func<TSource, TMember>> property, string value)
    {
        var param = Expression.Parameter(typeof(TSource), "t");
        var propertyInfo = GetPropertyInfo(property);
        var member = Expression.Property(param, propertyInfo.Name);

        var startWith = value.StartsWith("%");
        var endsWith = value.EndsWith("%");

        if (startWith)
            value = value.Remove(0, 1);

        if (endsWith)
            value = value.Remove(value.Length - 1, 1);

        var constant = Expression.Constant(value);
        Expression exp;

        if (endsWith && startWith)
        {
            exp = Expression.Call(member, ContainsMethod, constant);
        }
        else if (startWith) 
        {
            exp = Expression.Call(member, EndsWithMethod, constant);
        }
        else if (endsWith)
        {
            exp = Expression.Call(member, StartsWithMethod, constant);
        }
        else
        {
            exp = Expression.Equal(member, constant);
        }

        return Expression.Lambda<Func<TSource, bool>>(exp, param);
    }

    public static IQueryable<TSource> Like<TSource, TMember>(this IQueryable<TSource> source, Expression<Func<TSource, TMember>> parameter, string value)
    {
        return source.Where(LikeExpression(parameter, value));
    }

    private static PropertyInfo GetPropertyInfo(Expression expression)
    {
        var lambda = expression as LambdaExpression;
        if (lambda == null)
            throw new ArgumentNullException("expression");

        MemberExpression memberExpr = null;

        switch (lambda.Body.NodeType)
        {
            case ExpressionType.Convert:
                memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
                break;
            case ExpressionType.MemberAccess:
                memberExpr = lambda.Body as MemberExpression;
                break;
        }

        if (memberExpr == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");


        var output = memberExpr.Member as PropertyInfo;

        if (output == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");

        return output;
    }
}

คุณมีเวอร์ชันที่ใช้งานได้IEnumerableหรือไม่?
Nicke Manarin

9

ใน LINQ ดั้งเดิมคุณอาจใช้การรวมกันของContains/StartsWith/EndsWithหรือ RegExp

ในวิธีการใช้ LINQ2SQL SqlMethods.Like()

    from i in db.myTable
    where SqlMethods.Like(i.field, "tra%ata")
    select i

เพิ่ม Assembly: System.Data.Linq (ใน System.Data.Linq.dll) เพื่อใช้คุณสมบัตินี้


ผมเข้าใจว่า OP ไม่จริงพูด Linq2SQL แต่มันดูเหมือนนัย เหตุผลที่ฉันอยู่ที่นี่ก็คือว่าStartsWith(), Contains()ฯลฯ ไม่ได้ทำงานร่วมกับ Linq2SQL (อย่างน้อยฉันจะได้รับ"การแสดงออก LINQ ... ไม่อาจจะแปล ..."และการเรียนการสอนกับการใช้ ToList () สำหรับ "การประเมินผลลูกค้าไงเล่า" ซึ่งฉัน' กำลังทำอยู่หมายเหตุใน EF Core ได้ย้ายไปที่EF.Functions.Like()
Auspex

8

ดังที่ Jon Skeet และ Marc Gravell ได้กล่าวไว้แล้วคุณสามารถใช้เงื่อนไขที่มีอยู่ได้ง่ายๆ แต่ในกรณีของข้อความค้นหาที่คุณชอบการใช้คำสั่ง Single () นั้นอันตรายมากเพราะหมายความว่าคุณจะพบผลลัพธ์เพียง 1 รายการ ในกรณีที่ผลลัพธ์มากกว่านี้คุณจะได้รับข้อยกเว้นที่ดี :)

ดังนั้นฉันต้องการใช้ FirstOrDefault () แทน Single ():

var first = Database.DischargePorts.FirstOrDefault(p => p.PortName.Contains("BALTIMORE"));
var portcode = first != null ? first.PortCode : string.Empty;

หากเป็นความคาดหวังของเราที่ยืนยันว่ามีการแข่งขันเพียงนัดเดียวความโสดก็ไม่ "อันตราย" นั่นคือ "ถูกต้อง" ทั้งหมดนี้ขึ้นอยู่กับสิ่งที่เราอ้างเกี่ยวกับข้อมูล ... "หมายเลขใด ๆ " "อย่างน้อยหนึ่ง" "มากที่สุดหนึ่ง" "หนึ่งเดียว" ฯลฯ
Marc Gravell

3
ขึ้นอยู่กับบริบทอาจเป็นได้ ... ขึ้นอยู่กับความคาดหวังของข้อความค้นหาทั้งหมด
Marc Gravell

แล้วการค้นหา "ว่าง" หรือ "%" ล่ะ? สิ่งนี้สามารถจัดการ "B" "BALT" และ "" ได้หรือไม่
BlueChippy



2

คุณสามารถเรียกวิธีเดียวด้วยเพรดิเคต:

var portCode = Database.DischargePorts
                   .Single(p => p.PortName.Contains("BALTIMORE"))
                   .PortCode;

2
  .Where(e => e.Value.StartsWith("BALTIMORE"))

ทำงานเหมือน "LIKE" ของ SQL ...


9
ไม่ .. ไม่มันไม่เพียง แต่ทำงานเหมือนกับ 'term%' ซึ่งอยู่ไกลจากการทำงานเหมือนกับตัวดำเนินการที่เหมือนกันโดยรวมและไม่รองรับสัญลักษณ์แทน
Chris McGrath


0
   public static class StringEx
    {
        public static bool Contains(this String str, string[] Arr, StringComparison comp)
        {
            if (Arr != null)
            {
                foreach (string s in Arr)
                {
                    if (str.IndexOf(s, comp)>=0)
                    { return true; }
                }
            }

            return false;
        }

        public static bool Contains(this String str,string[] Arr)
        {
            if (Arr != null)
            {
                foreach (string s in Arr)
                {
                    if (str.Contains(s))
                    { return true; }
                }
            }

            return false;
        }
    }


var portCode = Database.DischargePorts
                   .Single(p => p.PortName.Contains( new string[] {"BALTIMORE"},  StringComparison.CurrentCultureIgnoreCase) ))
                   .PortCode;

0

เพียงเพิ่มวิธีการขยายวัตถุสตริง

public static class StringEx
{
    public static bool Contains(this String str, string[] Arr, StringComparison comp)
    {
        if (Arr != null)
        {
            foreach (string s in Arr)
            {
                if (str.IndexOf(s, comp)>=0)
                { return true; }
            }
        }

        return false;
    }

    public static bool Contains(this String str,string[] Arr)
    {
        if (Arr != null)
        {
            foreach (string s in Arr)
            {
                if (str.Contains(s))
                { return true; }
            }
        }

        return false;
    }
}

การใช้งาน:

use namespase that contains this class;

var sPortCode = Database.DischargePorts
            .Where(p => p.PortName.Contains(new string [] {"BALTIMORE"},  StringComparison.CurrentCultureIgnoreCase) )
            .Single().PortCode;

0

@adobrzyc มีLIKEฟังก์ชั่นแบบกำหนดเองที่ยอดเยี่ยมนี้- ฉันแค่อยากแชร์IEnumerableเวอร์ชันของมัน

public static class LinqEx
{
    private static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains");
    private static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
    private static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });

    private static Func<TSource, bool> LikeExpression<TSource, TMember>(Expression<Func<TSource, TMember>> property, string value)
    {
        var param = Expression.Parameter(typeof(TSource), "t");
        var propertyInfo = GetPropertyInfo(property);
        var member = Expression.Property(param, propertyInfo.Name);

        var startWith = value.StartsWith("%");
        var endsWith = value.EndsWith("%");

        if (startWith)
            value = value.Remove(0, 1);

        if (endsWith)
            value = value.Remove(value.Length - 1, 1);

        var constant = Expression.Constant(value);
        Expression exp;

        if (endsWith && startWith)
        {
            exp = Expression.Call(member, ContainsMethod, constant);
        }
        else if (startWith)
        {
            exp = Expression.Call(member, EndsWithMethod, constant);
        }
        else if (endsWith)
        {
            exp = Expression.Call(member, StartsWithMethod, constant);
        }
        else
        {
            exp = Expression.Equal(member, constant);
        }

        return Expression.Lambda<Func<TSource, bool>>(exp, param).Compile();
    }

    public static IEnumerable<TSource> Like<TSource, TMember>(this IEnumerable<TSource> source, Expression<Func<TSource, TMember>> parameter, string value)
    {
        return source.Where(LikeExpression(parameter, value));
    }


    private static PropertyInfo GetPropertyInfo(Expression expression)
    {
        var lambda = expression as LambdaExpression;
        if (lambda == null)
            throw new ArgumentNullException("expression");

        MemberExpression memberExpr = null;

        switch (lambda.Body.NodeType)
        {
            case ExpressionType.Convert:
                memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
                break;
            case ExpressionType.MemberAccess:
                memberExpr = lambda.Body as MemberExpression;
                break;
        }

        if (memberExpr == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");


        var output = memberExpr.Member as PropertyInfo;

        if (output == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");

        return output;
    }
}

0

เช่นเดียวกับส่วนขยาย Linq / SQL

คลาส LikeExtension

ผ่านการทดสอบใน. NET 5

 public static class LikeExtension {

    private static string ColumnDataBase<TEntity, TKey>(IModel model, Expression<Func<TEntity, TKey>> predicate) where TEntity : class {

        ITable table = model
            .GetRelationalModel()
            .Tables
            .First(f => f
                .EntityTypeMappings
                .First()
                .EntityType == model
                .FindEntityType(predicate
                    .Parameters
                    .First()
                .Type
            ));

        string column = (predicate.Body as MemberExpression).Member.Name;
        string columnDataBase = table.Columns.First(f => f.PropertyMappings.Count(f2 => f2.Property.Name == column) > 0).Name;

        return columnDataBase;

    }

    public static IQueryable<TEntity> Like<TEntity, TKey>(this DbContext context, Expression<Func<TEntity, TKey>> predicate, string text) where TEntity : class {

        string columnDataBase = ColumnDataBase(context.Model, predicate);
        return context.Set<TEntity>().FromSqlRaw(context.Set<TEntity>().ToQueryString() + " WHERE [" + columnDataBase + "] LIKE {0}", text);

    }

    public static async Task<IEnumerable<TEntity>> LikeAsync<TEntity, TKey>(this DbContext context, Expression<Func<TEntity, TKey>> predicate, string text, CancellationToken cancellationToken) where TEntity : class {

        string columnDataBase = ColumnDataBase(context.Model, predicate);
        return await context.Set<TEntity>().FromSqlRaw(context.Set<TEntity>().ToQueryString() + " WHERE [" + columnDataBase + "] LIKE {0}", text).ToListAsync(cancellationToken);

    }

    public static async Task<IEnumerable<TEntity>> LikeAsync<TEntity, TKey>(this IQueryable<TEntity> query, Expression<Func<TEntity, TKey>> predicate, string text, CancellationToken cancellationToken) where TEntity : class {

        DbSet<TEntity> entities = query as DbSet<TEntity>;
        string columnDataBase = ColumnDataBase(entities.EntityType.Model, predicate);
        return await entities.FromSqlRaw(query.ToQueryString() + " WHERE [" + columnDataBase + "] LIKE {0}", text).ToListAsync(cancellationToken);

    }

    public static IQueryable<TEntity> Like<TEntity, TKey>(this IQueryable<TEntity> query, Expression<Func<TEntity, TKey>> predicate, string text) where TEntity : class {

        DbSet<TEntity> entities = query as DbSet<TEntity>;
        string columnDataBase = ColumnDataBase(entities.EntityType.Model, predicate);
        return entities.FromSqlRaw(query.ToQueryString() + " WHERE [" + columnDataBase + "] LIKE {0}", text);

    }

}

ที่เก็บ

    public async Task<IEnumerable<TEntity>> LikeAsync<TKey>(Expression<Func<TEntity, TKey>> predicate, string text, CancellationToken cancellationToken) {

        return await context.LikeAsync(predicate, text, cancellationToken);

    }

    public IQueryable<TEntity> Like<TKey>(Expression<Func<TEntity, TKey>> predicate, string text) {

        return context.Like(predicate, text);

    }

ใช้

 IQueryable<CountryEntity> result = countryRepository
     .Like(k => k.Name, "%Bra[sz]il%") /*Use Sync*/
     .Where(w => w.DateRegister < DateTime.Now) /*Example*/
     .Take(10); /*Example*/

หรือ

 IEnumerable<CountryEntity> result = await countryRepository
     .LikeAsync(k => k.Name, "%Bra[sz]il%", cancellationToken); /*Use Async*/

หรือ

 IQueryable<CountryEntity> result = context.Countries
     .Like(k => k.Name, "%Bra[sz]il%")
     .Where(w => w.Name != null); /*Example*/

หรือ

 List<CountryEntity> result2 = await context.Countries
     .Like(k => k.Name, "%Bra[sz]il%")
     .Where(w => w.Name != null) /*Example*/
     .ToListAsync(); /*Use Async*/

หรือ

 IEnumerable<CountryEntity> result3 = await context.Countries
     .Where(w => w.Name != null)
     .LikeAsync(k => k.Name, "%Bra[sz]il%", cancellationToken); /*Use Async*/

0

คุณยังสามารถใช้ฟังก์ชัน EF ที่ทดสอบใน. net5

public async Task<IEnumerable<District>> SearchDistrict(string query, int stateId)
        {
            return await _dbContext
                .Districts
                .Include(s => s.State)
                .Where(s => s.StateId == stateId && EF.Functions.Like(s.Name, "$%{query}%"))
                .AsNoTracking()
                .ToListAsync();
        }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.