String.IsNullOrWhiteSpace trong LINQ Expression


151

Tôi có đoạn mã sau:

return this.ObjectContext.BranchCostDetails.Where(
    b => b.TarrifId == tariffId && b.Diameter == diameter
        || (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
        || (!b.TarrifId.HasValue) && b.Diameter==diameter);

Và tôi gặp lỗi này khi tôi cố chạy mã:

LINQ to Entities không nhận ra phương thức 'Boolean IsNullOrWhiteSpace (System.String)' và phương thức này không thể được dịch thành biểu thức lưu trữ. "

Làm thế nào tôi có thể giải quyết vấn đề này và viết mã tốt hơn thế này?

Câu trả lời:


263

Bạn cần thay thế

!string.IsNullOrWhiteSpace(b.Diameter)

với

!(b.Diameter == null || b.Diameter.Trim() == string.Empty)

Đối với Linq cho các thực thể, điều này được dịch thành:

DECLARE @p0 VarChar(1000) = ''
...
WHERE NOT (([t0].[Diameter] IS NULL) OR (LTRIM(RTRIM([t0].[Diameter])) = @p0))

và đối với Linq to SQL gần như không hoàn toàn giống nhau

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)

3
Tại sao? Mã này biên dịch:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Eric J.

37
Nó có thể biên dịch, nhưng nó sẽ không được Linq dịch sang các thực thể. Phương thức 'Boolean IsNullOrWhiteSpace (System.String)' không được hỗ trợ dịch sang SQL. Điều tương tự cũng áp dụng cho IsNullOrEmpty.
Phil

1
Điều tương tự cũng đúng với Linq to SQL
Phil

3
Một lời cảnh báo: Đó là điều tối quan trọng để sử dụng 'chuỗi.Empty' hơn "" (còn gọi là chuỗi trống). Cái trước hoạt động cái sau không (ít nhất là liên quan đến trình điều khiển EF của Oracle). Aka nếu bạn sử dụng: b.Diameter.Trim () == "" <- điều này sẽ không hoạt động như dự định (điên tôi biết ...)
XDS

có vẻ như Trim () cũng không được hỗ trợ ít nhất cho các truy vấn bằng MongoDB.Driver
Leandro

20

Trong trường hợp này, điều quan trọng là phải phân biệt giữa IQueryable<T>IEnumerable<T>. Tóm lại IQueryable<T>được xử lý bởi nhà cung cấp LINQ để cung cấp một truy vấn tối ưu. Trong quá trình chuyển đổi này, không phải tất cả các câu lệnh C # đều được hỗ trợ, vì không thể dịch chúng sang truy vấn cụ thể phía sau (ví dụ SQL) hoặc do người triển khai không thấy trước nhu cầu về câu lệnh.

Ngược lại IEnumerable<T>được thực hiện chống lại các đối tượng cụ thể và, do đó, sẽ không được chuyển đổi. Vì vậy, điều khá phổ biến là các cấu trúc, IEnumerable<T>có thể sử dụng được, không thể được sử dụng IQueryable<T>và cũng được IQueryables<T>hỗ trợ bởi các nhà cung cấp LINQ khác nhau không hỗ trợ cùng một bộ chức năng.

Tuy nhiên, có một số cách giải quyết (như câu trả lời của Phil ), điều chỉnh sửa truy vấn. Ngoài ra, như một cách tiếp cận tổng quát hơn, có thể quay trở lại IEnumerable<T>trước khi tiếp tục với đặc tả của truy vấn. Tuy nhiên, điều này có thể có một điểm nhấn về hiệu suất - đặc biệt là khi sử dụng nó trong các hạn chế (ví dụ: các mệnh đề). Ngược lại, khi xử lý các phép biến đổi, hiệu năng đạt được nhỏ hơn rất nhiều, đôi khi thậm chí không tồn tại - tùy thuộc vào truy vấn của bạn.

Vì vậy, đoạn mã trên cũng có thể được viết lại như thế này:

return this.ObjectContext.BranchCostDetails
    .AsEnumerable()
    .Where(
        b => b.TarrifId == tariffId && b.Diameter == diameter
        || (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
        ||(!b.TarrifId.HasValue) && b.Diameter==diameter
    );

LƯU Ý: Mã Ths sẽ có tác động hiệu suất cao hơn câu trả lời của Phil . Tuy nhiên, nó cho thấy nguyên tắc.


10

Sử dụng một khách truy cập biểu thức để phát hiện các tham chiếu đến chuỗi.IsNullOrWhiteSpace và chia chúng thành một biểu thức đơn giản hơn (x == null || x.Trim() == string.Empty).

Vì vậy, bên dưới là một khách truy cập mở rộng và một phương pháp mở rộng để sử dụng nó. Nó không yêu cầu cấu hình đặc biệt để sử dụng, chỉ cần gọi WhereEx thay vì Where.

public class QueryVisitor: ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.IsStatic && node.Method.Name == "IsNullOrWhiteSpace" && node.Method.DeclaringType.IsAssignableFrom(typeof(string)))
        {
            //!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
            var arg = node.Arguments[0];
            var argTrim = Expression.Call(arg, typeof (string).GetMethod("Trim", Type.EmptyTypes));

            var exp = Expression.MakeBinary(ExpressionType.Or,
                    Expression.MakeBinary(ExpressionType.Equal, arg, Expression.Constant(null, arg.Type)),
                    Expression.MakeBinary(ExpressionType.Equal, argTrim, Expression.Constant(string.Empty, arg.Type))
                );

            return exp;
        }

        return base.VisitMethodCall(node);
    }
}

public static class EfQueryableExtensions
{
    public static IQueryable<T> WhereEx<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> where)
    {
        var visitor = new QueryVisitor();
        return queryable.Where(visitor.VisitAndConvert(where, "WhereEx"));
    }
}

Vì vậy, nếu bạn chạy, myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace())nó sẽ được chuyển đổi thành !(c.Name == null || x.Trim() == "")trước khi chuyển sang bất cứ thứ gì (linq sang sql / entity) và được chuyển đổi thành sql.


Rất phức tạp hơn câu trả lời của Phil cho một yêu cầu đơn giản như vậy, nhưng rất thú vị cho mục đích giáo dục liên quan đến ExpressionVisitor, cảm ơn
AFract 2/12/2016

2

Bạn cũng có thể sử dụng điều này để kiểm tra khoảng trắng:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())

6
điều này sẽ ném một ngoại lệ nếu đường kính là null.
Okan Kocyigit

@OkanKocyigit Bạn nói đúng. Tôi đã chỉnh sửa câu trả lời. :)
Majid

0
!String.IsNullOrEmpty(b.Diameter.Trim()) 

sẽ ném ngoại lệ nếu b.Diameternull.
Nếu bạn vẫn muốn sử dụng câu lệnh của mình, tốt hơn hãy sử dụng kiểm tra này

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace

2
Chào mừng bạn đến với StackOverflow! Trước hết, cảm ơn bạn đã tham gia vào SO với tư cách là người trả lời. Xin hãy xem định dạng để tạo ra một câu trả lời rõ ràng và dễ đọc.
Hille
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.