3

ここで私たちがやりたいこと。

レポートを作成するためにフォーマットする必要があるデータベースからのデータがあります。これには、いくつかの計算 (合計、平均、およびフィールド間の計算 (例: xa / xb)) が含まれます。

制限の 1 つは、たとえば合計で、データの 1 つが null、-1、または -2 の場合、計算を停止して「-」を表示する必要があることです。生成するレポートが多数あり、それぞれに同じロジックと多くの計算があるため、このロジックを一元化したいと考えています。今のところ、生成するコードでは、フィールド間の計算 (xa / xb など) を確認できますが、グループの合計 (xb / SUM(xa) など) を確認することはできません。

テストケース

ルール

  • 計算で使用される値のいずれかが -1、-2、または null の場合、計算を実行しないでください。この場合、-1 または null が見つかった場合は "-" を返し、-2 が見つかった場合は "C" を返します。
  • 計算に複数の「悪い値」がある場合は、null -> -1 -> -2 のように定義された優先度を尊重する必要があります。この優先順位は、値が計算内にあるレベルとは無関係です

テスト

簡単な計算

オブジェクト: 新しい DataInfo { A = 10、B = 2、C = 4 }
計算: x => xA / xB + xC
結果: 9
オブジェクト: 新しい DataInfo { A = 10、B = 2、C = -2 }
計算: x => xA / xB + xC
結果:C(計算に「-2」の値があるため)
オブジェクト: 新しい DataInfo { A = 10、B = -2、C = null }
計算: x => xA / xB + xC
結果:-(計算に「null」値があり、-2値で勝つため)

複雑な計算

オブジェクト: var list = new List();
        list.Add(新しい DataInfo { A = 10, B = 2, C = 4 });
        list.Add(新しい DataInfo { A = 6, B = 3, C = 2 });
計算: list.Sum(x => xA / xB + list.Max(y => yC))
結果: 15
オブジェクト: var list = new List();
        list.Add(新しい DataInfo { A = 10, B = 2, C = 4 });
        list.Add(新しい DataInfo { A = 6, B = 3, C = -2 });
計算: list.Sum(x => xA / xB + list.Max(y => yC))
結果:C(計算に「-2」の値があるため)

これまでに行ったこと

ここで、このスレッドに基づいて単純な計算を処理する必要があるコード:
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<>と、エラーが発生します。

オブジェクトがターゲット タイプと一致しません

私たちが理解しているように、それは Sub-Expressionがではなく にy => y.UnitesDisponibles適用されているためです。IEnumerableRapport1Data

いつか次のような複雑な式がある場合、完全に再帰的になるように修正するにはどうすればよいですか。

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);
    }
}

[更新] やりたいことの詳細を追加

4

2 に答える 2

3

これを機能させるには、いくつか変更する必要があります。

  • 式を前処理して集計を実行する新しい 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; }
    }
}
于 2011-12-22T21:04:59.457 に答える
0

私が正しく理解していれば、あなたは「再帰的合計」関数を探しています。このようなことを提案してもいいですか?

    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
    }

*注:コードはテストされておらず、コンパイルさえされない可能性があります

于 2011-12-22T14:51:57.220 に答える