Comprendo che si desidera utilizzare la dichiarazione in linea Expression
per selezionare la proprietà in un modo conveniente (senza dover analizzare una stringa separata da punti che rappresenta il percorso della proprietà e utilizzando Reflection). Tuttavia, per fare ciò sarà necessario dichiarare esplicitamente lo Expression
e utilizzare il tipo esplicito. Sfortunatamente il tipo object
non può essere utilizzato come tipo di ritorno di Expression perché in seguito non può essere convertito in uno dei tipi supportati nel database.
Penso che ci sia un work-around qui. L'idea è di convertire lo Expression<T,object>
in un altro Expression<T,returnType>
dove returnType
è il tipo di reso effettivo della proprietà (restituito da selector
). Tuttavia, Select
richiede sempre un tipo esplicito di Expression<T,returnType>
che significa che returnType
deve essere noto in fase di progettazione. Quindi è impossibile. Non abbiamo modo di chiamare direttamente Select
. Invece dobbiamo usare Reflection per invocare lo Select
. Il risultato di ritorno è previsto come IEnumerable<object>
che è possibile chiamare ToList()
per ottenere un elenco di oggetti come desiderato.
Ora è possibile utilizzare questo metodo di estensione per IQueryable<T>
:
public static class QExtension
{
public static IEnumerable<object> Select<T>(this IQueryable<T> source,
Expression<Func<T, object>> exp) where T : class
{
var u = exp.Body as UnaryExpression;
if(u == null) throw new ArgumentException("exp Body should be a UnaryExpression.");
//convert the Func<T,object> to Func<T, actualReturnType>
var funcType = typeof(Func<,>).MakeGenericType(source.ElementType, u.Operand.Type);
//except the funcType, the new converted lambda expression
//is almost the same with the input lambda expression.
var le = Expression.Lambda(funcType, u.Operand, exp.Parameters);
//try getting the Select method of the static class Queryable.
var sl = Expression.Call(typeof(Queryable), "Select",
new[] { source.ElementType, u.Operand.Type },
Expression.Constant(source), le).Method;
//finally invoke the Select method and get the result
//in which each element type should be the return property type
//(returned by selector)
return ((IEnumerable)sl.Invoke(null, new object[] { source, le })).Cast<object>();
}
}
Uso: (esattamente come il codice)
Expression<Func<Customer, object>> selector = cust => cust.CreatedOn;
IQueryable<Customer> customers = ctx.Set<Customer>();
IList<object> customerNames = customers.Select(selector).Distinct().ToList();
In un primo momento ho provato l'accesso alla exp.Body.Type
e ho pensato che era il tipo di ritorno effettivo dell'espressione interna. Tuttavia, in qualche modo è sempre System.Object
tranne il caso speciale di string
(quando il tipo di ritorno di accesso alla proprietà è string
). Ciò significa che le informazioni sul tipo di ritorno effettivo dell'espressione interna sono completamente perse (o almeno nascoste con molta attenzione). Quel tipo di design è abbastanza strano e totalmente inaccettabile. Non capisco perché lo facciano. Le informazioni sul tipo di ritorno effettivo dell'espressione avrebbero dovuto essere facilmente accessibili.
Infine si chiama 'ToList', quindi perché non eseguire il casting di DateTime dopo questo? – Hopeless
Poiché l'espressione del membro del selettore viene creata in fase di runtime, il tipo di proprietà non è noto al momento della compilazione. Potrei usare il reflection per determinare il tipo, ma sarebbe complicato per le proprietà nidificate – user2346738