【问题标题】:Entity Framework + DayOfWeek实体框架 + DayOfWeek
【发布时间】:2016-10-26 20:29:31
【问题描述】:

使用System.Linq.Dynamic(在此处管理https://github.com/kahanu/System.Linq.Dynamic),我正在尝试使用实体框架6(或更高版本)捕获DateTime 上的DayOfWeek 字段以用于聚合目的。

之前求过类似的东西,帮了大忙,Dynamic Linq + Entity Framework: datetime modifications for dynamic select

Entity Framework 支持使用

获取DayOfWeek
SqlFunctions.DatePart("dw", datetime?)

或者我们可以使用类似

的东西做一些更需要的事情
DbFunctions.DiffDays(date?, date?).  

想法:

Getting the DayOfWeek in Linq to Entities

我发现这很有趣,我喜欢它,因为它不使用 SqlFunctions,这可能会让我受限于 SQL Server。此外,开发人员可以控制一周的第一天是什么,而无需查询 SQL Server 属性以查找其配置方式(第一天)。

出于实验目的,我一直在尝试在 VisitMember() 覆盖中实现这一点:

protected override Expression VisitMember(MemberExpression node)
{
     if (node.Type == typeof(System.DayOfWeek))
     {
          var firstSunday = new DateTime(1753, 1, 7);

                var firstSundayExpression = Expression.Constant(firstSunday, typeof(DateTime?));
                var timeValue = node.Expression;
                if (timeValue.Type != typeof(DateTime?)) timeValue = Expression.Convert(timeValue, typeof(DateTime?));
                var methodCall = Expression.Call(
                              typeof(DbFunctions), "DiffDays", Type.EmptyTypes, firstSundayExpression, timeValue);
                return Expression.Convert(methodCall, typeof(int?));
      }

      return base.VisitMember(node);
 }

使用上面的想法,我想我可以包装这个表达式,然后将模值应用于输入时间,但我什至无法让这个表达式更进一步。

我觉得我错过了如何构建表达式的基本部分。我遇到的错误

参数类型不匹配

在 System.Linq.Expressions.Expression.Bind(MemberInfo 成员,表达式表达式)
在 System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection1 nodes, Func2 elementVisitor)
在 System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression 节点)
在 System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 节点)
在 System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression 节点)
在 System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider 节点)
在 System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression 节点)
在 Path\QueryableExtensions.cs:line 48 中的 QueryableExtensions.DbFunctionsBinder.VisitMethodCall(MethodCallExpression 节点)
在 Path\QueryableExtensions.cs:line 13 中的 BindDbFunctions(IQueryable source)
在 Path\AggregateHelper.cs:line 811 中的 AggregateHelper.d__15.MoveNext()

--- 从先前抛出异常的位置结束堆栈跟踪

在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(任务任务)
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务任务)
在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
在 Path\AggregationPluginServiceHelper.cs:line 199 中的 AggregationPluginServiceHelper.d__9.MoveNext()

我知道我可以做到这一点是我编写了内联编译的查询。这很好用。但这是专门使用动态库的。

示例用法如下:

var grouping = select.GroupBy("new (DateTimeColumn.DayOfWeek)", "it");

有没有更好的方法来获取星期几?我知道这可能在文化上有所不同,所以我相信与星期日不同的日子模数(上面的链接想法)是正确的方法。

【问题讨论】:

  • 您在查询后没有计算这个是有原因的吗?在您调用动态 linq 后,这样做可能会更容易

标签: c# linq entity-framework-6 expression-trees dynamic-linq


【解决方案1】:

所以你基本上需要转换一个表达式

expr.DayOfWeek

var firstSunday = new DateTime(1753, 1, 7);
(DayOfWeek)(((int)DbFunctions.DiffDays((DateTime?)firstSunday, (DateTime?)expr)) % 7)

您可以这样做:

protected override Expression VisitMember(MemberExpression node)
{
    if (node.Type == typeof(DayOfWeek))
    {
        var expr = node.Expression;
        var firstSunday = new DateTime(1753, 1, 7);
        var diffDays = Expression.Convert(
            Expression.Call(
                typeof(DbFunctions), "DiffDays", Type.EmptyTypes,
                Expression.Constant(firstSunday, typeof(DateTime?)),
                Expression.Convert(expr, typeof(DateTime?))),
            typeof(int));
        var dayOfWeek = Expression.Convert(
            Expression.Modulo(diffDays, Expression.Constant(7)),
            typeof(DayOfWeek));
        return dayOfWeek;
    }
    return base.VisitMember(node);
}

更新:可以通过使用编译时原型表达式来简化该过程,使用小型辅助实用程序将参数替换为实际值:

public static class ExpressionUtils
{
    public static Expression<Func<T, TResult>> Expr<T, TResult>(Expression<Func<T, TResult>> e) => e;
    public static Expression<Func<T1, T2, TResult>> Expr<T1, T2, TResult>(Expression<Func<T1, T2, TResult>> e) => e;
    public static Expression<Func<T1, T2, T3, TResult>> Expr<T1, T2, T3, TResult>(Expression<Func<T1, T2, T3, TResult>> e) => e;
    public static Expression<Func<T1, T2, T3, T4, TResult>> Expr<T1, T2, T3, T4, TResult>(Expression<Func<T1, T2, T3, T4, TResult>> e) => e;
    public static Expression WithParameters(this LambdaExpression expression, params Expression[] values)
    {
        return expression.Parameters.Zip(values, (p, v) => new { p, v })
            .Aggregate(expression.Body, (e, x) => e.ReplaceParameter(x.p, x.v));
    }
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
    {
        return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
    }
    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == Source ? Target : base.VisitParameter(node);
        }
    }
}

这与 C#6 静态导入功能相结合,使实现更加简单易读。

例如:

using static System.Linq.Expressions.Expression;
using static ExpressionUtils;

有问题的方法现在看起来像这样:

protected override Expression VisitMember(MemberExpression node)
{
    if (node.Type == typeof(DayOfWeek))
    {
        return Expr((DateTime dateValue1, DateTime dateValue2) => 
            (DayOfWeek)(DbFunctions.DiffDays(dateValue1, dateValue2).Value % 7))
            .WithParameters(Constant(new DateTime(1753, 1, 7)), Visit(node.Expression));
    }
    return base.VisitMember(node);
}

还有你之前关于AddHours的问题:

protected override Expression VisitMethodCall(MethodCallExpression node)
{
    if (node.Object != null && node.Object.Type == typeof(DateTime))
    {
        if (node.Method.Name == "AddHours")
        {
            return Expr((DateTime timeValue, double addValue) => 
                DbFunctions.AddHours(timeValue, (int)addValue).Value)
                .WithParameters(Visit(node.Object), Visit(node.Arguments[0]));
        }
    }
    return base.VisitMethodCall(node);
}

【讨论】:

  • 不客气:) 要记住的最重要的事情是,您不应该更改要替换的表达式的数据类型。 Convert 只是 Expression 相当于 C# cast 运算符。干杯。
  • 啊,明白了。我刚刚添加了另一个功能...重新阅读您的提示。我正在返回一个(DateTime?),但在 DateTime.Date 的情况下,它需要一个(DateTime)。一定要注意类型细节。非常感谢。
  • 看起来你要制作更多这样的东西,所以作为奖励,我在答案中添加了一种更简单、更不容易出错的方法。
  • 太棒了!例如,我创建的其中一个变成了 if (node.Member.Name == "Date") { return Expr((DateTime dateValue1) => DbFunctions.TruncateTime(dateValue1).Value).WithParameters(Visit(node.表达)); }
  • 在学习更多关于表达式的过程中,我还接触到了 C#6 的新特性。当然也更不容易出错。非常感谢更新。太酷了。
猜你喜欢
  • 2017-07-07
  • 2020-04-11
  • 1970-01-01
  • 2015-12-26
  • 2014-04-01
  • 2014-11-11
  • 2021-05-01
  • 2010-10-07
  • 2011-08-21
相关资源
最近更新 更多