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