2010-08-10 49 views
6

Làm cách nào để xây dựng một cây biểu thức khi các phần của biểu thức được chuyển làm đối số?Kết hợp các biểu thức trong cây biểu thức

Ví dụ: những gì nếu tôi muốn tạo ra cây biểu hiện như thế này:

IQueryable<LxUser> test1(IQueryable<LxUser> query, string foo, string bar) 
{ 
    query=query.Where(x => x.Foo.StartsWith(foo)); 
    return query.Where(x => x.Bar.StartsWith(bar)); 
} 

nhưng bằng cách tạo ra chúng gián tiếp:

IQueryable<LxUser> test2(IQueryable<LxUser> query, string foo, string bar) 
{ 
    query=testAdd(query, x => x.Foo, foo); 
    return testAdd(query, x => x.Bar, bar); 
} 

IQueryable<T> testAdd<T>(IQueryable<T> query, 
    Expression<Func<T, string>> select, string find) 
{ 
    // how can I combine the select expression with StartsWith? 
    return query.Where(x => select(x) .. y => y.StartsWith(find)); 
} 

Kết quả:

Trong khi các mẫu không có ý nghĩa nhiều (xin lỗi nhưng tôi đã cố giữ nó đơn giản), đây là kết quả (cảm ơn Quartermeister).

Nó có thể được sử dụng với LINQ-to-Sql để tìm kiếm chuỗi bắt đầu bằng hoặc bằng findText.

public static IQueryable<T> WhereLikeOrExact<T>(IQueryable<T> query, 
    Expression<Func<T, string>> selectField, string findText) 
{ 
    Expression<Func<string, bool>> find; 
    if (string.IsNullOrEmpty(findText) || findText=="*") return query; 

    if (findText.EndsWith("*")) 
    find=x => x.StartsWith(findText.Substring(0, findText.Length-1)); 
    else 
    find=x => x==findText; 

    var p=Expression.Parameter(typeof(T), null); 
    var xpr=Expression.Invoke(find, Expression.Invoke(selectField, p)); 

    return query.Where(Expression.Lambda<Func<T, bool>>(xpr, p)); 
} 

ví dụ:

var query=context.User; 

query=WhereLikeOrExact(query, x => x.FirstName, find.FirstName); 
query=WhereLikeOrExact(query, x => x.LastName, find.LastName); 

Trả lời

5

Bạn có thể sử dụng Expression.Invoke để tạo ra một biểu thức đại diện cho áp dụng một biểu thức khác, và Expression.Lambda để tạo ra một biểu thức lambda mới cho biểu thức kết hợp. Một cái gì đó như thế này:

IQueryable<T> testAdd<T>(IQueryable<T> query, 
    Expression<Func<T, string>> select, string find) 
{ 
    Expression<Func<string, bool>> startsWith = y => y.StartsWith(find); 
    var parameter = Expression.Parameter(typeof(T), null); 
    return query.Where(
     Expression.Lambda<Func<T, bool>>(
      Expression.Invoke(
       startsWith, 
       Expression.Invoke(select, parameter)), 
      parameter)); 
} 

Các Expression.Invoke bên đại diện cho biểu select(x) và một trong những đại diện bên ngoài gọi y => y.StartsWith(find) trên giá trị được trả về bởi select(x).

Bạn cũng có thể sử dụng Expression.Call để đại diện cho cuộc gọi đến StartsWith mà không sử dụng một lambda thứ hai:

IQueryable<T> testAdd<T>(IQueryable<T> query, 
    Expression<Func<T, string>> select, string find) 
{ 
    var parameter = Expression.Parameter(typeof(T), null); 
    return query.Where(
     Expression.Lambda<Func<T, bool>>(
      Expression.Call(
       Expression.Invoke(select, parameter), 
       "StartsWith", 
       null, 
       Expression.Constant(find)), 
      parameter)); 
} 
+0

Cảm ơn, câu trả lời đầu tiên của bạn chính xác là những gì tôi đang tìm kiếm! – laktak

+0

Một lưu ý quan trọng ở đây là sẽ làm việc với LINQ2SQL và LINQ2Entities, nhưng không với EF-EF, vì lý do nổi tiếng nhất, không thực hiện 'Expression.Invoke'. – nicodemus13

3

này Tác phẩm:

public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector1, 
        Expression<Func<T, string>> Selector2, string data1, string data2) 
{ 
    return Add(Add(query, Selector1, data1), Selector2, data2); 
} 

public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector, string data) 
{ 
    var row = Expression.Parameter(typeof(T), "row"); 
    var expression = 
     Expression.Call(
      Expression.Invoke(Selector, row), 
      "StartsWith", null, Expression.Constant(data, typeof(string)) 
     ); 
    var lambda = Expression.Lambda<Func<T, bool>>(expression, row); 
    return query.Where(lambda); 
} 

Bạn sử dụng nó như:

IQueryable<XlUser> query = SomehowInitializeIt(); 
query = Add(query, x => x.Foo, y => y.Bar, "Foo", "Bar"); 
1

Thông thường bạn không làm điều đó theo cách bạn descirbed (sử dụng giao diện IQueryable) nhưng bạn thay vì sử dụng Biểu thức như Expression<Func<TResult, T>>. Có nói rằng, bạn soạn các hàm bậc cao hơn (chẳng hạn như where hoặc select) vào một truy vấn và chuyển vào các biểu thức sẽ "điền vào" chức năng mong muốn.

Ví dụ, hãy xem xét chữ ký của phương pháp Enumerable.Where:

Where<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>) 

Chức năng mất một đại biểu như là đối số thứ hai đó được gọi là trên mỗi phần tử. Giá trị bạn trả về từ đại biểu đó cho biết hàm bậc cao hơn nếu nó mang lại phần tử hiện tại (bao gồm nó trong kết quả hay không).

Bây giờ, chúng ta hãy nhìn vào Queryable.Where:

Queryable.Where<TSource>-Methode (IQueryable<TSource>, Expression<Func<TSource, Boolean>>) 

Chúng ta có thể quan sát cùng một khuôn mẫu của một hàm bậc cao, nhưng thay vì một đại biểu Func<> phải mất một Expression. Biểu thức cơ bản là biểu diễn dữ liệu của mã của bạn.Biên dịch biểu thức đó sẽ cung cấp cho bạn một ủy nhiệm thực sự (thực thi). Trình biên dịch thực hiện rất nhiều việc nâng hạng nặng để xây dựng các cây biểu thức từ lambdas mà bạn gán cho Expression<...>. Cây biểu thức làm cho nó có thể biên dịch mã được mô tả dựa trên các nguồn dữ liệu khác nhau, chẳng hạn như Cơ sở dữ liệu SQL Server.

Để quay lại ví dụ của bạn, điều tôi cho rằng bạn đang tìm kiếm là một bộ chọn . Bộ chọn lấy mỗi phần tử đầu vào và trả về một phép chiếu của nó. Chữ ký của nó trông giống như sau: Expression<Func<TResult, T>>. Ví dụ, bạn có thể chỉ định này một:

Expression<Func<int, string>> numberFormatter = (i) => i.ToString(); // projects an int into a string 

Để vượt qua trong một selector, mã của bạn sẽ cần phải xem xét như thế này:

IQueryable<T> testAdd<T>(IQueryable<T> query, Expression<Func<string, T>> selector, string find) 
{ 
    // how can I combine the select expression with StartsWith? 
    return query.Select(selector) // IQueryable<string> now 
       .Where(x => x.StartsWith(find)); 
} 

chọn này sẽ cho phép bạn dự án chuỗi đầu vào mong muốn kiểu. Tôi hy vọng tôi có ý định của bạn một cách thẳng thắn, thật khó để thấy những gì bạn đang cố gắng đạt được.

Các vấn đề liên quan