Я понимаю, что вы хотите использовать объявление inline Expression
, чтобы выбрать свойство удобным способом (без необходимости разбора строки, разделенной точками, представляющей путь свойства и используя Reflection). Однако для этого требуется, чтобы Expression
был явно объявлен, и мы должны использовать явный тип. К сожалению, тип object
не может использоваться в качестве возвращаемого типа выражения, потому что позже он не может быть преобразован в один из поддерживаемых типов в базе данных.
Я думаю, что здесь есть работа. Идея заключается в том, что мы преобразуем Expression<T,object>
в другой Expression<T,returnType>
, где returnType
- это фактический тип возврата недвижимости (возвращается selector
). Однако Select
всегда требует явного типа Expression<T,returnType>
. Значение returnType
должно быть известно во время разработки. Так что это невозможно. У нас нет возможности напрямую позвонить Select
. Вместо этого мы должны использовать Reflection для вызова Select
. Результат возврата ожидается как IEnumerable<object>
, который затем вы можете вызвать ToList()
, чтобы получить список объектов как то, что вы хотите.
Теперь вы можете использовать этот метод расширения для 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>();
}
}
Использование: (точно так, как ваш код)
Expression<Func<Customer, object>> selector = cust => cust.CreatedOn;
IQueryable<Customer> customers = ctx.Set<Customer>();
IList<object> customerNames = customers.Select(selector).Distinct().ToList();
Сначала я попытался получить доступ к exp.Body.Type
и думал, что это было фактический тип возврата внутреннего выражения. Однако почему-то это всегда System.Object
, за исключением специального случая string
(когда возвращаемый тип доступа к объекту - string
). Это означает, что информация о фактическом обратном типе внутреннего выражения полностью потеряна (или, по крайней мере, скрыта очень осторожно). Такой дизайн довольно странный и совершенно неприемлемый. Я не понимаю, почему они это делают. Информация о фактическом обратном типе выражения должна быть легко доступна.
Вы, наконец, называете 'ToList', так почему бы не отбросить DateTime на объект после этого? – Hopeless
Поскольку выражение члена селектора создается во время выполнения, поэтому тип свойства неизвестен во время компиляции. Я мог бы использовать отражение для определения типа, но это было бы сложно для вложенных свойств. – user2346738