ここで私たちがやりたいこと。
レポートを作成するためにフォーマットする必要があるデータベースからのデータがあります。これには、いくつかの計算 (合計、平均、およびフィールド間の計算 (例: 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
適用されているためです。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;
}
}
およびカスタム 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);
}
}
[更新] やりたいことの詳細を追加