【问题标题】:Dynamic Linq + Entity Framework: datetime modifications for dynamic selectDynamic Linq + Entity Framework:动态选择的日期时间修改
【发布时间】:2016-10-06 20:30:23
【问题描述】:

我正在尝试在进行 sql 分组之前找到一种将 UTC 时间移动到本地的方法。我正在使用 System.Linq.Dynamic (在此处管理 https://github.com/kahanu/System.Linq.Dynamic )。它非常适合在编译时没有必填字段的情况下进行动态选择。在我们的例子中,我们以 UTC 格式存储所有日期时间。在这种动态选择中,可能有人想对小时、年、月等进行分组。在这种情况下,我们必须将数据移动到本地时间,以防止混淆。

例子:

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

通常在我们的 tsql 甚至在使用 lambda 表达式的实体框架中,您可以添加所需的偏移量。但是在动态 linq 选项中,您似乎无法像使用 Linq2Sql 那样执行任何日期操作,例如 DateTime.AddHours(x) 或 DateTime.Subtract(x)。 Entity Framework 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 类型中不存在任何属性或字段“系统”
(删除命名空间没有帮助)。

使用所需的代码:

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

结果错误:LINQ to Entities 无法识别方法“System.DateTime AddHours(Double)”方法,并且该方法无法转换为存储表达式。

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

恐怕我只是用一些扩展来更新我的 github fork 以支持传入实体框架扩展,但在我这样做之前,想看看其他人是否有解决方案或想法。

注意:由于可能更改 SQL 数据存储技术,我没有使用 DateTimeOffset。

【问题讨论】:

    标签: c# .net entity-framework-6 dynamic-linq


    【解决方案1】:

    您可以使用自定义 ExpressionVisitor 对查询表达式进行后期处理,并将不受支持的方法替换为它们的 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();
    

    【讨论】:

    • 很棒的解决方案。感谢那!它帮助我更好地理解表达并学到了新的东西。出色的解决方案,开箱即用。我做了一些我想要的改动,但允许我使用 DbFunctions。这也解锁了其他潜在用途。希望这个答案在未来对其他人有所帮助!
    • Ivan,你有机会看看这个问题吗?时尚相似,只是坚持实施。 stackoverflow.com/questions/40271588/entity-framework-dayofweek
    • 当然,你去:)
    猜你喜欢
    • 2014-02-18
    • 2019-09-18
    • 1970-01-01
    • 1970-01-01
    • 2013-10-02
    • 2022-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多