C# - 使用属性名称作为字符串由属性排序的代码

Jer*_*emy 72 .net c# linq linq-to-entities

当我将属性名称作为字符串时,对C#中的属性进行编码的最简单方法是什么?例如,我想允许用户通过他们选择的属性(使用LINQ)来订购一些搜索结果.他们将在UI中选择"order by"属性 - 当然是字符串值.有没有办法直接使用该字符串作为linq查询的属性,而不必使用条件逻辑(if/else,switch)将字符串映射到属性.反射?

从逻辑上讲,这就是我想做的事情:

query = query.OrderBy(x => x."ProductId");
Run Code Online (Sandbox Code Playgroud)

更新:我最初没有指定我正在使用Linq to Entities - 看起来反射(至少GetProperty,GetValue方法)不会转换为L2E.

Ada*_*son 105

我会提供其他人发布的替代方案.

System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName");

query = query.OrderBy(x => prop.GetValue(x, null));
Run Code Online (Sandbox Code Playgroud)

这避免了重复调用反射API以获取属性.现在唯一重复的调用是获取值.

然而

我建议使用PropertyDescriptor替代,因为这将允许将自定义TypeDescriptors分配给您的类型,从而可以使用轻量级操作来检索属性和值.在没有自定义描述符的情况下,它无论如何都会回归到反射.

PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName");

query = query.OrderBy(x => prop.GetValue(x));
Run Code Online (Sandbox Code Playgroud)

至于加快速度,请查看Marc Gravel HyperDescriptor在CodeProject上的项目.我用它取得了巨大的成功; 它可以为业务对象的高性能数据绑定和动态属性操作提供生命保护.

  • /sf/ask/4314494551/ throwing-execption-for-orderby?noredirect=1#comment109026507_61635636 存在反射问题,在 EfCore 3.1.3 中无法解决。它似乎在 EfCore 2 中抛出错误,需要激活该错误才能发出警告。使用下面@Mark的答案 (2认同)

Mar*_*ell 53

我有点迟到了,但是,我希望这会有所帮助.

使用反射的问题在于,除了内部.Net提供程序之外,任何Linq提供程序几乎肯定不会支持生成的表达式树.这适用于内部集合,但是在分页之前在源(在SQL,MongoDb等)进行排序时,这将不起作用.

下面的代码示例为OrderBy和OrderByDescending提供了IQueryable扩展方法,可以这样使用:

query = query.OrderBy("ProductId");
Run Code Online (Sandbox Code Playgroud)

扩展方法:

public static class IQueryableExtensions 
{
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
    {
        return source.OrderBy(ToLambda<T>(propertyName));
    }

    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
    {
        return source.OrderByDescending(ToLambda<T>(propertyName));
    }

    private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
    {
        var parameter = Expression.Parameter(typeof(T));
        var property = Expression.Property(parameter, propertyName);
        var propAsObject = Expression.Convert(property, typeof(object));

        return Expression.Lambda<Func<T, object>>(propAsObject, parameter);            
    }
}
Run Code Online (Sandbox Code Playgroud)

问候,马克.

  • 投票支持适用于EF的解决方案 (9认同)
  • 为什么我们需要`Expression.Convert`将`property`转换为`object`?我得到一个`无法将类型'System.String'转换为'System.Object'类型.LINQ to Entities仅支持转换EDM原语或枚举类型.错误,并删除它似乎工作. (4认同)
  • 得到错误“ LINQ to Entities仅支持强制转换EDM基本类型或枚举类型” (2认同)

Dav*_*cht 26

我喜欢@Mark Powell的答案,但正如@ShuberFu所说,它给出了错误LINQ to Entities only supports casting EDM primitive or enumeration types.

删除var propAsObject = Expression.Convert(property, typeof(object));不适用于值类型的属性,例如整数,因为它不会隐式地将int封装到对象.

使用Kristofer AnderssonMarc Gravell的想法我找到了一种使用属性名构造Queryable函数的方法,并且它仍然可以使用Entity Framework.我还包括一个可选的IComparer参数.警告: IComparer参数不适用于Entity Framework,如果使用Linq to Sql,则应该省略它.

以下适用于Entity Framework和Linq to Sql:

query = query.OrderBy("ProductId");
Run Code Online (Sandbox Code Playgroud)

@Simon Scheurer这也有效:

query = query.OrderBy("ProductCategory.CategoryId");
Run Code Online (Sandbox Code Playgroud)

如果你没有使用Entity Framework或Linq to Sql,这可行:

query = query.OrderBy("ProductCategory", comparer);
Run Code Online (Sandbox Code Playgroud)

这是代码:

public static class IQueryableExtensions 
{    
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "OrderBy", propertyName, comparer);
}

public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "OrderByDescending", propertyName, comparer);
}

public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "ThenBy", propertyName, comparer);
}

public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "ThenByDescending", propertyName, comparer);
}

/// <summary>
/// Builds the Queryable functions using a TSource property name.
/// </summary>
public static IOrderedQueryable<T> CallOrderedQueryable<T>(this IQueryable<T> query, string methodName, string propertyName,
        IComparer<object> comparer = null)
{
    var param = Expression.Parameter(typeof(T), "x");

    var body = propertyName.Split('.').Aggregate<string, Expression>(param, Expression.PropertyOrField);

    return comparer != null
        ? (IOrderedQueryable<T>)query.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable),
                methodName,
                new[] { typeof(T), body.Type },
                query.Expression,
                Expression.Lambda(body, param),
                Expression.Constant(comparer)
            )
        )
        : (IOrderedQueryable<T>)query.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable),
                methodName,
                new[] { typeof(T), body.Type },
                query.Expression,
                Expression.Lambda(body, param)
            )
        );
}
}
Run Code Online (Sandbox Code Playgroud)

  • 天哪,老兄,你是微软吗?:)那个“聚合”片段太棒了!它负责使用“Join”从 EF Core 模型创建的虚拟视图,因为我使用“T.Property”等属性。否则,在“Join”之后进行排序将不可能产生“InvalidOperationException”或“NullReferenceException”。我确实需要在“Join”之后进行排序,因为大多数查询都是不变的,而视图中的顺序不是。 (2认同)
  • @Dat Nguyen 您可以使用“products.OrderBy("ProductId")”代替“products.OrderBy(x =&gt; x.ProductId)” (2认同)

Alo*_*kin 12

是的,我认为除了反思之外还有另一种方式.

例:

query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Run Code Online (Sandbox Code Playgroud)


Ngu*_*ong 7

警告\xe2\x9a\xa0\xef\xb8\x8f

\n

Reflection您只能在数据位于内存中的情况下使用。否则,当您使用时,您会看到如下错误Linq-2-EF, Linq-2-SQL, etc.

\n

@Florin V\xc3\xaerdol\ 的评论

\n
\n

LINQ to Entities 无法识别“System.Object\nGetValue(System.Object)\”方法,并且此方法无法转换\n为存储表达式。

\n
\n

为什么

\n

因为当您编写代码来向Linq query provider. 它首先被翻译成SQL语句,然后在数据库服务器上执行。

\n

(参见下图,来自https://www.tutorialsteacher.com/linq/linq-expression

\n

在此输入图像描述

\n

解决方案\xe2\x9c\x85

\n

通过使用表达式树,您可以编写这样的通用方法

\n
public static IEnumerable<T> OrderDynamic<T>(IEnumerable<T> Data, string propToOrder)\n{\n    var param = Expression.Parameter(typeof(T));\n    var memberAccess = Expression.Property(param, propToOrder);        \n    var convertedMemberAccess = Expression.Convert(memberAccess, typeof(object));\n    var orderPredicate = Expression.Lambda<Func<T, object>>(convertedMemberAccess, param);\n\n    return Data.AsQueryable().OrderBy(orderPredicate).ToArray();\n}\n
Run Code Online (Sandbox Code Playgroud)\n

像这样使用它

\n
var result = OrderDynamic<Student>(yourQuery, "StudentName"); // string property\n
Run Code Online (Sandbox Code Playgroud)\n

或者

\n
var result = OrderDynamic<Student>(yourQuery, "Age");  // int property\n
Run Code Online (Sandbox Code Playgroud)\n

它还可以通过将数据转换为IQueryable<TElement>通用方法返回语句来处理内存中的数据,如下所示

\n
return Data.AsQueryable().OrderBy(orderPredicate).ToArray();\n
Run Code Online (Sandbox Code Playgroud)\n

请参阅下图以更深入地了解。

\n

在此输入图像描述

\n

dotnetfiddle 上的演示

\n


dka*_*man 5

query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Run Code Online (Sandbox Code Playgroud)

试图回忆起我的头脑中的确切语法,但我认为这是正确的.