动态Linq +entity framework:动态选择的日期时间修改

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

例:

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

通常在我们的tsql或甚至在使用lambda表达式的entity framework中,您可以添加所需的偏移量。 但是在动态linq选项中,您似乎无法像使用Linq2Sql那样执行任何日期操作,例如DateTime.AddHours(x)或DateTime.Subtract(x)。 entity framework6希望您使用DbFunctions.AddHours(x)等。但是动态linq代码在没有修改的情况下不会接受DbFunction而不会出错。

例:

 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之前执行日期时间数学运算。 一旦groupby发生,就不再有UTC的概念,因为用户将看到本地化的结果集。

我担心我只是用一些扩展来更新我的github fork以支持传递entity framework扩展,但在我做之前,想看看是否有其他人有解决方案或想法。

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

您可以使用自定义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();