实体框架:选择属性为Object

use*_*738 5 c# linq linq-to-entities entity-framework

尝试将属性值检索为对象而不是各自的类型时,我遇到了一些麻烦.以下代码抛出此异常:

Unable to cast the type 'System.DateTime' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
Run Code Online (Sandbox Code Playgroud)

选择字符串时此代码可以正常工作,但选择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
{

}
Run Code Online (Sandbox Code Playgroud)

最终目标是通用过滤,以从任何对象的属性中选择不同的值.

Hop*_*ess 3

我知道您希望使用内联Expression声明以方便的方式选择属性(无需解析表示属性路径的点分隔字符串并使用反射)。然而,这样做需要Expression显式声明,并且我们必须使用显式类型。不幸的是,该object类型不能用作表达式的返回类型,因为稍后它无法转换为数据库中支持的类型之一。

我认为这里有一个解决方法。我们的想法是,我们将把 转换Expression<T,object>为另一个Expression<T,returnType>,其中returnType是属性的实际返回类型(由selector)返回。然而,总是要求在设计时应该知道明确的含义Select类型。所以这是不可能的。我们没有办法直接打电话。相反,我们必须使用反射来调用. 返回结果预计为一个,然后您可以调用该结果来获取您想要的对象列表。Expression<T,returnType>returnTypeSelectSelectIEnumerable<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>();
    }        
}
Run Code Online (Sandbox Code Playgroud)

用法:(与您的代码完全相同)

Expression<Func<Customer, object>> selector = cust => cust.CreatedOn;
IQueryable<Customer> customers = ctx.Set<Customer>();
IList<object> customerNames = customers.Select(selector).Distinct().ToList();
Run Code Online (Sandbox Code Playgroud)

起初我尝试访问 theexp.Body.Type并认为它是内部表达式的实际返回类型。然而,不知怎的,它总是System.Object除了特殊情况string(当属性访问的返回类型是string)。这意味着有关内部表达式的实际返回类型的信息完全丢失(或至少非常小心地隐藏)。这种设计相当奇怪,完全令人无法接受。我不明白他们为什么这样做。有关表达式实际返回类型的信息应该很容易访问。