我想通过字符串参数生成表达式,一些代码如:
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改为对象。
private Expression<Func<Task, object>> Generate(string orderby)
然后它可以编译,但它不起作用。
System.NotSupportedException:无法将类型“System.Int32”强制转换为“System.Object”类型。 LINQ to Entities仅支持转换EDM原语或枚举类型。
使用reflection和expression-trees你可以提供参数,然后调用OrderBy
函数,而不是返回Expression<Func<Task, T>>
然后调用OrderBy
。
请注意,OrderBy
是一种扩展方法,并在System.Linq.Enumarable
和System.Linq.Queryable
类中实现。第一个是linq-to-objects,后者是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
属性选择器。
您可以尝试在通用方法中转换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
只能是原始类型或枚举类型。
使用通用方法。由于lambda表达式只能分配给强类型的委托或表达式,因此我们必须使用相应的temp。然后我们可以将此临时值分配给类型为object
的变量。最后,我们可以通过强制转换为结果类型来返回结果。
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;
}