2010-01-18 19 views
27

estoy creando algo de LINQ dinámica y estoy teniendo problemas con la siguiente excepción:Expression.GreaterThan falla si un operando es de tipo anulable, otra es no anulable

El operador binario GreaterThanOrEqual no está definida para el tipos 'System.Nullable`1 [System.DateTime]' y 'System.DateTime'

entiendo por qué, porque mi tipo de campo es anulable y estoy pasando en DateTime.Now esencialmente.

Así que al tratar de resolver este problema he intentado

System.Nullable<DateTime> now; 
now = DateTime.Now; 

Pero el tipo resultante es un objeto no anulable y por lo tanto todavía me da la excepción anteriormente.

¿Alguna sugerencia ?!

Actualización: Para mayor aclaración la variable ahora se convierte en un tipo no anulable cuando se establece en lugar de permanecer como un DateTime anulable por lo que el partido se produce una excepción

Actualización: El código real se puede ver en el proyecto CodePlex:

http://webquarters.codeplex.com/SourceControl/changeset/view/36529#574700

la línea en cuestión es de ~ 145

fExp = Expression.GreaterThanOrEqual(fExpLeft, fExpRight); 
+2

Esto suena como un posible error en la biblioteca subyacente. El algoritmo de resolución de sobrecarga debe tomar una T no anulable y una T anulable y elegir la versión del operador levantada a anulable. ¿Puedes publicar una reproducción pequeña, simple y autónoma del problema? También necesitaré el número de versión del compilador que estás usando. Luego puedo pasarlo a los desarrolladores y probadores responsables de esa función. Si prefiere enviarme un correo electrónico directamente a mí, hay un enlace "contácteme" en mi blog, blogs.msdn.com/ericlippert. ¡Gracias! –

+0

Oh alegría :(no es lo que realmente quería escuchar, te envié un correo electrónico con una muestra –

+0

Muestra tu consulta y menciona qué proveedor estás usando. –

Respuesta

47

El problema aquí es que la biblioteca de expresiones arroja una excepción cuando se le dan dos argumentos de nulability no coincidentes. Aquí hay una reproducción simple:

Expression<Func<DateTime?>> ex1 =()=>DateTime.Now; 
Expression<Func<DateTime>> ex2 =()=>DateTime.Now; 
var ex3 = Expression.GreaterThan(ex1.Body, ex2.Body); 

No tengo claro si esto es un error o no; las reglas de C# requieren que en este escenario, el operando que no acepta nulos se convierta en nulable, y se use la forma de la comparación que se puede elevar a anulable. Sin embargo,, la biblioteca del árbol de expresiones es no requiere seguir las reglas de C# porque, por supuesto, la biblioteca del árbol de expresiones se puede usar para representar expresiones C#, expresiones Python, expresiones JScript, expresiones VB, etc. no puede seguir todas las reglas de todos los idiomas posibles.

Pero independientemente de esto, parece que podría tratarse de un error, así que lo enviaré al equipo del árbol de expresiones para ver qué dicen. Mientras tanto, puedes evitarlo definiendo tu propio método de ayuda que arregla los operandos. Un boceto rápido sería:

static Expression MyGreaterThan(Expression e1, Expression e2) 
    { 
     if (IsNullableType(e1.Type) && !IsNullableType(e2.Type)) 
      e2 = Expression.Convert(e2, e1.Type); 
     else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type)) 
      e1 = Expression.Convert(e1, e2.Type); 
     return Expression.GreaterThan(e1, e2); 
    } 
    static bool IsNullableType(Type t) 
    { 
     return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>); 
    } 

Sin embargo, nótese que este no comprueba que e1 y e2 de tipo sólo se diferencian en la capacidad de nulos; si pasa una nullable int y una doble expresión no nulable, suceden cosas malas. Lo dejo como ejercicio para implementar la mejor lógica que verifica si las dos expresiones son del tipo que solo difiere por la capacidad de anulación.

+1

A [LINQPad] (http: //www.linqpad. net /) ejemplo con algunos controles agregados: https://gist.github.com/kappa7194/21c3390130f3e8d54fc2 – Albireo

1

No estoy seguro de cuál es exactamente su código, pero para obtener la versión no nulable de un Nullable, llame a su miembro .Value.

+0

Ya no es un tipo que no admite nulos, por lo tanto, no tiene HasValue, etc. –

2

donde quiera que su COMPARACIÓN es, cambiar el comparar de esta manera:

(nullableDT >= DT) 

Para

(nullableDT != null && nullableDT.Value >= DT) 

Editar:

Según usted comenta, escribir una función que toma 2 objetos, dentro de la función compruebe si son tipos anulables, y compruebe nulo, luego compare los valores. Esta función probablemente usará un código similar a ^.

Sin embargo, esto empieza a sonar como si tuvieras un problema subyacente más grande. O bien obtiene datos incorrectos (es decir, su código en otro lugar devuelve datos que no deberían ser, no es un problema en el código, sino un problema en su lógica) o su suposición de que puede comparar estos objetos no es válido. Lo cual, una vez más, es un error de lógica.

Creo que es posible que deba dar un paso atrás en este momento. Si publica más código, es posible que podamos ayudarlo un poco más.

+0

No puedo, como dije, esta es una consulta dinámica de linq, así que nunca sé qué lado de las ecuaciones están o no consistentes en miembros con nulos, sin un poco de reflexión adicional –

+0

Ver mi edición anterior. – TJMonk15

+0

sí, es probable que tenga razón, por lo tanto, publicar aquí. En este caso, un método como este será un poco de código, pero definitivamente tendré que considerarlo como –

0

Eso es divertido,

He intentado tanto variación de este código:

System.Nullable<DateTime> now = new System.Nullable<DateTime>(); 
    now = DateTime.Now; 

y

System.Nullable<DateTime> now; 
    now = DateTime.Now; 

y ambos de ellos trabajaron sin errores.

Luego vuelvo a leer su pregunta. En realidad, la respuesta todavía está en la propiedad "Valor". Inicializando la variable si está bien, pero si lo hace:

(ahora> = DateTime.Now) en la consulta Linq obtendrá un error. Debería ser (ahora.Valor> = DateTime.Now)

+0

. No puede hacer esto como ahora. El valor es de solo –

2

Encontré una solución que funciona dentro del framework .Net. Aquí un método que aún está en curso, pero funciona, y funciona para LINQ a Entidades:

public PagedViewModel<T> Filter<TValue>(Expression<Func<T, TValue>> predicate, FilterType filterType = FilterType.Equals) { 
     var name = (predicate.Body as MemberExpression ?? ((UnaryExpression)predicate.Body).Operand as MemberExpression).Member.Name;    
     var value = Expression.Constant(ParamsData[name].To<TValue>(), typeof (T).GetProperty(name).PropertyType);       

     // If nothing has been set for filter, skip and don't filter data. 
     ViewData[name] = m_QueryInternal.Distinct(predicate.Compile()).ToSelectList(name, name, ParamsData[name]); 
     if (string.IsNullOrWhiteSpace(ParamsData[name])) 
      return this; 

     var nameExpression = Expression.Parameter(typeof(T), name); 
     var propertyExpression = Expression.Property(nameExpression, typeof(T).GetProperty(name)); 

     // Create expression body based on type of filter 
     Expression expression; 
     MethodInfo method; 
     switch(filterType) { 
      case FilterType.Like: 
       method = typeof(string).GetMethod("Contains", new[] { typeof(string) }); 
       expression = Expression.Call(propertyExpression, method, value); 
       break; 
      case FilterType.EndsWith: 
      case FilterType.StartsWith: 
       method = typeof(string).GetMethod(filterType.ToString(), new[] { typeof(string) }); 
       expression = Expression.Call(propertyExpression, method, value); 
       break; 
      case FilterType.GreaterThan: 
       expression = Expression.GreaterThan(propertyExpression, value);      
       break; 
      case FilterType.Equals: 
       expression = Expression.Equal(propertyExpression, value); 
       break; 
      default: 
       throw new ArgumentException("Filter Type could not be determined"); 
     }    

     // Execute the expression against Query. 
     var methodCallExpression = Expression.Call(
      typeof (Queryable), 
      "Where", 
      new[] { Query.ElementType }, 
      Query.Expression, 
      Expression.Lambda<Func<T, bool>>(expression, new[] { nameExpression })); 

     // Filter the current Query data. 
     Query = Query.Provider.CreateQuery<T>(methodCallExpression);    

     return this; 
    } 

Lo que sucede aquí es el valor constante de la expresión se convierte en el tipo de predicado. Para mayor claridad este método se llama por:

var paramsData = new NameValueCollection { { "CreatedOn", DateTime.Today.ToString() } }; 
     var model = m_data.ToPagedList(new ViewDataDictionary(), paramsData, 1, 10, null, x => x.LastName) 
          .Filters(Criteria<TrainerProfile>.New(x => x.CreatedOn, FilterType.GreaterThan)) 
          .Setup(); 

Este código se basa MVC 3, por lo tanto, algunos de los ParamsData [ ""] que es (Request.Params).

+0

** x => x.CreatedOn es el System.Nullable Novak

Cuestiones relacionadas