Dynamic Linq + Entity Framework:动态 select 的日期时间修改
Dynamic Linq + Entity Framework: datetime modifications for dynamic select
我正在尝试找到一种方法,在进行 sql 分组之前将 UTC 时间移动到本地时间。我正在使用 System.Linq.Dynamic(在此处管理 https://github.com/kahanu/System.Linq.Dynamic)。它非常适合在编译时没有必填字段的情况下执行动态 selects。在我们的例子中,我们将所有日期时间存储在 UTC 中。在这个动态select中,可能有人想对小时、年、月等进行分组。在这种情况下,我们必须将数据移动到本地时间,以防止混淆。
示例:
var select = queryable.Select(string.Format("new ({0}, {1})", datetimeColumn, someOtherColumn));
通常在我们的 tsql 甚至 entity framework 中使用 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));
Returns 错误:类型 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 分支以支持传递 entity framework 扩展,但在我这样做之前,想看看是否有其他人有解决方案或想法。
注意:由于可能会更改 SQL 数据存储技术,我没有使用 DateTimeOffset。
您可以 post 使用自定义 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();
我正在尝试找到一种方法,在进行 sql 分组之前将 UTC 时间移动到本地时间。我正在使用 System.Linq.Dynamic(在此处管理 https://github.com/kahanu/System.Linq.Dynamic)。它非常适合在编译时没有必填字段的情况下执行动态 selects。在我们的例子中,我们将所有日期时间存储在 UTC 中。在这个动态select中,可能有人想对小时、年、月等进行分组。在这种情况下,我们必须将数据移动到本地时间,以防止混淆。
示例:
var select = queryable.Select(string.Format("new ({0}, {1})", datetimeColumn, someOtherColumn));
通常在我们的 tsql 甚至 entity framework 中使用 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));
Returns 错误:类型 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 分支以支持传递 entity framework 扩展,但在我这样做之前,想看看是否有其他人有解决方案或想法。
注意:由于可能会更改 SQL 数据存储技术,我没有使用 DateTimeOffset。
您可以 post 使用自定义 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();