2010-04-11 10 views
54

Si tengo un producto.Acceda al valor de una expresión miembro

var p = new Product { Price = 30 }; 

y tengo la siguiente consulta LINQ.

var q = repo.Products().Where(x=>x.Price == p.Price).ToList() 

En un proveedor IQueryable, aparece un MemberExpression atrás para la p.Price que contiene una expresión constante, sin embargo, me parece que no puede obtener el valor "30" atrás de ella.

Actualización He intentado esto pero parece que no funciona.

var memberExpression = (MemberExpression)GetRootConstantExpression(m); 
var fi = (PropertyInfo)memberExpression.Member; 
var val = fi.GetValue(((ConstantExpression)memberExpression.Expression).Value, null); 

Cheers.

Respuesta

84

Puede compilar e invocar una expresión lambda cuyo cuerpo es el acceso de los miembros:

private object GetValue(MemberExpression member) 
{ 
    var objectMember = Expression.Convert(member, typeof(object)); 

    var getterLambda = Expression.Lambda<Func<object>>(objectMember); 

    var getter = getterLambda.Compile(); 

    return getter(); 
} 

La evaluación local es una técnica común al analizar los árboles de expresión. LINQ to SQL hace esto exactamente en bastantes lugares.

+0

Obtenga este error La expresión de tipo 'System.Double' no se puede usar para el tipo de retorno 'System.Object' cuando se resuelve en un doble en el ejemplo que utilicé – Schotime

+2

Tuve que agregar: var expression = Expression.Convert (member, typeof (object)); en la primera línea de la función para corregir el error anterior con doble conversión! – Schotime

+0

Ah sí, a veces me olvido de que tiene que ser explícito con los árboles de expresiones donde C# es implícito (como las conversiones). Me alegra que esto funcione para ti. –

1

q es del tipo List<Product>. La Lista no tiene una propiedad de Precio, solo los Productos individuales.

El primer o el último producto tendrá un precio.

q.First().Price 
q.Last().Price 

Si sabe que sólo hay una en la colección también se puede aplanar el uso individual

q.Single().Price 
+1

Ahhhh ... no. repo.Products() devuelve un IQueryable . – Schotime

+0

Sí, pero '.ToList()' al final lo convierte en una lista. –

+1

Independientemente de si se trata de una Lista o una IQueryable, aún puede usar Primero, Último o Único, pero no se equivoque, 'repo.Products.ToList()' es definitivamente una Lista –

0

Y ¿qué estás tratando de lograr?

Debido a acceder al valor de Price, que tendría que hacer algo como:

var valueOfPrice = q[0].Price; 
+0

estoy tratando de pasar la expresión a texto sin formato, y necesito evaluar el precio p. "en" 30 " – Schotime

1

¿Se puede utilizar el siguiente:

var price = p.Price; 
var q = repo.Products().Where(x=>x.Price == price).ToList() 
+0

Esto funcionará, sin embargo, sería genial si esto no fuera necesario. ¿Linq-2-Sql es compatible con la sintaxis que intento lograr? – Schotime

23

La expresión constante va a apuntar a una clase de captura generada por el compilador. No he incluido los puntos de decisión, etc., pero aquí es cómo conseguir 30 desde que:

var p = new Product { Price = 30 }; 
Expression<Func<Product, bool>> predicate = x => x.Price == p.Price; 
BinaryExpression eq = (BinaryExpression)predicate.Body; 
MemberExpression productToPrice = (MemberExpression)eq.Right; 
MemberExpression captureToProduct = (MemberExpression)productToPrice.Expression; 
ConstantExpression captureConst = (ConstantExpression)captureToProduct.Expression; 
object product = ((FieldInfo)captureToProduct.Member).GetValue(captureConst.Value); 
object price = ((PropertyInfo)productToPrice.Member).GetValue(product, null); 

price es ahora 30. Tenga en cuenta que asumo que Price es una propiedad, pero en realidad escribiría un método GetValue que maneja propiedad/campo.

+0

ahhhhh .... Estaba a un nivel de distancia. ¡¡¡Leyenda!!! – Schotime

+0

Si tuviera otro nivel de anidación en el objeto, ¿cambiaría algo? p.ej. p.Product.Price – Schotime

+3

@Schotime - de hecho lo haría. Para manejar esto de una manera genérica, vea 'Evaluate' y' TryEvaluate' aquí: http://code.google.com/p/protobuf-net/source/browse/trunk/protobuf-net.Extensions/ServiceModel/Client/ ProtoClientExtensions.cs –

26
MemberExpression right = (MemberExpression)((BinaryExpression)p.Body).Right; 
Expression.Lambda(right).Compile().DynamicInvoke(); 
+0

La forma más rápida y concisa de obtener el resultado. – iggymoran

+1

No puedo creer que algo que involucre 'DynamicInvoke' pueda ser * el más rápido * @ggymoran lo hayas probado? ¿O quisiste decir más rápido? ;) – nawfal

+0

Lo más rápido para escribir y más fácil de entender exactamente qué está pasando. DynamicInvoke termina usando el reflejo para ejecutarlo y no es la cosa más rápida del mundo.La respuesta de Bryan Watts soluciona este problema obteniendo un func y ejecutándolo (solo con una invocación). Cuando respondí por primera vez esta respuesta, fue más fácil entender lo que estaba pasando. – iggymoran

1

Usando Expression.Lambda(myParameterlessExpression).Compile().Invoke() tiene varios inconvenientes:

  • .Compile() es lenta. Puede llevar varios milisegundos completar incluso fragmentos de expresión pequeños. Sin embargo, la llamada Invoke es superrápida, tarda solo unos pocos nanosegundos para expresiones aritméticas simples o accesos de miembros.
  • .Compile() generará (emitirá) código MSIL.Eso puede sonar perfecto (y explica la excelente velocidad de ejecución) pero el problema es: ese código ocupa memoria, que no puede liberarse antes de que la aplicación finalice, ¡incluso cuando el GC recolectó la referencia de delegado!

Uno puede evitar Compile() en conjunto para evitar estos problemas o almacenar en caché los delegados compilados para volver a utilizarlos. This pequeña biblioteca mío ofrece tanto interpretación de Expressions, así como compilación caché, en donde todas las constantes y los cierres de la expresión son reemplazados por parámetros adicionales de forma automática, que luego se vuelve a insertar en un cierre, que se devuelve a la usuario. Ambos procesos están bien probados, se utilizan en producción, ambos tienen sus pros y sus contras, pero son más de 100 veces más rápidos que Compile() y evitan la pérdida de memoria.

0

Si tuviera una clase:

public class Item 
{ 
    public int Id { get; set; } 
} 

y una instancia del objeto:

var myItem = new Item { Id = 7 }; 

Usted puede obtener el valor de Id utilizando una expresión usando el siguiente código:

Expression<Func<Item, int>> exp = x => x.Id; 
var me = exp.Body as MemberExpression; 
var propInfo = me.Member as PropertyInfo; 
var value = propInfo.GetValue(myItem, null); 

valor contendrá "7"

Cuestiones relacionadas