3 Stimmen

Besuch von IEnumerable<T> children

Das ist es, was wir tun wollen.

Wir haben Daten aus der Datenbank, die wir für einen Bericht formatieren müssen, einschließlich einiger Berechnungen (Summe, Durchschnittswerte und Feld-zu-Feld-Berechnungen (z.B.: x.a / x.b)).

Eine der Einschränkungen besteht darin, dass die Berechnung abgebrochen und ein "-" angezeigt werden muss, wenn z. B. in einer Summe eine der Daten Null, -1 oder -2 ist. Da wir viele Berichte mit derselben Logik und vielen Berechnungen in jedem Bericht erstellen müssen, möchten wir diese Logik zentralisieren. Mit dem von uns erstellten Code können wir zwar die Berechnung von Feld zu Feld überprüfen (z. B. x.a / x.b), aber nicht die Gruppensumme (z. B. x.b / SUM(x.a)).

Testfall

Regeln

  • Die Berechnung sollte nicht getan werden wenn einer der in der Berechnung verwendeten Werte -1, -2 oder Null ist. In diesem Fall wird "-" zurückgegeben, wenn Sie -1 oder null finden, und "C", wenn Sie -2 finden.
  • Wenn Sie mehrere "schlechte Werte" in der Berechnung haben, müssen Sie eine Priorität einhalten, die wie folgt definiert ist: null -> -1 -> -2. Diese Priorität ist unabhängig von der Ebene, auf der sich der Wert in der Berechnung befindet

Tests

Einfache Berechnung

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)

Komplexe Berechnung

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)

Was wir bis jetzt getan haben

Hier der Code, den wir bearbeiten müssen einfache Berechnungen auf der Grundlage dieses Threads:
Wie extrahiert man Eigenschaften, die in einer Expression<Func<T, TResult>>-Abfrage verwendet werden, und testet ihren Wert?

Wir haben eine Klasse vom Typ strongly erstellt, die eine Berechnung durchführt und das Ergebnis als String zurückgibt. Aber wenn ein Teil des Ausdrucks gleich einem speziellen Wert ist, muss der Rechner ein spezielles Zeichen zurückgeben.

In einem einfachen Fall wie diesem funktioniert sie gut:

var data = new Rapport1Data() { UnitesDisponibles = 5, ... };
var q = new Calculator<Rapport1Data>()
    .Calcul(data, y => y.UnitesDisponibles, "N0");

Aber ich muss in der Lage sein, etwas Komplizierteres zu machen wie:

IEnumerable<Rapport1Data> data = ...;
var q = new Calculator<IEnumerable<Rapport1Data>>()
    .Calcul(data, x => x.Sum(y => y.UnitesDisponibles), "N0");

Wenn wir damit beginnen, Daten zu kapseln oder in IEnurmarable<> erhalten wir einen Fehler:

Objekt entspricht nicht dem Zieltyp

Nach unserem Verständnis liegt das daran, dass der Unter-Ausdruck y => y.UnitesDisponibles wird auf den IEnumerable anstelle der Rapport1Data .

Wie können wir sicherstellen, dass er vollständig rekursiv ist, wenn wir eines Tages einen komplexen Ausdruck wie:

IEnumerable<IEnumerable<Rapport1Data>> data = ...;
var q = new Calculator<IEnumerable<IEnumerable<Rapport1Data>>>()
    .Calcul(data,x => x.Sum(y => y.Sum(z => z.UnitesDisponibles)), "N0");

Klassen, die wir gebaut haben

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

und der benutzerdefinierte 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);
    }
}

[UPDATE] Genauere Angaben dazu, was wir tun wollen

3voto

Tom Brothers Punkte 5849

Es gibt ein paar Dinge, die Sie ändern müssen, damit dies funktioniert:

  • Erstellen Sie einen neuen ExpressionVisitor, der Ihren Ausdruck vorverarbeitet, um die Aggregate auszuführen.
  • Verwenden Sie den neuen ExpressionVisitor in der Calculator.Calcul-Methode.
  • Ändern Sie den RulesChecker so, dass er einen Override für die Methode VisitConstant enthält. Diese neue Methode muss die gleiche Logik wie die VisitMember-Methode enthalten.
  • Ändern Sie die RulesChecker-Methode VisitBinary, um die Bedingung "Teilen durch Null" für ConstantExpressions zu prüfen.

Hier ist ein grobes Beispiel dafür, was meiner Meinung nach getan werden muss.

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

0voto

MBirchmeier Punkte 134

Wenn ich richtig verstanden habe, suchen Sie eine "rekursive Summenfunktion". Darf ich etwas wie dieses vorschlagen?

    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
    }

*Anmerkung: Code nicht getestet, möglicherweise nicht einmal kompilierbar

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X