Note that there are some explanatory texts on larger screens.

plurals
  1. POVisiting IEnumerable<T> children
    text
    copied!<p>Here what we want to do.</p> <p>We have data from the database that we need to format to make a report, including some calculation (Sum, Averages, and field to field calculation (ex : x.a / x.b)). </p> <p>One of the limitations is that if, in a sum for exemple, one of the data is null, -1 or -2 we have to stop the calculation and display '-'. Since we have many reports to produce, with the same logic and many calculation in each, we want to centralise this logic. For now, the code we produce allow us to check for field to field calculation (x.a / x.b for exemple), but can't allow us to check for group total (ex: x.b / SUM(x.a))</p> <h1>Test case</h1> <h2>Rules</h2> <ul> <li>The calcul <strong>should not be done</strong> if one of the value used in the calcul is -1, -2 or null. In this case, return "-" if you find -1 or null, and "C" if you find -2</li> <li>If you have multiple "bad values" in the calcul, you need to respect a priority defined like this: null -> -1 -> -2. This priority is independant of the level where the value is in the calcul</li> </ul> <h2>Tests</h2> <p>Simple calcul</p> <pre> object: new DataInfo { A = 10, B = 2, C = 4 } calcul: x => x.A / x.B + x.C result: <b>9</b> </pre> <pre> 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) </pre> <pre> 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) </pre> <p>Complex calcul</p> <pre> 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> </pre> <pre> 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) </pre> <h2>What we have done so far</h2> <p>Here the code we have to handle <strong>simple calculs</strong>, based on this thread:<br> <a href="https://stackoverflow.com/questions/8508316/how-to-extract-properties-used-in-a-expressionfunct-tresult-query-and-test/8509503#8509503">How to extract properties used in a Expression&lt;Func&lt;T, TResult&gt;&gt; query and test their value?</a></p> <p>We have created a strongly type class that perform a calcul and return the result as a String. But if any part of the expression is equal to a special value, the calculator has to return a special character.</p> <p>It works well for a simple case, like this one:</p> <pre><code>var data = new Rapport1Data() { UnitesDisponibles = 5, ... }; var q = new Calculator&lt;Rapport1Data&gt;() .Calcul(data, y =&gt; y.UnitesDisponibles, "N0"); </code></pre> <p>But I need to be able to perform something more complicated like:</p> <pre><code>IEnumerable&lt;Rapport1Data&gt; data = ...; var q = new Calculator&lt;IEnumerable&lt;Rapport1Data&gt;&gt;() .Calcul(data, x =&gt; x.Sum(y =&gt; y.UnitesDisponibles), "N0"); </code></pre> <p>When we start encapsulating or data in <code>IEnurmarable&lt;&gt;</code> we get an error:</p> <blockquote> <p>Object does not match target type</p> </blockquote> <p>As we understand it, it's because the Sub-Expression <code>y =&gt; y.UnitesDisponibles</code> is being applied to the <code>IEnumerable</code> instead of the <code>Rapport1Data</code>.</p> <p>How can we fix it to ensure that it will be fully recursive if we some day have complex expression like:</p> <pre><code>IEnumerable&lt;IEnumerable&lt;Rapport1Data&gt;&gt; data = ...; var q = new Calculator&lt;IEnumerable&lt;IEnumerable&lt;Rapport1Data&gt;&gt;&gt;() .Calcul(data,x =&gt; x.Sum(y =&gt; y.Sum(z =&gt; z.UnitesDisponibles)), "N0"); </code></pre> <h2>Classes we've built</h2> <pre><code>public class Calculator&lt;T&gt; { public string Calcul( T data, Expression&lt;Func&lt;T, decimal?&gt;&gt; query, string format) { var rulesCheckerResult = RulesChecker&lt;T&gt;.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; } } </code></pre> <p>and the Custom ExpressionVisitor</p> <pre><code>class RulesChecker&lt;T&gt; : 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&lt;T&gt;(data); visitor.Visit(expression); return new RulesCheckerResult( visitor.hasConfidentielle, visitor.hasNonDiffusable, visitor.hasDivisionParZero, visitor.hasManquante); } protected override Expression VisitBinary(BinaryExpression node) { if (!this.hasDivisionParZero &amp;&amp; node.NodeType == ExpressionType.Divide &amp;&amp; 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&lt;int?&gt;(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); } } </code></pre> <p>[UPDATE] Adding more detail on what we want to do</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload