Могу ли я использовать SqlFunctions.DateDiff () в Entity Framework с динамическим datePartArg? - PullRequest
0 голосов
/ 01 июня 2018

Следующий код выдает EntityCommandCompilationException из-за закомментированной строки:

var datePartArg = "dd";
var minutesInStatePerSegment = await db.History_WorkPlaceStates
        .Where(x => selector.StartTimeUtc <= x.Started && x.Ended < selector.EndTimeUtc)
        .Select(x => new {
                    start = x.Started,
                    minutes = x.Minutes,
                    state = x.State,
                })
        .GroupBy(x => new {
                    //This causes an exception:
                    segment = SqlFunctions.DateDiff(datePartArg, selector.StartTimeUtc, x.start),
                    state = x.state,
                })
        .Select(x => new {
                    state = x.Key.state,
                    segment = x.Key.segment,
                    minutes = x.Sum(y => y.minutes),
                }).ToListAsync();

Это происходит потому, что DateDiff в SQL Server может использовать только литеральную строку для своего первого аргумента и не может использоватьпеременная.Entity Framework генерирует переменную в SQL, и мы получаем исключение.

Есть ли способ обойти эту проблему?

1 Ответ

0 голосов
/ 01 июня 2018

Вы можете создать следующий метод расширения, чтобы обойти это.Просто замените функцию GroupBy этим методом расширения при использовании DateDiff:

    private static object GetMemberValue(MemberExpression member) {
        var objectMember = Expression.Convert(member, typeof(object));
        var getterLambda = Expression.Lambda<Func<object>>(objectMember);
        var getter = getterLambda.Compile();
        return getter();
    }

    public static IQueryable<IGrouping<TKey, TSource>> GroupByDateDiff<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector) {
        var body = (NewExpression)keySelector.Body;
        foreach (var arg in body.Arguments) {
            if (arg.NodeType == ExpressionType.Call) {
                var callNode = (MethodCallExpression)arg;
                if (callNode.Method.Name == "DateDiff") {
                    var dateDiffFirstArg = callNode.Arguments[0];
                    if (dateDiffFirstArg.NodeType == ExpressionType.Constant) {
                        //It was already a constant, so we're good.
                    }
                    else {
                        //HACK: This will break if the internal implementation of ReadOnlyCollection changes.
                        var listInfo = typeof(ReadOnlyCollection<Expression>).GetField("list", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                        var list = (IList)listInfo.GetValue(callNode.Arguments);
                        if (dateDiffFirstArg.NodeType == ExpressionType.MemberAccess) {
                            list[0] = Expression.Constant((string)GetMemberValue((MemberExpression)dateDiffFirstArg));
                        }
                        else {
                            throw new ArgumentException($"{nameof(GroupByDateDiff)} was unable to parse the datePartArg argument to the DateDiff function.");
                        }
                    }
                }
            }
        }
        return source.GroupBy(keySelector);
    }

Обратите внимание, что при этом используется отражение для доступа к закрытым переменным, и он может прерваться, если реализация ReadOnlyCollection изменится.Это было бы довольно легко исправить, если бы это случилось, однако.

...