通过字符串生成EF orderby表达式
Generate EF orderby expression by string
我想通过字符串参数生成表达式,代码如下:
private Expression<Func<Task, T>> Generate(string orderby)
{
switch (orderby)
{
case "Time":
return t => t.Time;
case "Money":
return t => t.RewardMoney;
default:
return t => t.Id;
}
}
然后调用它:
_context.Items.OrderBy(Generate("Money"));
但是编译不了!我把T改成object.
private Expression<Func<Task, object>> Generate(string orderby)
然后可以编译,但是不行
System.NotSupportedException: Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
您可以尝试在泛型方法中转换 Generate
方法:
private Expression<Func<Task, TResult>> Generate<TResult>(string orderby)
{
switch (orderby)
{
case "Time":
return t => t.Time;
case "Money":
return t => t.RewardMoney;
default:
return t => t.Id;
}
}
所以,如果你调用这个方法,你需要指定你想要订购的 属性 的类型:
_context.Items.OrderBy(Generate<decimal>("Money"));
现在记住TResult
只能是原始类型或枚举类型。
使用reflection and expression-trees你可以提供参数然后调用OrderBy
函数,而不是返回Expression<Func<Task, T>>
然后调用OrderBy
.
请注意,OrderBy
是一种扩展方法,已在 System.Linq.Enumarable
和 System.Linq.Queryable
类 中实现。第一个是 linq-to-objects and the latter is for linq-to-entities. entity-framework 需要查询的表达式树,以便将其转换为 SQL 命令。所以我们使用 Queryable
实现。
可以通过扩展方法来完成(解释添加为评论):
public static IOrderedQueryable<TSource> OrderBy<TSource>(
this IQueryable<TSource> query, string propertyName)
{
var entityType = typeof(TSource);
//Create x=>x.PropName
var propertyInfo = entityType.GetProperty(propertyName);
ParameterExpression arg = Expression.Parameter(entityType, "x");
MemberExpression property = Expression.Property(arg, propertyName);
var selector = Expression.Lambda(property, new ParameterExpression[] { arg });
//Get System.Linq.Queryable.OrderBy() method.
var enumarableType = typeof(System.Linq.Queryable);
var method = enumarableType.GetMethods()
.Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
.Where(m =>
{
var parameters = m.GetParameters().ToList();
//Put more restriction here to ensure selecting the right overload
return parameters.Count == 2;//overload that has 2 parameters
}).Single();
//The linq's OrderBy<TSource, TKey> has two generic types, which provided here
MethodInfo genericMethod = method
.MakeGenericMethod(entityType, propertyInfo.PropertyType);
/*Call query.OrderBy(selector), with query and selector: x=> x.PropName
Note that we pass the selector as Expression to the method and we don't compile it.
By doing so EF can extract "order by" columns and generate SQL for it.*/
var newQuery = (IOrderedQueryable<TSource>)genericMethod
.Invoke(genericMethod, new object[] { query, selector });
return newQuery;
}
现在您可以像调用它的任何其他重载一样调用 OrderBy
的这个重载。
例如:
var cheapestItems = _context.Items.OrderBy("Money").Take(10).ToList();
转换为:
SELECT TOP (10) {coulmn names} FROM [dbo].[Items] AS [Extent1]
ORDER BY [Extent1].[Money] ASC
此方法可用于定义 OrderBy
和 OrderByDescending
方法的所有重载以具有 string
属性 选择器。
使用通用方法。由于 lambda 表达式只能分配给强类型的委托或表达式,因此我们必须使用相应的 temp。然后我们可以将此临时值分配给类型为 object
的变量。最后,我们可以 return 通过转换为结果类型来得到结果。
public Expression<Func<Task, TResult>> Generate<TResult>(string orderby)
{
object result;
switch (orderby) {
case "Time":
Expression<Func<Task, DateTime>> temp1 = t => t.Time;
result = temp1;
break;
case "Money":
Expression<Func<Task, decimal>> temp2 = t => t.RewardMoney;
result = temp2;
break;
default:
Expression<Func<Task, int>> temp3 = t => t.Id;
result = temp3;
break;
}
return (Expression<Func<Task, TResult>>)result;
}
public static IQueryable<T> OrderByHelper<T>(this IQueryable<T> source, string propertyName, string sortDirection)
{
try
{
if (source == null)
{
return source;
}
if (propertyName == null)
{
return source;
}
propertyName = propertyName.First().ToString().ToUpper(new CultureInfo("en-US", false)) + propertyName.Substring(1);
var type = typeof(T);
var arg = Expression.Parameter(type, "x");
var propertyInfo = type.GetProperty(propertyName);
var mExpr = Expression.Property(arg, propertyInfo);
type = propertyInfo.PropertyType;
var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
var lambda = Expression.Lambda(delegateType, mExpr, arg);
var methodName = !string.IsNullOrEmpty(sortDirection) && sortDirection.ToLower(new CultureInfo("en-US", false)) == "desc" ? "OrderByDescending" : "OrderBy";
var orderedSource = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
return (IQueryable<T>)orderedSource;
}
catch (Exception)
{
return source;
}
}
我参考了CodePlex中旧的System.Linq.Dynamic codebase,从实现和调用的角度做了一个比较简单的版本。当然是IQueryable<T>
上的扩展方法
/*
using System;
using System.Linq;
using System.Linq.Expressions;
*/
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string orderByExpression)
{
if (string.IsNullOrEmpty(orderByExpression))
return query;
string propertyName, orderByMethod;
string[] strs = orderByExpression.Split(' ');
propertyName = strs[0];
if (strs.Length == 1)
orderByMethod = "OrderBy";
else
orderByMethod = strs[1].Equals("DESC", StringComparison.OrdinalIgnoreCase) ? "OrderByDescending" : "OrderBy";
ParameterExpression pe = Expression.Parameter(query.ElementType);
MemberExpression me = Expression.Property(pe, propertyName);
MethodCallExpression orderByCall = Expression.Call(typeof(Queryable), orderByMethod, new Type[] { query.ElementType, me.Type }, query.Expression
, Expression.Quote(Expression.Lambda(me, pe)));
return query.Provider.CreateQuery(orderByCall) as IQueryable<T>;
}
这里是如何使用它的示例,针对 Entity Framework Core 3:
进行了测试
IQueryable<Person> query = dbContext.People;
query = query.OrderBy("FirstName"); // ORDER BY FirstName
IQueryable<Person> query = dbContext.People;
query = query.OrderBy("FirstName ASC"); // ORDER BY FirstName
IQueryable<Person> query = dbContext.People;
query = query.OrderBy("FirstName DESC"); // ORDER BY FirstName DESC
对于那些在 EF Core 中寻找解决方案的人:
Microsoft.EntityFrameworkCore.EF
中有一组函数可用于动态访问和查询编译。您可以使用 EF.Property
方法按 属性 名称甚至阴影属性对可查询进行排序。
示例:
bool descending = false;
if (sortOrder.EndsWith("_desc"))
{
sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
descending = true;
}
if (descending)
{
students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
}
else
{
students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
}
来源:Tutorial: Learn about advanced scenarios - ASP.NET MVC with EF Core
我想通过字符串参数生成表达式,代码如下:
private Expression<Func<Task, T>> Generate(string orderby)
{
switch (orderby)
{
case "Time":
return t => t.Time;
case "Money":
return t => t.RewardMoney;
default:
return t => t.Id;
}
}
然后调用它:
_context.Items.OrderBy(Generate("Money"));
但是编译不了!我把T改成object.
private Expression<Func<Task, object>> Generate(string orderby)
然后可以编译,但是不行
System.NotSupportedException: Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
您可以尝试在泛型方法中转换 Generate
方法:
private Expression<Func<Task, TResult>> Generate<TResult>(string orderby)
{
switch (orderby)
{
case "Time":
return t => t.Time;
case "Money":
return t => t.RewardMoney;
default:
return t => t.Id;
}
}
所以,如果你调用这个方法,你需要指定你想要订购的 属性 的类型:
_context.Items.OrderBy(Generate<decimal>("Money"));
现在记住TResult
只能是原始类型或枚举类型。
使用reflection and expression-trees你可以提供参数然后调用OrderBy
函数,而不是返回Expression<Func<Task, T>>
然后调用OrderBy
.
请注意,OrderBy
是一种扩展方法,已在 System.Linq.Enumarable
和 System.Linq.Queryable
类 中实现。第一个是 linq-to-objects and the latter is for linq-to-entities. entity-framework 需要查询的表达式树,以便将其转换为 SQL 命令。所以我们使用 Queryable
实现。
可以通过扩展方法来完成(解释添加为评论):
public static IOrderedQueryable<TSource> OrderBy<TSource>(
this IQueryable<TSource> query, string propertyName)
{
var entityType = typeof(TSource);
//Create x=>x.PropName
var propertyInfo = entityType.GetProperty(propertyName);
ParameterExpression arg = Expression.Parameter(entityType, "x");
MemberExpression property = Expression.Property(arg, propertyName);
var selector = Expression.Lambda(property, new ParameterExpression[] { arg });
//Get System.Linq.Queryable.OrderBy() method.
var enumarableType = typeof(System.Linq.Queryable);
var method = enumarableType.GetMethods()
.Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
.Where(m =>
{
var parameters = m.GetParameters().ToList();
//Put more restriction here to ensure selecting the right overload
return parameters.Count == 2;//overload that has 2 parameters
}).Single();
//The linq's OrderBy<TSource, TKey> has two generic types, which provided here
MethodInfo genericMethod = method
.MakeGenericMethod(entityType, propertyInfo.PropertyType);
/*Call query.OrderBy(selector), with query and selector: x=> x.PropName
Note that we pass the selector as Expression to the method and we don't compile it.
By doing so EF can extract "order by" columns and generate SQL for it.*/
var newQuery = (IOrderedQueryable<TSource>)genericMethod
.Invoke(genericMethod, new object[] { query, selector });
return newQuery;
}
现在您可以像调用它的任何其他重载一样调用 OrderBy
的这个重载。
例如:
var cheapestItems = _context.Items.OrderBy("Money").Take(10).ToList();
转换为:
SELECT TOP (10) {coulmn names} FROM [dbo].[Items] AS [Extent1]
ORDER BY [Extent1].[Money] ASC
此方法可用于定义 OrderBy
和 OrderByDescending
方法的所有重载以具有 string
属性 选择器。
使用通用方法。由于 lambda 表达式只能分配给强类型的委托或表达式,因此我们必须使用相应的 temp。然后我们可以将此临时值分配给类型为 object
的变量。最后,我们可以 return 通过转换为结果类型来得到结果。
public Expression<Func<Task, TResult>> Generate<TResult>(string orderby)
{
object result;
switch (orderby) {
case "Time":
Expression<Func<Task, DateTime>> temp1 = t => t.Time;
result = temp1;
break;
case "Money":
Expression<Func<Task, decimal>> temp2 = t => t.RewardMoney;
result = temp2;
break;
default:
Expression<Func<Task, int>> temp3 = t => t.Id;
result = temp3;
break;
}
return (Expression<Func<Task, TResult>>)result;
}
public static IQueryable<T> OrderByHelper<T>(this IQueryable<T> source, string propertyName, string sortDirection)
{
try
{
if (source == null)
{
return source;
}
if (propertyName == null)
{
return source;
}
propertyName = propertyName.First().ToString().ToUpper(new CultureInfo("en-US", false)) + propertyName.Substring(1);
var type = typeof(T);
var arg = Expression.Parameter(type, "x");
var propertyInfo = type.GetProperty(propertyName);
var mExpr = Expression.Property(arg, propertyInfo);
type = propertyInfo.PropertyType;
var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
var lambda = Expression.Lambda(delegateType, mExpr, arg);
var methodName = !string.IsNullOrEmpty(sortDirection) && sortDirection.ToLower(new CultureInfo("en-US", false)) == "desc" ? "OrderByDescending" : "OrderBy";
var orderedSource = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
return (IQueryable<T>)orderedSource;
}
catch (Exception)
{
return source;
}
}
我参考了CodePlex中旧的System.Linq.Dynamic codebase,从实现和调用的角度做了一个比较简单的版本。当然是IQueryable<T>
/*
using System;
using System.Linq;
using System.Linq.Expressions;
*/
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string orderByExpression)
{
if (string.IsNullOrEmpty(orderByExpression))
return query;
string propertyName, orderByMethod;
string[] strs = orderByExpression.Split(' ');
propertyName = strs[0];
if (strs.Length == 1)
orderByMethod = "OrderBy";
else
orderByMethod = strs[1].Equals("DESC", StringComparison.OrdinalIgnoreCase) ? "OrderByDescending" : "OrderBy";
ParameterExpression pe = Expression.Parameter(query.ElementType);
MemberExpression me = Expression.Property(pe, propertyName);
MethodCallExpression orderByCall = Expression.Call(typeof(Queryable), orderByMethod, new Type[] { query.ElementType, me.Type }, query.Expression
, Expression.Quote(Expression.Lambda(me, pe)));
return query.Provider.CreateQuery(orderByCall) as IQueryable<T>;
}
这里是如何使用它的示例,针对 Entity Framework Core 3:
进行了测试IQueryable<Person> query = dbContext.People;
query = query.OrderBy("FirstName"); // ORDER BY FirstName
IQueryable<Person> query = dbContext.People;
query = query.OrderBy("FirstName ASC"); // ORDER BY FirstName
IQueryable<Person> query = dbContext.People;
query = query.OrderBy("FirstName DESC"); // ORDER BY FirstName DESC
对于那些在 EF Core 中寻找解决方案的人:
Microsoft.EntityFrameworkCore.EF
中有一组函数可用于动态访问和查询编译。您可以使用 EF.Property
方法按 属性 名称甚至阴影属性对可查询进行排序。
示例:
bool descending = false;
if (sortOrder.EndsWith("_desc"))
{
sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
descending = true;
}
if (descending)
{
students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
}
else
{
students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
}
来源:Tutorial: Learn about advanced scenarios - ASP.NET MVC with EF Core