[英]How to pass an Expression into Entity Framework LINQ query OrderBy clause
I have the following LINQ query: 我有以下LINQ查询:
using (var context = new EUContext())
{
var tmp = context.Terms.Include(x => x.StudentCourses)
.Where(x => x.StudentID == studentId && x.DepartmentID == departmentId)
.OrderBy(x => x.AcademicYear)
.ThenBy(x=> x.TermRegistered == "Fall" ? 1 :
x.TermRegistered == "Spring" ? 2 : 3));
return tmp.ToList();
}
I am trying to move the OrdyBy in the ThenBy clause to clean up the code. 我试图在ThenBy子句中移动OrdyBy来清理代码。 I am trying to use an expression as following:
我试图使用如下表达式:
private static Expression<Func<string, int>> TermsOrder(string x)
{
return (x == "Fall" ? 1 :
x == "Spring" ? 2 : 3);
}
and my code should look like this: 我的代码应如下所示:
using (var context = new EUContext())
{
var tmp = context.Terms.Include(x => x.StudentCourses)
.Where(x => x.StudentID == studentId && x.DepartmentID == departmentId)
.OrderBy(x => x.AcademicYear)
.ThenBy(x=> TermsOrder(x.TermRegistered));
return tmp.ToList();
}
Unfortunately the expression doesn't work there is a long squiggly line in the body of the expression with the following error message: 不幸的是,表达式不起作用表达式正文中有一条长曲线,并带有以下错误消息:
Cannot implicitly convert type 'int' to 'System.Linq.Expressions.Expression>
无法将类型'int'隐式转换为'System.Linq.Expressions.Expression>
What am I doing wrong? 我究竟做错了什么? This is my first try on using expressions and I know that I am missing something obvious due to not fully understanding how expressions work.
这是我第一次尝试使用表达式,我知道由于没有完全理解表达式如何工作,我遗漏了一些明显的东西。
Thanks 谢谢
This isn't as simple as it seems. 这并不像看起来那么简单。 You need to combine
Expression
s or build Expression
s to generate what you want, and unfortunately C# doesn't include a lot of help in that area. 你需要结合
Expression
或构建Expression
来生成你想要的东西,不幸的是C#在这方面没有很多帮助。
The easiest approach is to use an extension method for LambdaExpression
composition. 最简单的方法是使用
LambdaExpression
组合的扩展方法。 It depends on some Expression
extension methods for replacing one Expression
with another in an Expression
: 它依赖于一些
Expression
扩展方法用于替换一个Expression
中的另一个Expression
:
public static class ExpressionExt {
// Compose: f.Compose(g) => x => f(g(x))
/// <summary>
/// Composes two LambdaExpression into a new LambdaExpression: f.Compose(g) => x => f(g(x))
/// </summary>
/// <param name="fFn">The outer LambdaExpression.</param>
/// <param name="gFn">The inner LambdaExpression.</param>
/// <returns>LambdaExpression representing outer composed with inner</returns>
public static Expression<Func<T, TResult>> Compose<T, TIntermediate, TResult>(this Expression<Func<TIntermediate, TResult>> fFn, Expression<Func<T, TIntermediate>> gFn) =>
Expression.Lambda<Func<T, TResult>>(fFn.Body.Replace(fFn.Parameters[0], gFn.Body), gFn.Parameters[0]);
/// <summary>
/// Replaces a sub-Expression with another Expression inside an Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
}
/// <summary>
/// Standard ExpressionVisitor to replace an Expression with another in an Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
Now you can create your method that takes a lambda representing the field you want to test. 现在,您可以创建一个方法,该方法采用代表您要测试的字段的lambda。 It uses a local
LambdaExpression
as a template for the final result: 它使用本地
LambdaExpression
作为最终结果的模板:
public static class Util {
static Expression<Func<string, int>> TermOrderTemplateFn = p => (p == "Fall" ? 1 : p == "Spring" ? 2 : 3);
public static Expression<Func<TRec, int>> TermsOrder<TRec>(Expression<Func<TRec, string>> selectorFn) =>
TermOrderTemplateFn.Compose(selectorFn);
}
Now you can call the method in your expression, passing in a lambda representing the desired field (or field expression) to test: 现在,您可以在表达式中调用该方法,传入表示所需字段(或字段表达式)的lambda来测试:
var tmp = context.Terms.Include(x => x.StudentCourses).AsQueryable()
.Where(x => x.StudentID == studentId && x.DepartmentID == departmentId)
.OrderBy(x => x.AcademicYear)
.ThenBy(Util.TermsOrder<Term>(p => p.TermRegistered));
Note: I am calling the type of context.Terms.First()
Term
but you would need to use the actual correct type name in the call to TermsOrder
. 注意:我正在调用
context.Terms.First()
Term
的类型,但您需要在调用TermsOrder
使用实际的正确类型名称。 You could also do TermsOrder((Term p) => ...)
instead. 您也可以使用
TermsOrder((Term p) => ...)
。
I would probably prefer to create a special version of ThenBy
so you can use type inference to determine the record type: 我可能更喜欢创建一个特殊版本的
ThenBy
以便您可以使用类型推断来确定记录类型:
public static class EFExt {
static Expression<Func<string, int>> TermThenOrderTemplateFn = p => (p == "Fall" ? 1 : p == "Spring" ? 2 : 3);
public static IOrderedQueryable<T> ThenByTerm<T>(this IOrderedQueryable<T> src, Expression<Func<T, string>> selectorFn) =>
src.ThenBy(TermThenOrderTemplateFn.Compose(selectorFn));
}
Then you can use it directly: 然后你可以直接使用它:
var tmp = context.Terms.Include(x => x.StudentCourses).AsQueryable()
.Where(x => x.StudentID == studentId && x.DepartmentID == departmentId)
.OrderBy(x => x.AcademicYear)
.ThenByTerm(p => p.TermRegistered);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.