我可以将 SqlFunctions.DateDiff() in Entity Framework 与动态 datePartArg 一起使用吗?

Can I use SqlFunctions.DateDiff() in Entity Framework with a dynamic datePartArg?

由于注释行,以下代码抛出 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 服务器中只能使用文字字符串作为其第一个参数,而不能使用变量。 Entity Framework 在 SQL 中生成了一个变量,所以我们得到了异常。

有办法解决这个问题吗?

您可以创建以下扩展方法来解决这个问题。使用 DateDiff 时,只需将 GroupBy 函数替换为此扩展方法即可:

    public static IQueryable<IGrouping<TKey, TSource>> GroupByDateDiff<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector) {
        var body = (NewExpression)keySelector.Body;
        var transformedBodyArguments = body.Arguments.Select(arg => arg switch {
            MethodCallExpression callNode
                when callNode.Method.Name == "DateDiff" && callNode.Arguments[0].NodeType != ExpressionType.Constant
                    => getTransformedDateDiffCall(callNode),
            _ => arg,
        }).ToArray();

        var updatedExpr = keySelector.Update(body.Update(transformedBodyArguments), keySelector.Parameters);
        return source.GroupBy(updatedExpr);

        MethodCallExpression getTransformedDateDiffCall(MethodCallExpression dateDiffCallNode) {
            var dateDiffFirstArg = dateDiffCallNode.Arguments[0];
            if (dateDiffFirstArg.NodeType != ExpressionType.MemberAccess) {
                throw new ArgumentException($"{nameof(GroupByDateDiff)} was unable to parse the datePartArg argument to the DateDiff function.");
            }
            var replacementExpression = Expression.Constant((string)GetMemberValue((MemberExpression)dateDiffFirstArg));
            var alternativeArgs = dateDiffCallNode.Arguments.Skip(1).Prepend(replacementExpression).ToArray();
            return dateDiffCallNode.Update(dateDiffCallNode.Object, alternativeArgs);
        };
    }

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