2012-05-15 27 views
8

Tôi đang trong quá trình tạo một hệ thống lọc phức tạp hơn cho dự án lớn này của chúng ta. Một trong những vị từ chính là có thể vượt qua các so sánh thông qua một tham số chuỗi. Điều này thể hiện trong các hình thức sau đây: "> 50" hoặc "5-10" hoặc "< 123,2"Phương pháp mở rộng trả về biểu thức lambda thông qua so sánh

Những gì tôi có (như là một ví dụ để minh họa)

ViewModel:

TotalCost (string) (value: "<50") 
Required (string) (value: "5-10") 

EF mô hình:

TotalCost (double) 
Required(double) 

biểu mà tôi muốn sử dụng:

model => model.Where(field => field.TotalCost.Compare(viewModel.TotalCost) && field.Required.Compare(viewModel.Required)); 

biểu mà tôi muốn nhận:

model => model.Where(field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10); 

Hoặc một cái gì đó giống như rằng

Tuy nhiên ... Tôi không có ý tưởng bắt đầu từ đâu. Tôi đã thu hẹp nó xuống

public static Expression Compare<T>(this Expression<Func<T, bool>> value, string compare) 

Nó có thể không chính xác, nhưng đây là tất cả những gì tôi có. Người xây dựng so sánh không phải là vấn đề, đó là một chút dễ dàng. Phần khó thực sự là trả lại biểu thức. Tôi chưa bao giờ thử trả về các biểu thức như các giá trị hàm. Vì vậy, về cơ bản những gì tôi cần phải giữ, là lĩnh vực và trả lại một biểu thức so sánh, khá nhiều.

Bất kỳ trợ giúp nào? : X

Cập nhật:

Alas này không giải quyết vấn đề của tôi. Có thể là vì tôi đã thức dậy trong 23 giờ qua, nhưng tôi không có manh mối nhỏ nào về cách biến nó thành một phương pháp mở rộng. Như tôi đã nói, những gì tôi muốn ... về cơ bản là một cách để viết:

var ex = new ExTest(); 
var items = ex.Repo.Items.Where(x => x.Cost.Compare("<50")); 

Con đường tôi hình ra rằng chức năng (có thể là hoàn toàn sai) là

public static Expression<Func<decimal, bool>> Compare(string arg) 
{ 
    if (arg.Contains("<")) 
     return d => d < int.Parse(arg); 

    return d => d > int.Parse(arg); 
} 

Nó thiếu " giá trị này - để so sánh ở vị trí đầu tiên và tôi chưa tìm ra cách để có thể nhận được biểu thức đầu vào ... như đối với ReSharper, nó gợi ý tôi chuyển nó thành boolean thay thế ...

Đầu tôi đầy lông tơ tại thời điểm này ...

Cập nhật 2:

Tôi cố gắng tìm ra một cách để có một đoạn mã mà làm việc trong một kho lưu trữ bộ nhớ trên một ứng dụng console. Tôi chưa thử nó với Entity Framework.

public static bool Compare(this double val, string arg) 
    { 
     var arg2 = arg.Replace("<", "").Replace(">", ""); 
     if (arg.Contains("<")) 
      return val < double.Parse(arg2); 

     return val > double.Parse(arg2); 
    } 

Tuy nhiên, tôi rất nghi ngờ đó là những gì tôi là sau khi

Cập nhật 3:

Đúng vậy, sau khi ngồi xuống và nhìn qua biểu thức lambda một lần nữa, trước khi câu trả lời cuối cùng, tôi đi với một cái gì đó tương tự như sau, nó không điền vào các yêu cầu chính xác của "So sánh()" nhưng Đó là một 'quá tải-ish' Trường hợp phương pháp:

public static IQueryable<T> WhereExpression<T>(this IQueryable<T> queryable, Expression<Func<T, double>> predicate, string arg) 
    { 
     var lambda = 
      Expression.Lambda<Func<T, bool>>(Expression.LessThan(predicate.Body, Expression.Constant(double.Parse(50.ToString())))); 

     return queryable.Where(lambda); 
    } 

Tuy nhiên, dù cho đôi mắt của tôi, tất cả mọi thứ dường như hợp lý, tôi nhận được ngoại lệ thời gian chạy của:

System.ArgumentException was unhandled 
    Message=Incorrect number of parameters supplied for lambda declaration 
    Source=System.Core 
    StackTrace: 
     at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters) 
     at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters) 
     at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters) 
     at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters) 

Đây là dòng thủ phạm rõ ràng:

var lambda = 
       Expression.Lambda<Func<T, bool>>(Expression.LessThan(predicate.Body, Expression.Constant(double.Parse(50.ToString())))); 

Tôi rất gần với giải pháp. Nếu tôi có thể bị lỗi đó, tôi tin rằng EF có khả năng dịch nó thành SQL. Nếu không ... tốt, phản ứng cuối cùng có lẽ sẽ đi.

+0

Tôi nghĩ rằng, đó là một phần Update2 của bạn sẽ không thực hiện đối với SQL Server (EF). Bạn đã thử chưa? –

+0

Vâng, đúng vậy. Như tôi đã nghĩ tbh. – NeroS

Trả lời

6

Để tạo ngôn luận, sẽ được dịch sang SQL (eSQL), bạn nên tự tạo Expression theo cách thủ công. Dưới đây là ví dụ cho việc tạo bộ lọc GreaterThan, các bộ lọc khác có thể được thực hiện bằng kỹ thuật tương tự.

static Expression<Func<T, bool>> CreateGreaterThanExpression<T>(Expression<Func<T, decimal>> fieldExtractor, decimal value) 
{ 
    var xPar = Expression.Parameter(typeof(T), "x"); 
    var x = new ParameterRebinder(xPar); 
    var getter = (MemberExpression)x.Visit(fieldExtractor.Body); 
    var resultBody = Expression.GreaterThan(getter, Expression.Constant(value, typeof(decimal))); 
    return Expression.Lambda<Func<T, bool>>(resultBody, xPar); 
} 

private sealed class ParameterRebinder : ExpressionVisitor 
{ 
    private readonly ParameterExpression _parameter; 

    public ParameterRebinder(ParameterExpression parameter) 
    { this._parameter = parameter; } 

    protected override Expression VisitParameter(ParameterExpression p) 
    { return base.VisitParameter(this._parameter); } 
} 

Đây là ví dụ về cách sử dụng.(Giả sử rằng chúng ta có StackEntites bối cảnh EF với tổ chức bộ TestEnitities của TestEntity thực thể)

static void Main(string[] args) 
{ 
    using (var ents = new StackEntities()) 
    { 
     var filter = CreateGreaterThanExpression<TestEnitity>(x => x.SortProperty, 3); 
     var items = ents.TestEnitities.Where(filter).ToArray(); 
    } 
} 

Cập nhật: Đối với sáng tạo của bạn biểu hiện phức tạp, bạn có thể sử dụng mã như thế này: (Giả sử có đã thực hiện CreateLessThanExpressionCreateBetweenExpression chức năng)

static Expression<Func<T, bool>> CreateFilterFromString<T>(Expression<Func<T, decimal>> fieldExtractor, string text) 
{ 
    var greaterOrLessRegex = new Regex(@"^\s*(?<sign>\>|\<)\s*(?<number>\d+(\.\d+){0,1})\s*$"); 
    var match = greaterOrLessRegex.Match(text); 
    if (match.Success) 
    { 
     var number = decimal.Parse(match.Result("${number}")); 
     var sign = match.Result("${sign}"); 
     switch (sign) 
     { 
      case ">": 
       return CreateGreaterThanExpression(fieldExtractor, number); 
      case "<": 
       return CreateLessThanExpression(fieldExtractor, number); 
      default: 
       throw new Exception("Bad Sign!"); 
     } 
    } 

    var betweenRegex = new Regex(@"^\s*(?<number1>\d+(\.\d+){0,1})\s*-\s*(?<number2>\d+(\.\d+){0,1})\s*$"); 
    match = betweenRegex.Match(text); 
    if (match.Success) 
    { 
     var number1 = decimal.Parse(match.Result("${number1}")); 
     var number2 = decimal.Parse(match.Result("${number2}")); 
     return CreateBetweenExpression(fieldExtractor, number1, number2); 
    } 
    throw new Exception("Bad filter Format!"); 
} 
+0

Điều này có vẻ hữu ích hơn. Tôi đã cập nhật câu hỏi ban đầu một lần nữa, với giải pháp một phần của riêng tôi mà ... tốt, vẫn không hoàn toàn hoạt động. – NeroS

+0

Tôi đã triển khai hàm này ngay bây giờ vào phương thức lọc. Có một vài vấn đề với EF mặc dù, đầu tiên tắt "getter" phải không có (MemberExpression) đúc ở phía trước của nó. Nó sẽ chỉ là ngoại lệ. Thứ hai, số regex (đó là điều dễ nhất để sửa chữa) là hơi tắt, nó phải là (? \ d + (\. \ D *)?), Một trong bài viết của bạn nói rằng nó ** yêu cầu ** a " . "tại đây. Nó không giải quyết cho một giá trị không thập phân. Nhưng khác với (y) – NeroS

+0

Về Regex: đúng vậy. Đã cập nhật câu trả lời với * {0,1} * quantifier (giống như *? *). –

4

Một trong những tính năng liếc đầu tiên của trình biên dịch C# có thể thực hiện công việc khó khăn cho bạn. Bạn có thể biết bạn có thể làm điều này:

Func<decimal, bool> totalCostIsUnder50 = d => d < 50m; 

tức là, sử dụng biểu thức lambda để gán Func. Nhưng bạn có biết bạn có thể cũng làm điều này:

Expression<Func<decimal, bool>> totalCostIsUnder50Expression = d => d < 50m; 

có nghĩa là, sử dụng một biểu thức lambda để gán một Expression thể hiện một Func? Nó khá gọn gàng.

Với bạn nói builder

Việc so sánh không phải là vấn đề, đó là chút dễ dàng. Phần khó thực sự trả lại biểu thức

Tôi giả sử bạn có thể điền vào chỗ trống ở đây; giả sử chúng ta vượt qua trong `" < 50" để:

Expression<Func<decimal, bool>> TotalCostCheckerBuilder(string criterion) 
{ 
    // Split criterion into operator and value 

    // when operator is < do this: 
    return d => d < value; 

    // when operator is > do this: 
    return d => d > value; 

    // and so on 
} 

Cuối cùng, để soạn của bạn Expression s cùng với && (và vẫn có một Expression), làm điều này:

var andExpression = Expression.And(firstExpression, secondExpression); 
+0

Không chắc chắn nếu nó thậm chí áp dụng cho tình hình. Đã cập nhật bài đăng gốc có nhận xét. – NeroS

0

Phần khó thực sự là trả về biểu thức.

Dịch các chuỗi thành công trình xây dựng có cấu trúc giống như enums và các lớp học để xác định tài sản, khai thác và lọc:

Enum Parameter 
    TotalCost 
    Required 
End Enum 

Enum Comparator 
    Less 
    More 
    Equals 
End Enum 

Class Criterion 
    Public ReadOnly Parameter As Parameter 
    Public ReadOnly Comparator As Comparator 
    Public ReadOnly Value As Double 

    Public Sub New(Parameter As Parameter, Comparator As Comparator, Value As Double) 
     Me.Parameter = Parameter 
     Me.Comparator = Comparator 
     Me.Value = Value 
    End Sub 
End Class 

Sau đó, một chức năng để tạo ra biểu thức được định nghĩa:

Function CreateExpression(Criteria As IEnumerable(Of Criterion)) As Expression(Of Func(Of Field, Boolean)) 
    Dim FullExpression = PredicateBuilder.True(Of Field)() 

    For Each Criterion In Criteria 
     Dim Value = Criterion.Value 

     Dim TotalCostExpressions As New Dictionary(Of Comparator, Expression(Of Func(Of Field, Boolean))) From { 
      {Comparator.Less, Function(Field) Field.TotalCost < Value}, 
      {Comparator.More, Function(Field) Field.TotalCost > Value}, 
      {Comparator.Equals, Function(Field) Field.TotalCost = Value} 
     } 

     Dim RequiredExpressions As New Dictionary(Of Comparator, Expression(Of Func(Of Field, Boolean))) From { 
      {Comparator.Less, Function(Field) Field.Required < Value}, 
      {Comparator.More, Function(Field) Field.Required > Value}, 
      {Comparator.Equals, Function(Field) Field.Required = Value} 
     } 

     Dim Expressions As New Dictionary(Of Parameter, IDictionary(Of Comparator, Expression(Of Func(Of Field, Boolean)))) From { 
      {Parameter.TotalCost, TotalCostExpressions}, 
      {Parameter.Required, RequiredExpressions}} 

     Dim Expression = Expressions(Criterion.Parameter)(Criterion.Comparator) 

     FullExpression = Expression.And(Expression) 
    Next 

    Return FullExpression 
End Function 

PredicateBuilder lấy here là cần thiết để kết hợp hai biểu thức với toán tử AND.

Cách sử dụng:

Function Usage() As Integer 

    Dim Criteria = { 
     New Criterion(Parameter.TotalCost, Comparator.Less, 50), 
     New Criterion(Parameter.Required, Comparator.More, 5), 
     New Criterion(Parameter.Required, Comparator.Less, 10)} 

    Dim Expression = CreateExpression(Criteria) 
End Function 

Nó sẽ tạo ra biểu hiện chính xác như được cung cấp trong một ví dụ

field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10 
Các vấn đề liên quan