尝试将属性值检索为对象而不是各自的类型时,我遇到了一些麻烦。以下代码抛出此异常:
Unable to cast the type 'System.DateTime' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
此代码在选择字符串时工作正常,但在选择DateTimes,Integers或Nullable类型时则不行。
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
{
}
最终目标是通用过滤,以从任何对象的属性中选择不同的值。
答案 0 :(得分:3)
我知道您希望使用内联Expression
声明以方便的方式选择属性(无需解析表示属性路径和使用Reflection的以点分隔的字符串)。但是,这样做需要显式声明Expression
,我们必须使用显式类型。遗憾的是,object
类型不能用作Expression的返回类型,因为稍后它不能转换为数据库中支持的类型之一。
我认为这里有一个解决方法。我们的想法是将Expression<T,object>
转换为另一个Expression<T,returnType>
,其中returnType
是属性的实际返回类型(由selector
返回)。但是Select
始终需要明确的Expression<T,returnType>
类型,因此{EL}应该在设计时知道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
。这意味着关于内部表达式的实际返回类型的信息完全丢失(或者至少非常小心地隐藏)。这种设计相当奇怪,完全不可接受。我不明白为什么他们这样做。应该可以轻松访问有关表达式实际返回类型的信息。
答案 1 :(得分:0)
linq to entity的要点是使用.NET linq指令创建sql查询。 linq to entities指令不是要执行,它们只被转换为sql。所以这些linq语句中的所有内容都需要转换为sql。并且sql具有适当的日期,字符串等类型。类被理解为表,其中每个属性表示某个列。但是在.NET中没有对象的概念,因为它在.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查询,其余内容在内存中执行。所以,多亏了这一点,我们将演员部分转移到记忆中,这样才有意义。