2015-08-17 6 views
5

У меня возникают проблемы при попытке получить значения свойств как Объекты вместо их соответствующих типов. Следующий код выдает это исключение:Entity Framework: выберите свойство как Object

Unable to cast the type 'System.DateTime' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types. 

Этот код прекрасно работает при выборе строки, но не при выборе DateTimes, целочисленными или обнуляемых типов.

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

    public string Name { get; set; } 

    public DateTime CreatedOn { get; set; } 
} 

public class Program 
{ 
    public static void Main(string[] args) 
    { 
     using (var ctx = new MyContext()) 
     { 
      // Property selector: select DateTime as Object 
      Expression<Func<Customer, object>> selector = cust => cust.CreatedOn; 

      // Get set to query 
      IQueryable<Customer> customers = ctx.Set<Customer>(); 

      // Apply selector to set. This throws: 
      // 'Unable to cast the type 'System.DateTime' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.' 
      IList<object> customerNames = customers.Select(selector).Distinct().ToList(); 

     } 
    } 
} 

public class MyContext : DbContext 
{ 

} 

Конечная цель - это общая фильтрация для выбора отдельных значений из любых свойств объекта.

+4

Вы, наконец, называете 'ToList', так почему бы не отбросить DateTime на объект после этого? – Hopeless

+1

Поскольку выражение члена селектора создается во время выполнения, поэтому тип свойства неизвестен во время компиляции. Я мог бы использовать отражение для определения типа, но это было бы сложно для вложенных свойств. – user2346738

ответ

3

Я понимаю, что вы хотите использовать объявление 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). Это означает, что информация о фактическом обратном типе внутреннего выражения полностью потеряна (или, по крайней мере, скрыта очень осторожно). Такой дизайн довольно странный и совершенно неприемлемый. Я не понимаю, почему они это делают. Информация о фактическом обратном типе выражения должна быть легко доступна.

0

Точка linq для сущности - это создание sql-запроса с использованием команд .NET linq. Команды linq для сущностей не предназначены для выполнения, они переводятся только в sql. Таким образом, все внутри этих операторов linq должно быть преобразовано в sql. И sql имеет соответствующие типы для даты, строки и т. Д. Классы понимаются как таблицы, где каждое свойство означает определенный столбец. Но нет понятия объекта в sql, как в .NET, и это источник вашей проблемы. В вашем LinQ запросе вы должны сосредоточиться на создании только запрос, возвращающий правильные данные и сделать бросок в программе:

Expression<Func<Customer, DateTime>> selector = cust => cust.CreatedOn; 

// Get set to query 
IQueryable<Customer> customers = ctx.Set<Customer>(); 

IList<object> customerNames = customers.Select(selector).Distinct().ToList().Cast<object>().ToList(); 

Каждая вещь, что вы пишете в запросе до первого ToList транслируется в SQL-запрос, остальное выполняется в памяти. Поэтому благодаря этому мы перемещаем актерскую часть в память, где это имеет смысл сделать.

+2

Я работаю с user2346738 по этой проблеме, для краткости мы опустили некоторую информацию. Селектор создается во время выполнения, создавая выражение MemberExpression из строки (например, «Country.Name» становится customer => customer.Country.Name), поэтому тип свойства неизвестен во время компиляции. Мы могли бы использовать отражение, чтобы определить тип, но это немного неприятно для вложенных членов. – chrisv

Смежные вопросы