【问题标题】:IQueryable<T>.OrderBy<T, object> throws: Unable to cast the type 'System.Int16' to type 'System.Object'IQueryable<T>.OrderBy<T, object> 抛出:无法将类型“System.Int16”转换为类型“System.Object”
【发布时间】:2015-07-06 08:26:19
【问题描述】:

我们正在使用一个 GenericCriteria 类,该类用于查询 IQueryable 而无需直接访问它。基本上,查询构建在“业务”层并传递到“数据访问”层。为了防止重复,它需要尽可能通用。

这是 GenericCriteria 类的简化版本:

public class GenericCriteria<T>
{
    public Expression<Func<T, bool>> Where { get; set; }
    public Expression<Func<T, object>> OrderBy { get; set; }
}

解析条件是通过 IQueryable 上的扩展方法完成的:

public static IQueryable<T> ResolveCriteria<T>(this IQueryable<T> query, GenericCriteria<T> criteria)
{
    query = query.Where(criteria.Where);
    query = query.OrderBy(criteria.OrderBy);
    return query;
}

这是它在数据层中的解析方式:

public ICollection<Person> GetPersons(GenericCriteria<Person> criteria)
{
    using (var context = new EFContext())
    {
        return context.Persons
            .AsQueryable()
            .ResolveCriteria(criteria)
            .ToList();
    }
}

其用法示例:

var criteria = new GenericCriteria<Person>();
criteria.Where = c => c.Age > 20;
criteria.OrderBy = c => c.Age;

var persons = Data.Instance.GetPersons(criteria);

问题是Entity Framework无法处理OrderBy表达式的object类型,抛出异常:

Unable to cast the type 'System.Int16' to type 'System.Object'

所以我现在想弄清楚的是如何让 GenericCriteria 类接受某种通用 OrderBy 构造,以便 EF 能够完成它的工作。

我应该寻找什么?

更新:

如果不清楚,我宁愿避免:

public class GenericCriteria<T, TKey>
{
    public Expression<Func<T, bool>> Where { get; set; }
    public Expression<Func<T, TKey>> OrderBy { get; set; }
}

因为该类的用户可能并不总是想要订购,所以要求一个类型似乎有点脏。

【问题讨论】:

  • 为什么你的 orderBy 接口属性不是通用的?
  • 你有没有初始化/填充条件的例子?
  • 可以使用dynamic关键字吗?
  • 可以通过创建表达式访问者来重写 OrderBy 表达式以将强制转换为正确的类型,但这并不简单。我从来没有理解将 IQueryables 隐藏在堆栈中的驱动力,你最终只是编写了大量代码来拥有一个残缺的 Linq 版本
  • .net 中有一个名为 ExpressionVisitor 的基类,您可以从它继承来解析和重写表达式。您可以让它按表达式进行排序,获取最终属性(您可以按嵌套属性排序),确定其类型,然后将强制转换放入它生成的新表达式中,然后在 OrderBy 中使用它。这里有一个介绍pelebyte.net/blog/2011/05/13/…

标签: c# linq entity-framework generics


【解决方案1】:

既然你不希望GenericCriteria 有两个类型参数,而OrderBy 坚持两个类型参数,也许最好的方法是在ResolveCriteria 中做一些肮脏的工作,如下所示:

public static IQueryable<T> ResolveCriteria<T>(this IQueryable<T> query, GenericCriteria<T> criteria)
{
  query = query.Where(criteria.Where);

  var t = criteria.OrderBy;
  var b = t.Body;
  if (b.NodeType == ExpressionType.Convert &&
      ((UnaryExpression)b).Type == typeof(Object)) {
    // Handle simple types, such as short, int, long, etc.
    var bb = ((UnaryExpression)b).Operand;
    var tt = Expression.Lambda(bb, t.Parameters);
    if (bb.Type == typeof(short))
      query = query.OrderBy((Expression<Func<T, short>>)tt);
    else if (bb.Type == typeof(int))
      query = query.OrderBy((Expression<Func<T, int>>)tt);
    else if (...)
      ...
  } else
    // Handle non-simple types, such as string.
    query = query.OrderBy(t);

  return query;
}

每个简单类型都需要一个“else if”;幸运的是没有很多。简单类型列表请参考C# Simple Types

【讨论】:

    【解决方案2】:

    我将重写Criteria如下,

    public class GenericCriteria<T>
    {
         public List<Func<IQueryable<T>,IQueryable<T>>> 
              List { get; private set;}
    
         public GenericCriteria<T>(){
             List = new List<Func<IQueryable<T>,IQueryable<T>>>();
         }
    
    }
    

    解决

    public static IQueryable<T> ResolveCriteria<T>(this IQueryable<T> 
           query, GenericCriteria<T> criteria)
    {
        foreach(var exp in criteria.List){
           query = exp(query);
        }
        return query;
    }
    

    用法

    var criteria = new GenericCriteria<Person>();
    criteria.List.Add( q => q.Where( c => c.Age > 20) );
    criteria.List.Add( q => q.OrderBy( c => c.Age ));
    
    var persons = Data.Instance.GetPersons(criteria);
    

    【讨论】:

    • 谢谢。我一直在玩这个想法(使用 Func, IQueryable> 作为参数)。这种方法的缺点是我需要引用实体框架才能在 System.Data.Entity 中使用扩展方法“包含”。不是最糟糕的问题,但更愿意避免。
    猜你喜欢
    • 2012-11-15
    • 2010-10-19
    • 2022-11-02
    • 1970-01-01
    • 1970-01-01
    • 2019-12-02
    • 1970-01-01
    • 2012-08-04
    • 1970-01-01
    相关资源
    最近更新 更多