拜访IEnumerable孩子
作者:互联网
这是我们想要做的.
我们有数据库中需要格式化的数据才能生成报告,其中包括一些计算(总和,平均值和字段到字段的计算(例如:x.a / x.b)).
局限性之一是,例如,如果某一个数据总和为-1,则为-1或-2,则我们必须停止计算并显示“-”.由于我们要生成许多报告,每个报告具有相同的逻辑和许多计算,因此我们希望集中此逻辑.目前,我们生成的代码允许我们检查字段到字段的计算(例如x.a / x.b),但不能允许我们检查组总数(例如:x.b / SUM(x.a))
测试用例
规则
>如果计算中使用的值之一为-1,-2或为null,则不应进行计算.在这种情况下,如果找到-1或为null,则返回“-”,如果找到-2,则返回“ C”
>如果计算中有多个“错误值”,则需要遵守这样定义的优先级:null-> -1-> -2.此优先级与值在计算中的级别无关
测验
简单计算
object: new DataInfo { A = 10, B = 2, C = 4 } calcul: x => x.A / x.B + x.C result: 9
object: new DataInfo { A = 10, B = 2, C = -2 } calcul: x => x.A / x.B + x.C result: C (because you have a '-2' value in the calcul)
object: new DataInfo { A = 10, B = -2, C = null } calcul: x => x.A / x.B + x.C result: - (because you have a 'null' value in the calcul and it win on the -2 value)
复合计算
object: var list = new List(); list.Add(new DataInfo { A = 10, B = 2, C = 4 }); list.Add(new DataInfo { A = 6, B = 3, C = 2 }); calcul: list.Sum(x => x.A / x.B + list.Max(y => y.C)) result: 15
object: var list = new List(); list.Add(new DataInfo { A = 10, B = 2, C = 4 }); list.Add(new DataInfo { A = 6, B = 3, C = -2 }); calcul: list.Sum(x => x.A / x.B + list.Max(y => y.C)) result: C (because you have a '-2' value in the calcul)
到目前为止我们所做的
在这里,我们必须基于该线程处理简单计算的代码:
How to extract properties used in a Expression<Func<T, TResult>> query and test their value?
我们创建了一个强类型类,该类执行计算并以字符串形式返回结果.但是,如果表达式的任何部分等于一个特殊值,则计算器必须返回一个特殊字符.
对于像这样的简单情况,它工作得很好:
var data = new Rapport1Data() { UnitesDisponibles = 5, ... };
var q = new Calculator<Rapport1Data>()
.Calcul(data, y => y.UnitesDisponibles, "N0");
但是我需要能够执行更复杂的操作,例如:
IEnumerable<Rapport1Data> data = ...;
var q = new Calculator<IEnumerable<Rapport1Data>>()
.Calcul(data, x => x.Sum(y => y.UnitesDisponibles), "N0");
当我们开始在IEnurmarable中封装数据时.我们得到一个错误:
Object does not match target type
据我们了解,这是因为子表达式y => y.UnitesDisponibles应用于IEnumerable而不是Rapport1Data.
如果某天有复杂的表达式,我们如何解决它以确保它将完全递归:
IEnumerable<IEnumerable<Rapport1Data>> data = ...;
var q = new Calculator<IEnumerable<IEnumerable<Rapport1Data>>>()
.Calcul(data,x => x.Sum(y => y.Sum(z => z.UnitesDisponibles)), "N0");
我们建立的课程
public class Calculator<T>
{
public string Calcul(
T data,
Expression<Func<T, decimal?>> query,
string format)
{
var rulesCheckerResult = RulesChecker<T>.Check(data, query);
// l'ordre des vérifications est importante car il y a une gestion
// des priorités des codes à retourner!
if (rulesCheckerResult.HasManquante)
{
return TypeDonnee.Manquante.ReportValue;
}
if (rulesCheckerResult.HasDivisionParZero)
{
return TypeDonnee.DivisionParZero.ReportValue;
}
if (rulesCheckerResult.HasNonDiffusable)
{
return TypeDonnee.NonDiffusable.ReportValue;
}
if (rulesCheckerResult.HasConfidentielle)
{
return TypeDonnee.Confidentielle.ReportValue;
}
// if the query respect the rules, apply the query and return the
// value
var result = query.Compile().Invoke(data);
return result != null
? result.Value.ToString(format)
: TypeDonnee.Manquante.ReportValue;
}
}
和自定义ExpressionVisitor
class RulesChecker<T> : ExpressionVisitor
{
private readonly T data;
private bool hasConfidentielle = false;
private bool hasNonDiffusable = false;
private bool hasDivisionParZero = false;
private bool hasManquante = false;
public RulesChecker(T data)
{
this.data = data;
}
public static RulesCheckerResult Check(T data, Expression expression)
{
var visitor = new RulesChecker<T>(data);
visitor.Visit(expression);
return new RulesCheckerResult(
visitor.hasConfidentielle,
visitor.hasNonDiffusable,
visitor.hasDivisionParZero,
visitor.hasManquante);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (!this.hasDivisionParZero &&
node.NodeType == ExpressionType.Divide &&
node.Right.NodeType == ExpressionType.MemberAccess)
{
var rightMemeberExpression = (MemberExpression)node.Right;
var propertyInfo = (PropertyInfo)rightMemeberExpression.Member;
var value = Convert.ToInt32(propertyInfo.GetValue(this.data, null));
this.hasDivisionParZero = value == 0;
}
return base.VisitBinary(node);
}
protected override Expression VisitMember(MemberExpression node)
{
// Si l'un d'eux n'est pas à true, alors continuer de faire les tests
if (!this.hasConfidentielle ||
!this.hasNonDiffusable ||
!this.hasManquante)
{
var propertyInfo = (PropertyInfo)node.Member;
object value = propertyInfo.GetValue(this.data, null);
int? valueNumber = MTO.Framework.Common.Convert.To<int?>(value);
// Si la valeur est à true, il n'y a pas lieu de tester davantage
if (!this.hasManquante)
{
this.hasManquante =
valueNumber == TypeDonnee.Manquante.BdValue;
}
// Si la valeur est à true, il n'y a pas lieu de tester davantage
if (!this.hasConfidentielle)
{
this.hasConfidentielle =
valueNumber == TypeDonnee.Confidentielle.BdValue;
}
// Si la valeur est à true, il n'y a pas lieu de tester davantage
if (!this.hasNonDiffusable)
{
this.hasNonDiffusable =
valueNumber == TypeDonnee.NonDiffusable.BdValue;
}
}
return base.VisitMember(node);
}
}
[更新]
添加更多关于我们想做什么的细节
解决方法:
为了使它起作用,您需要进行一些更改:
>创建一个新的ExpressionVisitor,它将预处理您的表达式以执行聚合.
>在Calculator.Calcul方法中使用新的ExpressionVisitor.
>修改RulesChecker,以包含VisitConstant方法的替代.此新方法需要包含与VisitMember方法相同的逻辑.
>修改RulesChecker VisitBinary方法,以检查ConstantExpressions被零除的条件.
这是我认为需要完成的粗略示例.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace WindowsFormsApplication1 {
internal static class Program {
[STAThread]
private static void Main() {
var calculator = new Calculator();
//// DivideByZero - the result should be -1
var data1 = new DataInfo { A = 10, B = 0, C = 1 };
Expression<Func<DataInfo, decimal?>> expression1 = x => x.A / x.B + x.C;
var result1 = calculator.Calcul(data1, expression1, "N0");
//// Negative 1 - the result should be -
var data2 = new DataInfo { A = 10, B = 5, C = -1 };
Expression<Func<DataInfo, decimal?>> expression2 = x => x.A / x.B + x.C;
var result2 = calculator.Calcul(data2, expression2, "N0");
//// Negative 2 - the result should be C
var data3 = new DataInfo { A = 10, B = 5, C = -2 };
Expression<Func<DataInfo, decimal?>> expression3 = x => x.A / x.B + x.C;
var result3 = calculator.Calcul(data3, expression3, "N0");
//// the result should be 3
var data4 = new DataInfo { A = 10, B = 5, C = 1 };
Expression<Func<DataInfo, decimal?>> expression4 = x => x.A / x.B + x.C;
var result4 = calculator.Calcul(data4, expression4, "N0");
//// DivideByZero - the result should be -1
var data5 = new List<DataInfo> {
new DataInfo {A = 10, B = 0, C = 1},
new DataInfo {A = 10, B = 0, C = 1}
};
Expression<Func<IEnumerable<DataInfo>, decimal?>> expression5 = x => x.Sum(y => y.A) / x.Sum(y => y.B) + x.Sum(y => y.C);
var result5 = calculator.Calcul(data5, expression5, "N0");
//// the result should be 4
var data6 = new List<DataInfo> {
new DataInfo {A = 10, B = 5, C = 1},
new DataInfo {A = 10, B = 5, C = 1}
};
Expression<Func<IEnumerable<DataInfo>, decimal?>> expression6 = x => x.Sum(y => y.A) / x.Sum(y => y.B) + x.Sum(y => y.C);
var result6 = calculator.Calcul(data6, expression6, "N0");
//// the result should be -
var data7 = new List<DataInfo> {
new DataInfo {A = 10, B = 5, C = -1},
new DataInfo {A = 10, B = 5, C = 1}
};
Expression<Func<IEnumerable<DataInfo>, decimal?>> expression7 = x => x.Sum(y => y.A) / x.Sum(y => y.B) + x.Sum(y => y.C);
var result7 = calculator.Calcul(data7, expression7, "N0");
//// the result should be 14
var c1 = 1;
var c2 = 2;
var data8 = new DataInfo { A = 10, B = 1, C = 1 };
Expression<Func<DataInfo, decimal?>> expression8 = x => x.A / x.B + x.C + c1 + c2;
var result8 = calculator.Calcul(data8, expression8, "N0");
}
}
public class Calculator {
public string Calcul<T>(T data, LambdaExpression query, string format) {
string reportValue;
if (HasIssue(data, query, out reportValue)) {
return reportValue;
}
// executes the aggregates
query = (LambdaExpression)ExpressionPreProcessor.PreProcessor(data, query);
// checks the rules against the results of the aggregates
if (HasIssue(data, query, out reportValue)) {
return reportValue;
}
Delegate lambda = query.Compile();
decimal? result = (decimal?)lambda.DynamicInvoke(data);
return result != null
? result.Value.ToString(format)
: TypeDonnee.Manquante.ReportValue;
}
private bool HasIssue(object data, LambdaExpression query, out string reportValue) {
reportValue = null;
var rulesCheckerResult = RulesChecker.Check(data, query);
if (rulesCheckerResult.HasManquante) {
reportValue = TypeDonnee.Manquante.ReportValue;
}
if (rulesCheckerResult.HasDivisionParZero) {
reportValue = TypeDonnee.DivisionParZero.ReportValue;
}
if (rulesCheckerResult.HasNonDiffusable) {
reportValue = TypeDonnee.NonDiffusable.ReportValue;
}
if (rulesCheckerResult.HasConfidentielle) {
reportValue = TypeDonnee.Confidentielle.ReportValue;
}
return reportValue != null;
}
}
internal class ExpressionPreProcessor : ExpressionVisitor {
private readonly object _source;
public static Expression PreProcessor(object source, Expression expression) {
if (!IsValidSource(source)) {
return expression;
}
var visitor = new ExpressionPreProcessor(source);
return visitor.Visit(expression);
}
private static bool IsValidSource(object source) {
if (source == null) {
return false;
}
var type = source.GetType();
return type.IsGenericType && type.GetInterface("IEnumerable") != null;
}
public ExpressionPreProcessor(object source) {
this._source = source;
}
protected override Expression VisitMethodCall(MethodCallExpression node) {
if (node.Method.DeclaringType == typeof(Enumerable) && node.Arguments.Count == 2) {
switch (node.Method.Name) {
case "Count":
case "Min":
case "Max":
case "Sum":
case "Average":
var lambda = node.Arguments[1] as LambdaExpression;
var lambaDelegate = lambda.Compile();
var value = node.Method.Invoke(null, new object[] { this._source, lambaDelegate });
return Expression.Constant(value);
}
}
return base.VisitMethodCall(node);
}
}
internal class RulesChecker : ExpressionVisitor {
private readonly object data;
private bool hasConfidentielle = false;
private bool hasNonDiffusable = false;
private bool hasDivisionParZero = false;
private bool hasManquante = false;
public RulesChecker(object data) {
this.data = data;
}
public static RulesCheckerResult Check(object data, Expression expression) {
if (IsIEnumerable(data)) {
var result = new RulesCheckerResult(false, false, false, false);
IEnumerable dataItems = (IEnumerable)data;
foreach (object dataItem in dataItems) {
result = MergeResults(result, GetResults(dataItem, expression));
}
return result;
}
else {
return GetResults(data, expression);
}
}
private static RulesCheckerResult MergeResults(RulesCheckerResult results1, RulesCheckerResult results2) {
var hasConfidentielle = results1.HasConfidentielle || results2.HasConfidentielle;
var hasDivisionParZero = results1.HasDivisionParZero || results2.HasDivisionParZero;
var hasManquante = results1.HasManquante || results2.HasManquante;
var hasNonDiffusable = results1.HasNonDiffusable || results2.HasNonDiffusable;
return new RulesCheckerResult(hasConfidentielle, hasNonDiffusable, hasDivisionParZero, hasManquante);
}
private static RulesCheckerResult GetResults(object data, Expression expression) {
var visitor = new RulesChecker(data);
visitor.Visit(expression);
return new RulesCheckerResult(
visitor.hasConfidentielle,
visitor.hasNonDiffusable,
visitor.hasDivisionParZero,
visitor.hasManquante);
}
private static bool IsIEnumerable(object source) {
if (source == null) {
return false;
}
var type = source.GetType();
return type.IsGenericType && type.GetInterface("IEnumerable") != null;
}
protected override Expression VisitBinary(BinaryExpression node) {
if (!this.hasDivisionParZero && node.NodeType == ExpressionType.Divide) {
if (node.Right.NodeType == ExpressionType.MemberAccess) {
var rightMemeberExpression = (MemberExpression)node.Right;
var propertyInfo = (PropertyInfo)rightMemeberExpression.Member;
var value = Convert.ToInt32(propertyInfo.GetValue(this.data, null));
this.hasDivisionParZero = value == 0;
}
if (node.Right.NodeType == ExpressionType.Constant) {
var rightConstantExpression = (ConstantExpression)node.Right;
var value = Convert.ToInt32(rightConstantExpression.Value);
this.hasDivisionParZero = value == 0;
}
}
return base.VisitBinary(node);
}
protected override Expression VisitConstant(ConstantExpression node) {
this.CheckValue(this.ConvertToNullableInt(node.Value));
return base.VisitConstant(node);
}
protected override Expression VisitMember(MemberExpression node) {
if (!this.hasConfidentielle || !this.hasNonDiffusable || !this.hasManquante) {
var propertyInfo = node.Member as PropertyInfo;
if (propertyInfo != null) {
var value = propertyInfo.GetValue(this.data, null);
this.CheckValue(this.ConvertToNullableInt(value));
}
}
return base.VisitMember(node);
}
private void CheckValue(int? value) {
if (!this.hasManquante) {
this.hasManquante = value == TypeDonnee.Manquante.BdValue;
}
if (!this.hasConfidentielle) {
this.hasConfidentielle = value == TypeDonnee.Confidentielle.BdValue;
}
if (!this.hasNonDiffusable) {
this.hasNonDiffusable = value == TypeDonnee.NonDiffusable.BdValue;
}
}
private int? ConvertToNullableInt(object value) {
if (!value.GetType().IsPrimitive) {
return int.MinValue;
}
// MTO.Framework.Common.Convert.To<int?>(value);
return (int?)value;
}
}
class RulesCheckerResult {
public bool HasConfidentielle { get; private set; }
public bool HasNonDiffusable { get; private set; }
public bool HasDivisionParZero { get; private set; }
public bool HasManquante { get; private set; }
public RulesCheckerResult(bool hasConfidentielle, bool hasNonDiffusable, bool hasDivisionParZero, bool hasManquante) {
this.HasConfidentielle = hasConfidentielle;
this.HasNonDiffusable = hasNonDiffusable;
this.HasDivisionParZero = hasDivisionParZero;
this.HasManquante = hasManquante;
}
}
class TypeDonnee {
public static readonly TypeValues Manquante = new TypeValues(null, "-");
public static readonly TypeValues Confidentielle = new TypeValues(-1, "-");
public static readonly TypeValues NonDiffusable = new TypeValues(-2, "C");
public static readonly TypeValues DivisionParZero = new TypeValues(0, "-1");
}
class TypeValues {
public int? BdValue { get; set; }
public string ReportValue { get; set; }
public TypeValues(int? bdValue, string reportValue) {
this.BdValue = bdValue;
this.ReportValue = reportValue;
}
}
class DataInfo {
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
}
标签:c-4-0,expressionvisitor,linq,c 来源: https://codeday.me/bug/20191202/2084660.html