【发布时间】:2011-12-21 19:30:32
【问题描述】:
这是我们想要做的。
我们有来自数据库的数据,我们需要对其进行格式化以制作报告,包括一些计算(总和、平均值和字段到字段的计算(例如:x.a / x.b))。
其中一个限制是,例如,如果总和中的一个数据为空、-1 或-2,我们必须停止计算并显示“-”。由于我们要生成许多报表,每个报表具有相同的逻辑和许多计算,因此我们希望集中这个逻辑。目前,我们生成的代码允许我们检查字段到字段的计算(例如 x.a / x.b),但不能让我们检查组总数(例如:x.b / SUM(x.a))
测试用例
规则
- 如果计算中使用的值之一是 -1、-2 或 null,则计算不应进行。在这种情况下,如果找到 -1 或 null,则返回“-”,如果找到 -2,则返回“C”
- 如果计算中有多个“错误值”,则需要遵循如下定义的优先级:null -> -1 -> -2。此优先级与计算中值所在的级别无关
测试
简单计算
对象:新数据信息 { A = 10,B = 2,C = 4 } 计算:x => x.A / x.B + x.C 结果:9 对象:新数据信息 { A = 10, B = 2, C = -2 } 计算:x => x.A / x.B + x.C 结果:C(因为您在计算中有一个“-2”值) 对象:新数据信息 { A = 10, B = -2, C = null } 计算:x => x.A / x.B + x.C 结果:-(因为您在计算中有一个“空”值并且它在 -2 值上获胜)复杂的计算
对象:var list = new List(); list.Add(new DataInfo { A = 10, B = 2, C = 4 }); list.Add(new DataInfo { A = 6, B = 3, C = 2 }); 计算:list.Sum(x => x.A / x.B + list.Max(y => y.C)) 结果:15 对象:var list = new List(); list.Add(new DataInfo { A = 10, B = 2, C = 4 }); list.Add(new DataInfo { A = 6, B = 3, C = -2 }); 计算:list.Sum(x => x.A / x.B + list.Max(y => y.C)) 结果: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<>中的数据时,我们得到一个错误:
对象与目标类型不匹配
据我们了解,这是因为子表达式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;
}
}
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);
}
}
[更新] 添加更多关于我们想要做什么的细节
【问题讨论】:
-
你能解释一下你到底在计算什么吗?你从来没有真正说出你在做什么,并且在查看你的代码时并不明显(这是一个问题)。请将您的解释添加到问题正文中。
-
ExpressionVisitor旨在遍历Expression,而不是Expressions 的集合。您需要在访问者中编写逻辑来说明可能属于不同类型的数据。您现在编写它的方式假定data将是您可以从中获取某些属性值的东西。如果您想尽可能保持通用性,您可能应该为您传入的数据创建一个“对象访问者”。 -
再看一下您的代码,我想说您在这里尝试将
ExpressionVisitor用于错误的目的。 看起来您正在对某个对象进行一些验证。你不能那样把对象扔到ExpressionVisitor,这不是它的设计目的。我认为您需要重新评估您在这里所做的事情,并考虑使用不同的工具来执行此操作。 -
这是我们想要做的。我们有从数据库发送的数据,我们需要对其进行格式化以制作报告,包括一些计算(总和、平均值等)。限制之一是,例如,如果总和中的一个数据为空、-1 或 -2,我们必须停止计算并显示“-”。由于我们要生成许多报表,每个报表具有相同的逻辑和许多计算,因此我们希望集中这个逻辑。目前,我们生成的代码允许我们检查字段到字段的计算(例如 x.a / x.b),但不能让我们检查组总数(例如 SUM(x.a))。
-
我在描述中添加了一些测试用例。我认为每个人都会更清楚地了解我们的需求。顺便说一句,我正在和 Groumy 做同一个项目! :)
标签: c# linq c#-4.0 expressionvisitor