Toán tử LIKE trong LINQ


88

Có cách nào để so sánh các chuỗi trong biểu thức C # LINQ tương tự như LIKEtoán tử SQL không?

Giả sử tôi có một danh sách chuỗi. Trong danh sách này, tôi muốn tìm kiếm một chuỗi. Trong SQL, tôi có thể viết:

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

Thay vì ở trên, truy vấn muốn có cú pháp linq.

using System.Text.RegularExpressions;


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

Cú pháp LINQ ở trên của tôi không hoạt động. Tôi có gì sai?


1
Truy vấn này về cơ bản phù hợp với tôi khi bạn đặt nó vào đúng vị trí. Nhưng, tôi đang sử dụng trình điều khiển MongoDb Linq và có sự khác biệt về triển khai trong mỗi nhà cung cấp Linq ... dù sao, Cảm ơn.
Mark Ewer

Đây là giải pháp tốt nhất mà tôi đã tìm thấy giống như trong LINQ. Cảm ơn. - @ Pranay-Rana
Abhishek Tomar

Câu trả lời:


140

Thông thường bạn sử dụng String.StartsWith/ EndsWith/ Contains. Ví dụ:

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

Tuy nhiên, tôi không biết liệu có cách nào để thực hiện các biểu thức chính quy thích hợp thông qua LINQ to SQL hay không. (Lưu ý rằng nó thực sự phụ thuộc vào nhà cung cấp bạn đang sử dụng - nó sẽ ổn trong LINQ thành Đối tượng; vấn đề là liệu nhà cung cấp có thể chuyển đổi cuộc gọi thành định dạng truy vấn gốc của nó, ví dụ: SQL.)

CHỈNH SỬA: Như BitKFu đã nói, Singlenên được sử dụng khi bạn mong đợi chính xác một kết quả - khi đó không phải là lỗi. Tùy chọn SingleOrDefault, FirstOrDefaulthoặc Firstnên được sử dụng tùy thuộc vào chính xác những gì được mong đợi.


bạn nhưng, có một vấn đề, Danh sách của tôi chứa "BALTIMORE" và tham số so sánh đã cho của tôi là "BALTIMORE [MD], US". Không chọn được cú phápbove.
shamim

2
hãy xem câu lệnh của tôi bên dưới, nó có thể đến từ phương thức Single (). Nó tốt hơn để sử dụng FirstOrDefault ()
BitKFu

3
@shamim: Vậy dữ liệu của bạn không chứa chuỗi mà bạn đang tìm kiếm? Làm thế nào bạn mong đợi điều đó hoạt động ngay cả trong SQL?
Jon Skeet

Trong SQL, bạn có thể không nhận được bộ kết quả nào - trong C #, bạn sẽ nhận được một ngoại lệ. Đó là một chút khác nhau, thay vì không có kết quả. Đó là lý do tại sao tôi khuyên bạn nên sử dụng FirstOrDefault.
BitKFu

@BitKFu từ điểm xuất phát Single(), SingleOrDefault()sẽ là bước tiếp theo của tôi, trừ khi chúng tôi hiểu toàn bộ bối cảnh ...
Marc Gravell

34

Regex? Không. Nhưng đối với truy vấn đó, bạn chỉ có thể sử dụng:

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

Nếu bạn thực sự muốn SQL LIKE, bạn có thể sử dụng System.Data.Linq.SqlClient.SqlMethods.Like(...)LINQ-to-SQL ánh xạ tới LIKEtrong SQL Server.


@Maslow - không phải lĩnh vực chuyên môn của tôi, tôi e rằng - nhưng tôi không tin rằng có một cách ánh xạ rõ ràng tốt đẹp cho tất cả các triển khai EF, vì vậy ... không.
Marc Gravell

2
điều này có thể làm việc trên triển khai SQL nhưng không làm việc với một bộ sưu tập đối tượng tiêu chuẩn
Chris McGrath

13

Chà ... đôi khi có thể không thoải mái khi sử dụng Contains, StartsWithhoặc EndsWithđặc biệt là khi tìm kiếm giá trị xác định trạng thái, LIKEví dụ như 'giá trị%' đã chuyển yêu cầu từ nhà phát triển để sử dụng StartsWithhàm trong biểu thức. Vì vậy, tôi quyết định viết phần mở rộng cho IQueryablecác đối tượng.

Sử dụng

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

Bạn có một phiên bản làm việc với IEnumerable?
Nicke Manarin

8

Như Jon Skeet và Marc Gravell đã đề cập, bạn có thể đơn giản lấy điều kiện chứa. Nhưng trong trường hợp truy vấn like của bạn, sẽ rất nguy hiểm nếu sử dụng câu lệnh Single (), vì điều đó ngụ ý rằng bạn chỉ tìm thấy 1 kết quả. Trong trường hợp có nhiều kết quả hơn, bạn sẽ nhận được một ngoại lệ tuyệt vời :)

Vì vậy, tôi muốn sử dụng FirstOrDefault () thay vì Single ():

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

nếu như kỳ vọng đã khẳng định của chúng tôi là có đúng một trận đấu, thì Độc thân không "nguy hiểm" - đó là "chính xác". Tất cả liên quan đến những gì chúng tôi đang tuyên bố về dữ liệu ... "bất kỳ số nào", "ít nhất một", "nhiều nhất một", "chính xác một", v.v.
Marc Gravell

3
tùy thuộc vào ngữ cảnh, nó có thể là ... nó hoàn toàn phụ thuộc vào kỳ vọng của truy vấn
Marc Gravell

Điều gì về một tìm kiếm "trống" hoặc "%"? Điều này có thể xử lý "B", "BALT" và "" (có nghĩa là giúp tôi mọi thứ)?
BlueChippy

8

Trong LINQ gốc, bạn có thể sử dụng kết hợp của Contains/StartsWith/EndsWithhoặc RegExp.

Trong LINQ2SQL sử dụng phương pháp SqlMethods.Like()

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

thêm Assembly: System.Data.Linq (trong System.Data.Linq.dll) để sử dụng tính năng này.


Tôi hiểu rằng OP không thực sự nói Linq2SQL, nhưng nó có vẻ ngụ ý. Lý do tôi ở đây là StartsWith(), Contains(), vv, làm không làm việc với Linq2SQL (ít nhất tôi nhận được "các biểu thức LINQ ... Không thể dịch ..." và một hướng dẫn để sử dụng ToList () cho "thẩm định khách hàng" -which tôi' tôi đã làm. Lưu ý, trong EF Core, nó được chuyển đếnEF.Functions.Like()
Auspex,

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

Điều này hoạt động giống như "LIKE" của SQL ...


8
không .. không có nó không nó chỉ có tác dụng như LIKE '% hạn' là xa làm việc giống như những thứ tương tự điều hành như một toàn thể và không hỗ trợ ký tự đại diện
Chris McGrath

3

Đơn giản như thế này

string[] users = new string[] {"Paul","Steve","Annick","Yannick"};    
var result = from u in users where u.Contains("nn") select u;

Kết quả -> Annick, Yannick


2

Bạn có thể gọi phương thức đơn với một vị từ:

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

2

Tốt nhất bạn nên sử dụng StartWithhoặc EndWith.

Đây là một ví dụ:

DataContext  dc = new DCGeneral();
List<Person> lstPerson= dc.GetTable<Person>().StartWith(c=> c.strNombre).ToList();

return lstPerson;

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

Chỉ cần thêm vào các phương thức mở rộng đối tượng chuỗi.

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

sử dụng:

use namespase that contains this class;

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.