В гостях у IEnumerable <T>детей - PullRequest
       43

В гостях у IEnumerable <T>детей

3 голосов
/ 21 декабря 2011

Вот что мы хотим сделать.

У нас есть данные из базы данных, которые нам нужно отформатировать для составления отчета, включая некоторые расчеты (сумма, средние и расчеты от поля к полю (например: xa /XB)).

Одним из ограничений является то, что если в сумме для примера одно из данных равно нулю, -1 или -2, мы должны остановить вычисление и отобразить '-'.Поскольку у нас есть много отчетов с одинаковой логикой и множеством вычислений в каждом, мы хотим централизовать эту логику.На данный момент код, который мы производим, позволяет нам проверять вычисление от поля к полю (например, xa / xb), но не позволяет нам проверять общее количество групп (например: xb / SUM (xa))

Контрольный пример

Правила

  • Исчисление не должно выполняться , если одно из значений, используемых в исчислении, равно -1, -2 или равно нулю.В этом случае верните «-», если вы найдете -1 или ноль, и «C», если вы найдете -2
  • Если у вас есть несколько «плохих значений» в исчислении, вы должны соблюдать приоритет, определенныйкак это: нуль -> -1 -> -2.Этот приоритет не зависит от уровня, в котором значение находится в исчислении

Тесты

Простое вычисление

object: new DataInfo { A = 10, B = 2, C = 4 }
calcul: x => x.A / x.B + x.C
result: <b>9</b>
object: new DataInfo { A = 10, B = 2, C = <b>-2</b> }
calcul: x => x.A / x.B + x.C
result: <b>C</b> (because you have a '-2' value in the calcul)
object: new DataInfo { A = 10, B = -2, C = <b>null</b> }
calcul: x => x.A / x.B + x.C
result: <b>-</b> (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: <b>15</b>
object: var list = new List();
        list.Add(new DataInfo { A = 10, B = 2, C = 4 });
        list.Add(new DataInfo { A = 6, B = 3, C = <b>-2</b> });
calcul: list.Sum(x => x.A / x.B + list.Max(y => y.C))
result: <b>C</b> (because you have a '-2' value in the calcul)

Что мы сделали до сих пор

Вот код, который мы должны обработать простые исчисления , основываясь на этой теме:
Как извлечь свойства, используемые в выражении> запросить и проверить их значение?

Мы создали класс строго типа, который выполняет вычисление и возвращает результат в виде строки.Но если какая-либо часть выражения равна специальному значению, калькулятор должен возвращать специальный символ.

Он хорошо работает для простого случая, такого как этот:

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

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

[ОБНОВЛЕНИЕ] Добавление более подробной информации о том, что мы хотим сделать

Ответы [ 2 ]

3 голосов
/ 23 декабря 2011

Есть несколько вещей, которые нужно изменить, чтобы это заработало:

  • Создайте новый ExpressionVisitor, который будет предварительно обрабатывать ваше выражение для выполнения агрегатов.
  • Используйте новый ExpressionVisitor в методе Calculator.Calcul.
  • Измените RulesChecker, чтобы включить переопределение для метода VisitConstant. Этот новый метод должен включать ту же логику, что и в методе VisitMember.
  • Измените метод RulesBhecker 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; }
    }
}
0 голосов
/ 22 декабря 2011

Если я правильно понимаю, вы ищете функцию 'рекурсивная сумма'. Могу ли я предложить что-то подобное?

    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
    }

* примечание: код не проверен, может даже не скомпилироваться

...