Вот что мы хотим сделать.
У нас есть данные из базы данных, которые нам нужно отформатировать для составления отчета, включая некоторые расчеты (сумма, средние и расчеты от поля к полю (например: 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);
}
}
[ОБНОВЛЕНИЕ] Добавление более подробной информации о том, что мы хотим сделать