2012-05-15 6 views
8

Estoy en el proceso de crear un sistema de filtrado más elaborado para este gran proyecto nuestro. Uno de los predicados principales es poder pasar comparaciones a través de un parámetro de cadena. Esto expresa en la forma siguiente: "> 50" o "5-10" o "< 123.2"Método de extensión que devuelve la expresión lambda a través de comparar

Lo que tengo (como un ejemplo para ilustrar)

ViewModel:

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

EF modelo:

TotalCost (double) 
Required(double) 

expresión que me gustaría utilizar:

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

expresión que me gustaría recibir:

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

O algo similar a la

Sin embargo ... no tengo idea de por dónde empezar. Yo he reducido a

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

Puede que no sea incluso corregir, pero esto es sobre todo lo que tengo. El generador de la comparación no es el problema, eso es lo fácil. La parte difícil es en realidad devolver la expresión. Nunca intenté devolver expresiones como valores de función. Básicamente, lo que necesito conservar es el campo y devolver una expresión de comparación, más o menos.

¿Algún ayuda? : X

Actualización:

Por desgracia esto no resuelve mi problema. Puede ser porque he estado despierto durante las últimas 23 horas, pero no tengo la menor idea de cómo convertirlo en un método de extensión. Como ya he dicho, lo que me gustaría ... es básicamente una manera de escribir:

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

La forma di forma a cabo esa función (probablemente completamente equivocado) es

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

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

Le falta el " este -algo- valor "para compararlo en primer lugar, y aún no he podido descifrar cómo hacer que sea capaz de obtener una entrada de expresión ... en cuanto a ReSharper, me sugiere que lo convierta en booleano ...

Mi cabeza está llena de pelusa en este momento ...

Actualización 2:

que logró encontrar una manera de tener un pedazo de código que funciona en un repositorio de memoria en una aplicación de consola. Todavía estoy por intentarlo con 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); 
    } 

Sin embargo, dudo mucho que eso es lo que busco

Actualización 3:

derecho, después de sentarse y mirar a través de las expresiones lambda de nuevo, antes de la última respuesta, me vino con algo similar a lo siguiente, no cumple con los requisitos exactos de "Comparar()" pero es un método 'sobrecarga-ish' Donde:

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

Sin embargo, a pesar de mis ojos, todo lo que parece lógico, consigo excepción de ejecución de:

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) 

Esta es la línea culpable obvio:

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

estoy muy cerca de la solución. Si puedo evitar ese error, creo que EF debería ser capaz de traducir eso en SQL. De lo contrario ... bueno, la última respuesta probablemente desaparecerá.

+0

pienso, que su parte update2 no se ejecutará en SQL Server (EF). ¿Lo has intentado? –

+0

Sí, acabo de hacer. Como hubiera pensado, tbh. – NeroS

Respuesta

6

Para generar la expresión, que se traduciría a SQL (eSQL) debe generar Expression manualmente. Aquí está el ejemplo para la creación de filtro GreaterThan GreaterThan, otros filtros se pueden hacer con una técnica similar.

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

Aquí está el ejemplo del uso.(Suponga que tenemos StackEntites contexto EF con TestEnitities conjunto de entidades de TestEntity entidades)

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

Actualización: Para su creación de expresión compleja puede usar un código como éste: (Suponga que ya han hecho CreateLessThanExpression y CreateBetweenExpression funciones)

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

Esto parece más útil. He actualizado la pregunta original nuevamente, con mi propia solución parcial que ... bueno, todavía no funciona del todo. – NeroS

+0

Implementé la función ahora en el método de filtrado. Sin embargo, hay algunos problemas con EF: primero, "getter" no debe tener (MemberExpression) lanzado al frente. Solo será una excepción. En segundo lugar, el número de expresión regular (que es lo más fácil de arreglar) está un poco apagado, debería ser (? \ d + (\. \ D *)?), El de su publicación dice que ** requiere ** a " "aquí. No se conforma con un valor no decimal. Pero aparte de eso (y) – NeroS

+0

Acerca de Regex: es verdad. Respuesta actualizada con cuantificador * {0,1} * (igual que *? *). –

4

Una de las características mágicas a primera vista del compilador C# puede hacer el arduo trabajo por usted. Usted probablemente sabe que puede hacer esto:

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

Es decir, utilizar una expresión lambda para asignar un Func. ¿Pero usted sabe que puede también hacer esto:

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

que es, utilizar una expresión lambda para asignar una Expression que expresa una Func? Es bastante limpio.

Dado que usted dice constructor

La comparación no es la cuestión, que es la parte fácil. La parte dura realmente devuelve la expresión

Supongo que puede completar los espacios en blanco aquí; supongamos que pasamos en `" < 50" a:

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 
} 

Por último, para componer su Expression S, así como && (y aún así tener un Expression), hacer esto:

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

No estoy seguro si es incluso aplicable a la situación. Publicación original actualizada con comentarios. – NeroS

0

la parte difícil es en realidad volviendo la expresión.

Traducir cadenas en construcciones más estructurados como enumeraciones y clases para definir las propiedades, operadores y filtros:

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 

Entonces una función para crear la expresión se define:

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 tomado here es necesario para combinar dos expresiones con el operador AND.

Uso:

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 

Se va a crear exactamente igual que la expresión proporcionada en un ejemplo

field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10 
Cuestiones relacionadas