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

Tra*_*den 2 .net c# dynamic-linq entity-framework-6

我正在尝试找到一种方法在执行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));
Run Code Online (Sandbox Code Playgroud)

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

例:

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

返回错误:XXX类型
中不存在属性或字段"System" (删除命名空间无效).

使用所需的代码:

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

结果有错误:LINQ to Entities无法识别方法'System.DateTime AddHours(Double)'方法,并且此方法无法转换为商店表达式.

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

我担心我只是为了支持传递实体框架扩展而更新我的github fork以支持传递实体框架扩展,但在此之前,想知道是否有其他人有解决方案或想法.

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

Iva*_*oev 5

您可以使用自定义后处理查询表达式,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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

和样品用法:

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

  • 很棒的解决方案.感谢那!它帮助我更好地理解表达并学到新东西.优秀的解决方案,开箱即用.我做了一些我想要的改变,但允许我使用DbFunctions.这也解锁了其他潜在的用途.希望这个答案将来能帮助别人! (3认同)