动态Linq +实体框架:日期时间修改动态选择

本文关键字:动态 时间 修改 选择 日期 框架 Linq 实体 | 更新日期: 2023-09-27 18:11:50

我正试图找到一种方法来移动UTC时间到本地做sql分组之前。我正在使用System.Linq.Dynamic(在这里管理https://github.com/kahanu/System.Linq.Dynamic)。它可以很好地执行动态选择,而无需在编译时使用必需的字段。在我们的例子中,我们用UTC存储所有日期时间。在这个动态选择中,可能有人想要按小时、年、月等进行分组。在这种情况下,我们必须将数据移动到本地时间,以防止混淆。

的例子:

var select = queryable.Select(string.Format("new ({0}, {1})", datetimeColumn, someOtherColumn));

通常在使用lambda表达式的tsql或实体框架中,您可以添加所需的偏移量。但是在动态linq选项中,您似乎不能像使用Linq2Sql那样执行任何日期操作,例如DateTime.AddHours(x)或DateTime.Subtract(x)。实体框架6希望你使用DbFunctions.AddHours(x)等。然而,动态linq代码,如果不修改,将不会接受没有错误的DbFunctions。

的例子:

var select = queryable.Select(string.Format("new (System.Data.Entity.DbFunctions.AddHours({0},7) as {0}, {1})", datetimeColumn, someOtherColumn));

返回错误:XXX类型中不存在属性或字段"System"
(删除命名空间没有帮助)。

使用所需代码:

var select = queryable.Select(string.Format("new ({0}.AddHours(7), {1})", datetimeColumn, someOtherColumn));

结果错误:LINQ to Entities不识别方法System。DateTime AddHours(Double)'方法,该方法不能转换为存储表达式。

我想让SQL在groupby之前执行datetime数学。一旦groupby发生,UTC的概念就不再存在了,因为用户将看到本地化的结果集。

我担心我只是更新我的github分支与一些扩展,以支持在实体框架扩展传递,但在我这样做之前,想看看是否有人有一个解决方案或想法。

注意:我不使用DateTimeOffset由于改变SQL数据存储技术的可能性

动态Linq +实体框架:日期时间修改动态选择

您可以使用自定义ExpressionVisitor对查询表达式进行post处理,并将不支持的方法替换为相应的DbFunctions

这里是一个起点,只是为了了解这个想法:

public static class QueryableExtensions
{
    public static IQueryable BindDbFunctions(this IQueryable source)
    {
        var expression = new DbFunctionsBinder().Visit(source.Expression);
        if (expression == source.Expression) return source;
        return source.Provider.CreateQuery(expression);
    }
    class DbFunctionsBinder : ExpressionVisitor
    {
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Object != null && node.Object.Type == typeof(DateTime))
            {
                if (node.Method.Name == "AddHours")
                {
                    var timeValue = Visit(node.Object);
                    var addValue = Visit(node.Arguments.Single());
                    if (timeValue.Type != typeof(DateTime?)) timeValue = Expression.Convert(timeValue, typeof(DateTime?));
                    if (addValue.Type != typeof(int?)) addValue = Expression.Convert(addValue, typeof(int?));
                    var methodCall = Expression.Call(
                        typeof(DbFunctions), "AddHours", Type.EmptyTypes,
                        timeValue, addValue);
                    return Expression.Convert(methodCall, typeof(DateTime));
                }
            }
            return base.VisitMethodCall(node);
        }
    }
}

和示例用法:

var select = queryable
    .Select(string.Format("new ({0}.AddHours(7), {1})", datetimeColumn, someOtherColumn))
    .BindDbFunctions();