Как передать Expression в Entity Framework LINQ-запрос Предложение OrderBy - PullRequest
0 голосов
/ 28 ноября 2018

У меня следующий запрос 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();
        }

Я пытаюсь переместить OrdyBy в предложении ThenBy, чтобы очистить код.Я пытаюсь использовать следующее выражение:

private static Expression<Func<string, int>> TermsOrder(string x)
        {
            return (x == "Fall" ? 1 :
                    x == "Spring" ? 2 : 3);
        }

и мой код должен выглядеть следующим образом:

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();
            }

К сожалению, выражение не работает, в нем есть длинная волнистая строкатело выражения со следующим сообщением об ошибке:

Невозможно неявно преобразовать тип 'int' в 'System.Linq.Expressions.Expression>

Что я делаюнеправильно?Это моя первая попытка использования выражений, и я знаю, что упускаю что-то очевидное из-за неполного понимания работы выражений.

Спасибо

1 Ответ

0 голосов
/ 28 ноября 2018

Это не так просто, как кажется.Вам нужно объединить 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);
...