探访数不清<T>的孩子



我们要这样做。

我们有来自数据库的数据,我们需要格式化以制作报告,包括一些计算(Sum, average和字段到字段的计算(例如:x.a/x.b))。

其中一个限制是,如果,例如在求和中,其中一个数据为null, -1或-2,我们必须停止计算并显示'-'。由于我们要生成许多报告,每个报告都具有相同的逻辑和许多计算,因此我们希望集中这些逻辑。目前,我们生成的代码允许我们检查字段到字段的计算(例如x.a/x.b),但不允许我们检查group total(例如:x.b/SUM(x.a))

<标题> 的测试用例

  • 如果计算中使用的值之一是-1,-2或null,则不应执行。在这种情况下,如果找到-1或null,返回"-",如果找到-2,返回"C"
  • 如果你在计算中有多个"坏值",你需要尊重这样定义的优先级:null -> -1 -> -2。该优先级与计算
  • 中值的级别无关。

测试

简单计算<>之前对象:new DataInfo {A = 10, B = 2, C = 4}计算:x => x.A/x.B + x.C结果: 9之前<>之前对象:new DataInfo {A = 10, B = 2, C = -2}计算:x => x.A/x.B + x.C结果:C(因为你在计算中有一个'-2'值)之前<>之前对象:new DataInfo {A = 10, B = -2, C = null}计算:x => x.A/x.B + x.C结果:-(因为你在计算中有一个'null'值,它在-2值上获胜)之前

复杂的计算<>之前object: var list = new list ();列表。添加(new DataInfo {A = 10, B = 2, C = 4});列表。添加(new DataInfo {A = 6, B = 3, C = 2});计算:列表。Sum(x => x.A/x.B + list)Max(y => y. c)结果: 15之前<>之前object: var list = new list ();列表。添加(new DataInfo {A = 10, B = 2, C = 4});列表。添加(新DataInfo {= 6, B = 3, C = 2 });计算:列表。Sum(x => x.A/x.B + list)Max(y => y. c)结果:C(因为你在计算中有一个'-2'值)之前

我们已经做了什么

这里的代码,我们必须处理简单的计算,基于这个线程:
如何提取Expression<T、result>>中使用的属性查询和测试它们的值?

创建了一个强类型类,它执行计算并返回String类型的结果。但是,如果表达式的任何部分等于一个特殊值,则计算器必须返回一个特殊字符。

对于简单的情况,它工作得很好,就像下面这个:

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<>中封装数据时,我们得到一个错误:

对象不匹配目标类型

正如我们所理解的,这是因为子表达式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,它将预处理表达式以执行聚合。
  • 在计算器中使用新的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; }
    }
}

如果我理解正确,你正在寻找一个'递归求和'函数。我可以建议这样做吗?

    var q = new Calculator<Rapport1Data>()
.Calcul(data, y => RecursiveCalc(y), "N0");
    double RecursiveCalc(object toCalc)
    {
         var asRapport = toCalc as Rapport1Data;
         if (asRapport != null)
             return asRapport.UnitesDisponibles;
         var asEnumerable = toCalc as IEnumerable;
         if (asEnumerable != null)
             return asEnumerable.Sum(y => RecursiveCalc(y));

         return 0; // handle a condition for unexpected types
    }

*注意:未测试的代码,甚至可能无法编译

最新更新