Entity Framework + 迪沃克
Entity Framework + DayOfWeek
使用 System.Linq.Dynamic
(在此处管理 https://github.com/kahanu/System.Linq.Dynamic ),我试图使用 [=52= 捕获 DateTime
上找到的 DayOfWeek
字段以进行聚合] 6(或更大)。
之前问过类似的东西,帮了大忙,
Entity Framework 支持使用
获取 DayOfWeek
SqlFunctions.DatePart("dw", datetime?)
或者我们可以使用
之类的东西做一些更需要的事情
DbFunctions.DiffDays(date?, date?).
想法:
Getting the DayOfWeek in Linq to Entities
我发现这很有趣,我喜欢它,因为它不使用 SqlFunctions
这可能会让我局限于 SQL 服务器。此外,开发人员可以控制一周的第一天,而无需查询 SQL 服务器属性来查找其配置方式(第一天)。
出于实验目的,我一直在尝试在 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);
}
使用上面的想法,我想我可以包装这个表达式,然后将模值应用于输入时间,但我什至无法让这个表达式更进一步。
我觉得我缺少表达式构建方式的基本部分。我遇到的错误
Argument types do not match
at System.Linq.Expressions.Expression.Bind(MemberInfo member, Expression expression)
at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection1 nodes, Func
2 elementVisitor)
at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at QueryableExtensions.DbFunctionsBinder.VisitMethodCall(MethodCallExpression node) in Path\QueryableExtensions.cs:line 48
at BindDbFunctions(IQueryable source) in Path\QueryableExtensions.cs:line 13
at AggregateHelper.d__15.MoveNext() in Path\AggregateHelper.cs:line 811
--- End of stack trace from previous location where exception was thrown
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at AggregationPluginServiceHelper.d__9.MoveNext() in Path\AggregationPluginServiceHelper.cs:line 199
我知道我可以做到这一点,因为我编写了内联编译的查询。那很好用。不过这个是专门用动态库的。
示例用法为:
var grouping = select.GroupBy("new (DateTimeColumn.DayOfWeek)", "it");
有没有更好的方法来获取星期几?我知道这可能在文化上有所不同,所以我认为与周日不同的日子的模数(link 上面的想法)是正确的方法。
所以你基本上需要转换像
这样的表达式
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);
}
使用 System.Linq.Dynamic
(在此处管理 https://github.com/kahanu/System.Linq.Dynamic ),我试图使用 [=52= 捕获 DateTime
上找到的 DayOfWeek
字段以进行聚合] 6(或更大)。
之前问过类似的东西,帮了大忙,
Entity Framework 支持使用
获取DayOfWeek
SqlFunctions.DatePart("dw", datetime?)
或者我们可以使用
之类的东西做一些更需要的事情DbFunctions.DiffDays(date?, date?).
想法:
Getting the DayOfWeek in Linq to Entities
我发现这很有趣,我喜欢它,因为它不使用 SqlFunctions
这可能会让我局限于 SQL 服务器。此外,开发人员可以控制一周的第一天,而无需查询 SQL 服务器属性来查找其配置方式(第一天)。
出于实验目的,我一直在尝试在 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);
}
使用上面的想法,我想我可以包装这个表达式,然后将模值应用于输入时间,但我什至无法让这个表达式更进一步。
我觉得我缺少表达式构建方式的基本部分。我遇到的错误
Argument types do not match
at System.Linq.Expressions.Expression.Bind(MemberInfo member, Expression expression)
at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection1 nodes, Func
2 elementVisitor)
at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at QueryableExtensions.DbFunctionsBinder.VisitMethodCall(MethodCallExpression node) in Path\QueryableExtensions.cs:line 48
at BindDbFunctions(IQueryable source) in Path\QueryableExtensions.cs:line 13
at AggregateHelper.d__15.MoveNext() in Path\AggregateHelper.cs:line 811--- End of stack trace from previous location where exception was thrown
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at AggregationPluginServiceHelper.d__9.MoveNext() in Path\AggregationPluginServiceHelper.cs:line 199
我知道我可以做到这一点,因为我编写了内联编译的查询。那很好用。不过这个是专门用动态库的。
示例用法为:
var grouping = select.GroupBy("new (DateTimeColumn.DayOfWeek)", "it");
有没有更好的方法来获取星期几?我知道这可能在文化上有所不同,所以我认为与周日不同的日子的模数(link 上面的想法)是正确的方法。
所以你基本上需要转换像
这样的表达式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);
}