【问题标题】:Return Expression from a method to be used in OrderBy从要在 OrderBy 中使用的方法返回表达式
【发布时间】:2017-07-24 09:47:05
【问题描述】:

我想编写一个被子类覆盖的抽象方法,该方法的作用是返回一个表达式,以便随后在 LINQ OrderBy() 中使用。像这样的:

注意:Message 继承自 Notes 类,MyModel 继承自 MsgModel

public class Notes
{
    // abstract definition; using object so that I can (I hope) order by int, string, etc.
    public abstract Expression<Func<MsgModel, object>> OrderByField();
    // ...

    private string GetOrderByFieldName()
    {
        // I am not sure how to write this
        // This is my problem 2. Please see below for problem 1 :-(

        var lambda = OrderByField() as LambdaExpression;

        MemberExpression member = lambda.Body as MemberExpression;

        PropertyInfo propInfo = member.Member as PropertyInfo;

        return propInfo.Name;
    }
}

public class Message : Notes
{
    // second type parameter is object because I don't know the type
    // of the orderby field beforehand
    public override Expression<Func<MyModel, object>> OrderByField()
    {
        return m => m.item_no;
    }
}

现在如果我尝试通过这种方式订购:

var orderedQuery = myQueryable.OrderBy(OrderByField());

我收到此错误:

'无法将类型“System.Int32”转换为类型“System.Object”。 LINQ to Entities 仅支持转换 EDM 基元或枚举类型。'

我可以马上说类型参数对象是问题的原因。因此,当我将类型参数更改为 int 时,只要我按 int 字段(例如字段item_no)排序,它就可以正常工作。

第一季度。我怎样才能让它工作?当然,我可以使用字符串属性OrderByField 而不是那个表达式返回方法并按它排序,可能是通过为 IQueryable 编写一些扩展方法(可能使用this great answer)。但是我想在设置顺序时有更多的智能感知。

第二季度。如何从OrderByField() 方法返回的表达式中按列获取订单名称。显然,我尝试过的方法不起作用。 member 总是为空。


编辑:我对方法的类型参数做了一些更改。很抱歉没有第一次这样做。

【问题讨论】:

  • 你能不能将Notes 设为从该表达式返回的数据类型通用?
  • @Damien 我不确定我是否理解你的意思:-(你能说得更具体一点吗?
  • 如果您的意思是Expression&lt;Func&lt;MsgModel, T&gt;&gt; OrderByField&lt;T&gt;(),那么我会收到此错误:无法将 lambda 表达式转换为预期的委托类型,因为块中的某些返回类型不能隐式转换为委托返回类型
  • 嗯,你可以这样定义它(Expression&lt;Func&lt;Notes, object&gt;&gt;),但你不能直接在OrderBy使用它。如果您需要 OrderBy 的智能感知,那么为什么不使用 OrderBy 本身呢?
  • 我的意思是,它不应该被建模为抽象的Expression&lt;Func&lt;...&gt;&gt;,而是抽象的Order方法,例如public abstract IOrderedQueryable&lt;MsgModel&gt; Order(IQueryable&lt;MsgModel&gt; source);return source.OrderBy(m =&gt; m.item_no);这样的实现

标签: c# linq lambda expression-trees


【解决方案1】:

显然Expression&lt;Func&lt;T, object&gt;&gt; 不等同于Expression&lt;Func&lt;T, K&gt;&gt;,因此不能直接替换Queryable.OrderBy&lt;T, K&gt; 和类似方法所需的后者。

仍然可以在Expression 类的帮助下通过Expression.Lambda 方法创建非泛型LambdaExpression 并动态发出对相应Queryable 方法的调用来使其工作。

这是封装在自定义扩展方法中的所有内容(我对How to use a string to create a EF order by expression? 的回答的修改版本):

public static partial class QueryableExtensions
{
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "OrderBy");
    }
    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "OrderByDescending");
    }
    public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "ThenBy");
    }
    public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "ThenByDescending");
    }
    private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector, string method)
    {
        var parameter = keySelector.Parameters[0];
        var body = keySelector.Body;
        if (body.NodeType == ExpressionType.Convert)
            body = ((UnaryExpression)body).Operand;
        var selector = Expression.Lambda(body, parameter);
        var methodCall = Expression.Call(
            typeof(Queryable), method, new[] { parameter.Type, body.Type },
            source.Expression, Expression.Quote(selector));
        return (IOrderedQueryable<T>)source.Provider.CreateQuery(methodCall);
    }
}

这里的一个重要细节是Expression&lt;Func&lt;T, object&gt;&gt; 引入了Expression.Convert 用于返回值类型的表达式,因此需要从实际的lambda body 中剥离它,这是通过以下部分完成的代码:

var body = keySelector.Body;
if (body.NodeType == ExpressionType.Convert)
    body = ((UnaryExpression)body).Operand;

【讨论】:

    【解决方案2】:

    您的Notes 类需要几个泛型类型。第一个是您可以从中派生,并且仍然允许表达式在派生类上进行过滤。第二个是指定您希望用于订购的属性的类型。例如:

    public abstract class Notes<T, TProperty> where T : Notes<T, TProperty>
    {
        public abstract Expression<Func<T, TProperty>> OrderByField();
    
        public string GetOrderByFieldName()
        {
            //snip
        }
    }
    
    public class Message : Notes<Message, int>
    {
        public int item_no { get; set; }
    
        public override Expression<Func<Message, int>> OrderByField()
        {
            return m => m.item_no;
        }
    }
    

    这也应该允许GetOrderByFieldName 方法工作。

    【讨论】:

    • 感谢您的回答,但我的项目结构不允许我为 Notes 类创建 多种泛型类型,如果您的解决方案是确实是唯一的解决方案,那么我可能需要寻找GetOrderByFieldName 方法实现的解决方法。
    • 如果没有泛型类型,您的代码甚至无法编译。
    • 那我想我会为GetOrderByFieldName寻找一些B计划。
    • 不仅如此,还不能在Message 类中用不同的类型覆盖OrderByField
    • 在看到您的回答之前,我在想是否有办法在具有不同类型参数但子类的基类中定义抽象 OrderBy 方法的 pack将需要覆盖其中一个包,具体取决于它将使用的 order by 字段的类型,这显然是不可能的,而且我知道我听起来很愚蠢!直到我看到你的回答,我才想到泛型基类的想法。
    【解决方案3】:

    这是一个(几乎)不使用反射或表达式的解决方案: 它利用了排序 LINQ 函数在其结果中只有一种泛型类型这一事实。

    1、创建一个接口(只有一个泛型参数)和一个实现(有两个泛型参数):

    public interface ISortCrit<TSource>
    {
        string SortFieldName { get; }
    
        IOrderedEnumerable<TSource> MakeOrderBy(IEnumerable<TSource> source);
        IOrderedEnumerable<TSource> MakeOrderByDescending(IEnumerable<TSource> source);
        IOrderedEnumerable<TSource> MakeThenBy(IOrderedEnumerable<TSource> source);
        IOrderedEnumerable<TSource> MakeThenByDescending(IOrderedEnumerable<TSource> source);
    
        IOrderedQueryable<TSource> MakeOrderBy(IQueryable<TSource> source);
        IOrderedQueryable<TSource> MakeOrderByDescending(IQueryable<TSource> source);
        IOrderedQueryable<TSource> MakeThenBy(IOrderedQueryable<TSource> source);
        IOrderedQueryable<TSource> MakeThenByDescending(IOrderedQueryable<TSource> source);
    }
    
    public class SortCrit<TSource, TSort> : ISortCrit<TSource>
    {
        private readonly Expression<Func<TSource, TSort>> _sortExpression;
        private readonly Lazy<Func<TSource, TSort>> _sortDelegate;
        private readonly Lazy<string> _sortFieldName;
    
        public SortCrit(Expression<Func<TSource, TSort>> sortExpression)
        {
            _sortExpression = sortExpression;
            _sortDelegate = new Lazy<Func<TSource, TSort>>(() => sortExpression.Compile());
            _sortFieldName = new Lazy<string>(() => ((MemberExpression)sortExpression.Body).Member.Name);
        }
    
        public string SortFieldName => _sortFieldName.Value;
    
        public IOrderedEnumerable<TSource> MakeOrderBy(IEnumerable<TSource> source) => source.OrderBy(_sortDelegate.Value);
        public IOrderedEnumerable<TSource> MakeOrderByDescending(IEnumerable<TSource> source) => source.OrderByDescending(_sortDelegate.Value);
        public IOrderedEnumerable<TSource> MakeThenBy(IOrderedEnumerable<TSource> source) => source.ThenBy(_sortDelegate.Value);
        public IOrderedEnumerable<TSource> MakeThenByDescending(IOrderedEnumerable<TSource> source) => source.ThenBy(_sortDelegate.Value);
    
        public IOrderedQueryable<TSource> MakeOrderBy(IQueryable<TSource> source) => source.OrderBy(_sortExpression);
        public IOrderedQueryable<TSource> MakeOrderByDescending(IQueryable<TSource> source) => source.OrderByDescending(_sortExpression);
        public IOrderedQueryable<TSource> MakeThenBy(IOrderedQueryable<TSource> source) => source.ThenBy(_sortExpression);
        public IOrderedQueryable<TSource> MakeThenByDescending(IOrderedQueryable<TSource> source) => source.ThenByDescending(_sortExpression);
    }
    

    第二个方便的扩展:

    public static class SortCrit
    {
        public static ISortCrit<TSource> Create<TSource, TSort>(Expression<Func<TSource, TSort>> sortExpression) => new SortCrit<TSource, TSort>(sortExpression);
    
        public static IOrderedEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, ISortCrit<TSource> crit) => crit.MakeOrderBy(source);
        public static IOrderedEnumerable<TSource> OrderByDescending<TSource>(this IEnumerable<TSource> source, ISortCrit<TSource> crit) => crit.MakeOrderByDescending(source);
        public static IOrderedEnumerable<TSource> ThenBy<TSource>(this IOrderedEnumerable<TSource> source, ISortCrit<TSource> crit) => crit.MakeThenBy(source);
        public static IOrderedEnumerable<TSource> ThenByDescending<TSource>(this IOrderedEnumerable<TSource> source, ISortCrit<TSource> crit) => crit.MakeThenByDescending(source);
    
        public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, ISortCrit<TSource> crit) => crit.MakeOrderBy(source);
        public static IOrderedQueryable<TSource> OrderByDescending<TSource>(this IQueryable<TSource> source, ISortCrit<TSource> crit) => crit.MakeOrderByDescending(source);
        public static IOrderedQueryable<TSource> ThenBy<TSource>(this IOrderedQueryable<TSource> source, ISortCrit<TSource> crit) => crit.MakeThenBy(source);
        public static IOrderedQueryable<TSource> ThenByDescending<TSource>(this IOrderedQueryable<TSource> source, ISortCrit<TSource> crit) => crit.MakeThenByDescending(source);
    }
    

    用法:

    var messageCrit = SortCrit.Create((Message m) => m.ItemNo);
    
    IEnumerable<Message> msgs = ...;
    msgs.OrderBy(messageCrit);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-20
      • 1970-01-01
      • 1970-01-01
      • 2021-08-13
      • 1970-01-01
      • 2019-05-09
      • 1970-01-01
      • 2019-12-26
      相关资源
      最近更新 更多