Estoy construyendo un generador de consultas basado en LINQ.Métodos de Calling System.Linq.Queryable using types resolved at runtime
Una de las funciones es poder especificar una proyección arbitraria del lado del servidor como parte de la definición de la consulta. Por ejemplo:
class CustomerSearch : SearchDefinition<Customer>
{
protected override Expression<Func<Customer, object>> GetProjection()
{
return x => new
{
Name = x.Name,
Agent = x.Agent.Code
Sales = x.Orders.Sum(o => o.Amount)
};
}
}
Dado que el usuario debe entonces ser capaz de ordenar en las propiedades de proyección (en oposición a las propiedades de clientes), I recrear la expresión como un Func<Customer,anonymous type>
en lugar de Func<Customer, object>
:
//This is a method on SearchDefinition
IQueryable Transform(IQueryable source)
{
var projection = GetProjection();
var properProjection = Expression.Lambda(projection.Body,
projection.Parameters.Single());
En para devolver la consulta proyectado, me encantaría ser capaz de hacer esto (que, de hecho, funciona en una prueba de concepto casi idéntica):
return Queryable.Select((IQueryable<TRoot>)source, (dynamic)properProjection);
TRoot es el parámetro de tipo en SearchDefinition. Esto da lugar a la siguiente excepción:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
The best overloaded method match for
'System.Linq.Queryable.Select<Customer,object>(System.Linq.IQueryable<Customer>,
System.Linq.Expressions.Expression<System.Func<Customer,object>>)'
has some invalid arguments
at CallSite.Target(Closure , CallSite , Type , IQueryable`1 , Object)
at System.Dynamic.UpdateDelegates.UpdateAndExecute3[T0,T1,T2,TRet]
(CallSite site, T0 arg0, T1 arg1, T2 arg2)
at SearchDefinition`1.Transform(IQueryable source) in ...
Si se mira de cerca, es inferir los parámetros genéricos de forma incorrecta: Customer,object
en lugar de Customer,anonymous type
, que es el tipo real de la expresión properProjection
(doble marcado)
Mi solución alternativa es usar la reflexión. Pero con argumentos genéricos, que es un verdadero desastre:
var genericSelectMethod = typeof(Queryable).GetMethods().Single(
x => x.Name == "Select" &&
x.GetParameters()[1].ParameterType.GetGenericArguments()[0]
.GetGenericArguments().Length == 2);
var selectMethod = genericSelectMethod.MakeGenericMethod(source.ElementType,
projectionBody.Type);
return (IQueryable)selectMethod.Invoke(null, new object[]{ source, projection });
¿Alguien sabe de una mejor manera?
actualización: la razón por la dynamic
falla es que los tipos anónimos se definen como internal
. Es por eso que funcionó usando un proyecto de prueba de concepto, donde todo estaba en el mismo ensamblaje.
Estoy tranquilo con eso. Todavía me gustaría encontrar una manera más clara de encontrar la sobrecarga correcta Queryable.Select
.
es la llamada a 'ParameterRebinder.ReplaceParameter' realmente necesario ? El cuerpo de expresión ya tiene el tipo correcto, por lo que cuando se reconstruye la expresión, tendrá los tipos correctos. Mis propias pruebas parecen estar funcionando aquí. –
@JeffM: La llamada es necesaria para reemplazar el parámetro de la expresión lambda original en el inicializador de tipo anónimo; de lo contrario, obtendría 'variable 'x' del tipo 'Cliente' al que se hace referencia desde el alcance '', pero no está definido' .Probablemente debería crear un caso de prueba completo, ya que también funcionó para mí en un proyecto de prueba de concepto. –
Oh, olvidé que usaste una instancia de parámetro diferente para reconstruir tu expresión. Mis pruebas simplemente reutilizan el parámetro y el cuerpo existentes en una nueva expresión lambda (y funciona). ¿Te estaría haciendo el mismo trabajo? –