Это не так просто, как кажется.Вам нужно объединить Expression
s или построить Expression
s, чтобы сгенерировать то, что вы хотите, и, к сожалению, C # не содержит большой помощи в этой области.
Самый простой подход - использовать метод расширениядля LambdaExpression
композиции.Это зависит от некоторых 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);
}
Теперь вы можете создать свой метод, который принимает лямбду, представляющую поле, которое вы хотите проверить.Он использует локальный 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);
}
Теперь вы можете вызвать метод в своем выражении, передав лямбду, представляющую желаемое поле (или выражение поля), для проверки:
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));
Примечание: Я звоню по типу context.Terms.First()
Term
, но вам нужно будет использовать действительное правильное имя типа при вызове TermsOrder
.Вместо этого вы также можете сделать TermsOrder((Term p) => ...)
.
Я бы предпочел создать специальную версию 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));
}
ТогдаВы можете использовать его напрямую:
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);