【问题标题】:Handling invalid input in extension of IQueryable.OrderBy using string object member names使用字符串对象成员名称处理 IQueryable.OrderBy 扩展中的无效输入
【发布时间】:2018-10-23 09:23:10
【问题描述】:

我为 IQueryable OrderBy(以及 OrderByDescending、ThenBy 和 ThenByDescending)编写了一个扩展方法,它们都调用我的函数 CreateExpression 来获取用于排序的表达式。

问题是如果有人使用无效参数调用此方法,例如字符串不是正在排序的对象类型中的成员名称。这就是为什么我检查 pi 是否不为空。现在,如果参数无效且 pi 为 null,我需要返回 null 以外的其他内容,但我不太确定该怎么做。我想返回一个不排序的表达式,这可能吗?我该怎么做?

    public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string orderBy)
    {
        if (!string.IsNullOrWhiteSpace(orderBy))
        {
            var resultExp = CreateExpression(source, "OrderBy", orderBy);
            return source.Provider.CreateQuery<T>(resultExp);
        }
        else
        {
            return source;
        }
    }


    private static MethodCallExpression CreateExpression<T>(IQueryable<T> source, string methodName, string orderBy)
    {
        if (methodName != "OrderBy" && methodName != "OrderByDescending" && methodName != "ThenBy" && methodName != "ThenByDescending")
            methodName = "OrderBy";

        if (!string.IsNullOrWhiteSpace(orderBy))
        {
            PropertyInfo pi = typeof(T).GetProperty(orderBy);

            if (pi != null)
            {
                var parameter = Expression.Parameter(typeof(T), "p");
                MemberExpression me = Expression.MakeMemberAccess(parameter, pi);
                var orderByExp = Expression.Lambda(me, parameter);

                return Expression.Call(typeof(Queryable), methodName,
                    new Type[] { typeof(T), pi.PropertyType }, source.Expression, Expression.Quote(orderByExp));
            }
        }

        //todo
        return null;
    }

【问题讨论】:

  • 谢谢 CodingYoshi 和 Simon Katanski,你们的回复都非常好。我最终抛出了一个异常,因为我无法影响将字符串参数更改为表达式。我希望我可以标记两个答案,但我会标记 Simons 的答案,因为如果我可以摆脱字符串参数,我会这样做。但是,我认为我不需要首先编写扩展方法,我可以直接使用 OrderBy 的默认实现。谢谢!
  • 你仍然可以使用你的扩展方法: private static MethodCallExpression CreateExpression(IQueryable source, string methodName, string orderBy) 你可以使用一个表达式来代替orderBy——只是一个小的优化.

标签: c#


【解决方案1】:

可能的解决方案是使用这样的属性表达式。将属性选择器传递给您的方法,而不是字符串:

Expression<Func<T, TProperty>> propertySelector

在您的方法中,您可以获取属性的名称并像在代码中一样使用它:

private static string GetPropertyName<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertySelector)
{
    MemberExpression member = propertySelector.Body as MemberExpression;
    PropertyInfo propInfo = member.Member as PropertyInfo;
    return propInfo.Name;
}

您的方法签名如下所示:

public static IQueryable<T> OrderBy<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> propertySelector)

你可以这样称呼它:

collection.OrderBy(p => p.UserName)

您将获得静态类型,并且可以删除魔术字符串。

否则,我会抛出一个 InvalidOperationException,因为这是一种无效的情况,并且您的代码的用户应该被告知它异常 - 而不是代码静默失败。

【讨论】:

  • 您不会使用这种方法获得任何收益,因为IEnumerable 已经具备执行collection.OrderBy(p =&gt; p.UserName) 的能力。所以你不会从这种方法中得到任何额外的东西。
  • 但是你也可以传入methodName,这样你就可以更好地实现crazysnake想要的了。
  • 当然可以,但是你得到了什么?您已经可以使用 Linq 执行 OrderByDescending 并且您将获得编译器支持。在这里,您将传递一个 func,然后是一个字符串,对于该字符串,将没有编译器支持。所以真的值得吗?
  • 除非我误解了——你的代码获得了一个静态类型的属性选择器,而不是一个魔术字符串。其他一切都没有真正改变。
  • 好的,我将停止此消息。您所说的增益已经在 Linq 中可用。你的答案没有错,但让你知道。
【解决方案2】:

由于您已经编写了可用于任何类型的扩展方法(您没有任何限制),因此您唯一的选择是在用户向您提供不存在的属性名称时抛出异常。您无能为力。

但是,如果您对T 应用一个简单的约束,那么您可以在提供的属性不存在的情况下使用它来提供默认排序。方法如下:

  1. 创建一个接口并让您的所有Ts 实现该接口:

    public interface IOrderable
    {
        int Id { get; }
    }
    
  2. 更改您的扩展并对其应用约束。然后,如果您没有找到提供的属性名称,则返回一个表达式,默认情况下将在 Id 列上排序:

    private static MethodCallExpression CreateExpression<T>(IQueryable<T> source, string methodName, string orderBy)
        where T : IOrderable
    {
        // your code...
    
        //here is the todo
        // Here we are returning an expression which will sort on the 
        // Id column by default
        var parameterDef = Expression.Parameter(typeof(T), "p");
        PropertyInfo piDef = typeof(T).GetProperty(nameof(IOrderable.Id));
        MemberExpression meDef = Expression.MakeMemberAccess(parameterDef, piDef);
        var orderByExpDef = Expression.Lambda(meDef, parameterDef);
        return Expression.Call(typeof(Queryable), methodName,
                    new Type[] 
                    {
                        typeof(T), piDef.PropertyType
                    }, 
                    source.Expression, Expression.Quote(orderByExpDef));
    }
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-05
    • 2014-01-19
    • 2014-05-07
    • 1970-01-01
    • 2019-09-16
    • 1970-01-01
    相关资源
    最近更新 更多