2012-01-25 20 views
6

Ayer pregunté very similar question, pero no fue hasta hoy que me di cuenta de que la respuesta que acepté no resuelve todos mis problemas. Tengo el siguiente código:¿Cómo puedo crear una propiedad multi-propiedad dinámica en un IEnumerable <T> en tiempo de ejecución?

public Expression<Func<TItem, object>> SelectExpression<TItem>(string fieldName) 
{ 
    var param = Expression.Parameter(typeof(TItem), "item"); 
    var field = Expression.Property(param, fieldName); 
    return Expression.Lambda<Func<TItem, object>>(field, 
     new ParameterExpression[] { param }); 
} 

que se utiliza de la siguiente manera:

string primaryKey = _map.GetPrimaryKeys(typeof(TOriginator)).Single(); 
var primaryKeyExpression = SelectExpression<TOriginator>(primaryKey); 
var primaryKeyResults = query.Select(primaryKeyExpression).ToList(); 

Esto me permite sacar las claves principales de una IQueryable<TUnknown>. El problema es que este código solo funciona con una sola clave principal y necesito agregar soporte para múltiples PK.

Entonces, ¿hay alguna manera de adaptar el método SelectExpression anterior para tomar un IEnumerable<string> (que es mi lista de nombres de propiedades de claves principales) y hacer que el método devuelva una expresión que seleccione esas claves?

I.e. Teniendo en cuenta lo siguiente:

var knownRuntimePrimaryKeys = new string[] { "CustomerId", "OrderId" }` 

Mi Selecto tiene que hacer lo siguiente (en tiempo de ejecución):

var primaryKeys = query.Select(x=> new { x.CustomerId, x.OrderId }); 
+1

El problema es que los tipos anónimos que son los resultados de la selecta statemet se crean en tiempo de compilación .. – m0sa

+1

¿Existe una alternativa a la utilización de un tipo anónimo y todavía lograr lo que necesito? ¿O está de vuelta al tablero de dibujo? – GenericTypeTea

+0

@GenericTypeTea, ¿sería una Tuple una opción aceptable para usted? –

Respuesta

2

usted podría utilizar Tuple<> porque los tipos anónimos deben ser conocidas en tiempo de compilación:

public Expression<Func<TItem, object>> SelectExpression<TItem>(params string[] fieldNames) 
{ 
    var param = Expression.Parameter(typeof(TItem), "item"); 
    var fields = fieldNames.Select(x => Expression.Property(param, x)).ToArray(); 
    var types = fields.Select(x => x.Type).ToArray(); 
    var type = Type.GetType("System.Tuple`" + fields.Count() + ", mscorlib", true); 
    var tuple = type.MakeGenericType(types); 
    var ctor = tuple.GetConstructor(types); 
    return Expression.Lambda<Func<TItem, object>>(
     Expression.New(ctor, fields), 
     param 
    ); 
} 

y luego:

var primaryKeyExpression = SelectExpression<TOriginator>("CustomerId", "OrderId"); 

generará la siguiente expresión:

item => new Tuple<string, string>(item.CustomerId, item.OrderId) 
+1

Desafortunadamente, esto no parece funcionar con un IQueryable de Entity Framework. "Solo los constructores y los inicializadores sin parámetros son compatibles en LINQ to Entities". – GenericTypeTea

+0

@GenericTypeTea, oh, bueno, eso es triste. En este caso, podría usar Reflection.Emit para generar un tipo dinámico en tiempo de ejecución con el número correcto de propiedades y luego crear una expresión que asigne esas propiedades. Aunque parece mucho trabajo. –

+0

Estoy comenzando a preguntarme si es mejor simplemente forzar a la aplicación consumidora a implementar una interfaz que especifique en todos sus objetos EF POCO. Entonces podría hacer que implementen un método GetPrimaryKeySelectExpression ... pero eso llevaría a todo tipo de problemas posibles (sin incluir el hecho de que rompe el principio completo de una clase POCO) además de ser un dolor total en el b ' trasero para que el usuario lo implemente. ¡Columpios y rotondas! – GenericTypeTea

1

Como alguien ya se ha señalado, se está intentando conseguir esencialmente un tipo anónimo construido en tiempo de ejecución, que no se va a trabajar.

¿Hay alguna alternativa al uso de un tipo anónimo y aún así lograr lo que necesito?

Esto realmente depende de lo que usted diga. Las respuestas obvias son usar construcciones en tiempo de ejecución, como un diccionario, una tupla, etc. Pero es probable que usted sea totalmente consciente de esto, así que supongo que quiere un resultado en tiempo de compilación con nombres de campos reales, para que cualquier mal uso de primaryKeys queda atrapado durante la compilación.

Si es así, me temo que su única opción es generar el código relevante antes de la compilación. Esto no es tan malo como podría parecer, pero no es completamente transparente: cuando cambia el esquema, tiene que volver a ejecutar la generación de código de alguna manera.

En nuestra empresa hemos hecho exactamente eso, inspirándonos en SubSonic pero descubriendo que SubSonic en sí mismo no era lo que queríamos. Funcionó bastante bien en mi opinión.

+0

No tengo conocimiento de las claves principales en tiempo de ejecución. El código anterior es utilizado por una aplicación consumidora sobre la que no tengo control. Vea la pregunta anterior para una mejor explicación. – GenericTypeTea

3

No hay una manera fácil de hacer exactamente lo que quiere, porque requeriría crear dinámicamente un nuevo tipo (el compilador crea los tipos anónimos cuando se conocen estáticamente). Si bien es ciertamente factible, probablemente no sea la opción más fácil ...

Puede lograr un resultado similar usando tuples:

public Expression<Func<TItem, object>> SelectExpression<TItem>(string[] propertyNames) 
{ 
    var properties = propertyNames.Select(name => typeof(TItem).GetProperty(name)).ToArray(); 
    var propertyTypes = properties.Select(p => p.PropertyType).ToArray(); 
    var tupleTypeDefinition = typeof(Tuple).Assembly.GetType("System.Tuple`" + properties.Length); 
    var tupleType = tupleTypeDefinition.MakeGenericType(propertyTypes); 
    var constructor = tupleType.GetConstructor(propertyTypes); 
    var param = Expression.Parameter(typeof(TItem), "item"); 
    var body = Expression.New(constructor, properties.Select(p => Expression.Property(param, p))); 
    var expr = Expression.Lambda<Func<TItem, object>>(body, param); 
    return expr; 
} 
+2

Desafortunadamente, esto no parece funcionar con un IQueryable de Entity Framework. "Solo los constructores y los inicializadores sin parámetros son compatibles en LINQ to Entities". – GenericTypeTea

+1

@GenericTypeTea Quizás es obvio, pero resolví este mensaje de error al compilar la expresión antes del uso, es decir .: var primaryKeyResults = query.Select (primaryKeyExpression.Compile()). ToList(); –

+2

@ Jorr.it Su comentario puede ser cierto, pero la consulta no se ejecutará en la base de datos – Tokk

Cuestiones relacionadas