/// <summary> /// 构造树型结点,并查找计算变量引用的测点 /// </summary> /// <param name="appCalcName">计算名称</param> /// <param name="vars">计算公式中需要的计算变量</param> /// <param name="graph">拓扑树</param> /// <param name="idList">引用测点计算的计算名称列表</param> /// <param name="symbol">当前计算量的符号</param> private void intitalVars(string appCalcName, ArrayList vars, ALGraph.MyGraph graph, hammergo.caculator.MyList idList, string symbol) { for (int i = 0; i < vars.Count; i++) { string vs = (string)vars[i]; int pos = vs.IndexOf('.'); if (pos != -1)//带点的参数 { string otherID = vs.Substring(0, pos); if (otherID == appCalcName)//计算名称 { throw new Exception("公式中的变量不能包含本仪器的二级变量"); } else { idList.add(otherID.ToUpper(), 0); } } else { // //不带点的参数 //添加弧 graph.addArcNode(new ALGraph.ArcNode(), vs, symbol); } } }
/// <summary> /// 构建拓朴图 /// </summary> /// <param name="calculateName">计算名称</param> /// <param name="graph">拓朴图</param> /// <param name="date">数据的日期,有可能不同日期的公式所引用的测点不一样</param> private void constructGraph(string calculateName, ALGraph.MyGraph graph, DateTimeOffset date) { hammergo.caculator.MyList list = getChildApp(calculateName, date); for (int i = 0; i < list.Length; i++) { string calcSn = list.getKey(i); graph.addArcNode(new ALGraph.ArcNode(), calculateName, calcSn); constructGraph(calcSn, graph, date); } }
public void constructGraph(App app, ALGraph.MyGraph graph, List <Guid> loopCheckList) { foreach (App child in getChildApp(app)) { if (loopCheckList.Exists(s => s == app.Id)) { throw new Exception("测点公式中引用了其它测点的公式,但是存在循环引用的问题"); } else { loopCheckList.Add(app.Id); } graph.addArcNode(new ALGraph.ArcNode(), app.CalculateName, child.CalculateName); constructGraph(app, graph, loopCheckList); } }
public void Validate() { List <string> nameList = new List <string>(20); List <string> symbolList = new List <string>(20); //_modifiedApp _modifiedParams和实例中的其它Entity属于不同的dbcontext //需要验证公式更新的逻辑,这是整个app最复杂的问题之一,其它有公式解析和拓扑排序 //获取参数列表 var paramsLocal = (from i in dbcontext.AppParams where i.AppId == _modifiedApp.Id select i).AsNoTracking().ToList(); //获取该测点的所有计算公式 //当前数据库中的参数列表 var formulaeLocal = (from p in dbcontext.AppParams.OfType <CalculateParam>() where p.AppId == _modifiedApp.Id join f in dbcontext.Formulae on p.Id equals f.ParamId select f).ToList(); //需要更新formula的计算次序 //处理added,modified,deleted //用修改的值替换内存中的值 foreach (var entry in _paramsEntries) { var entity = entry.Entity as AppParam; if (entity.AppId == _modifiedApp.Id) { //只能是modified或deleted int index = paramsLocal.FindIndex(s => s.Id == entity.Id); if (index >= 0) { paramsLocal.RemoveAt(index); } if (entry.State == EntityState.Modified || entry.State == EntityState.Added) { paramsLocal.Add(entity); } else if (entry.State == EntityState.Deleted) { //在删除参数时,会级联删除公式 formulaeLocal.RemoveAll(s => s.ParamId == entity.Id); } } } foreach (ObjectStateEntry entry in _formulaEntries) { var entity = entry.Entity as Formula; //公式必须依附于参数 //paramList中的参数有可以是新增的参数,即数据库还没有记录 if (paramsLocal.Exists(s => s.Id == entity.ParamId)) { int index = formulaeLocal.FindIndex(s => s.ParamId == entity.ParamId && s.StartDate == entity.StartDate); if (index >= 0) { //entity已被修改,或增加,删除,得先从列表中先先移除 formulaeLocal.RemoveAt(index); } if (entry.State == EntityState.Modified || entry.State == EntityState.Added) { formulaeLocal.Add(entity); } } } //检查名称和符号是否有重复 checkParamNames(paramsLocal, nameList, symbolList); //测点公式的时间段必须具备连续性,所以需先对公式进行检查 var formulaGroup = (from i in formulaeLocal orderby i.StartDate ascending group i by i.StartDate).ToList(); int gCnt = formulaGroup.Count(); if (gCnt > 0) { int calcParamsCnt = paramsLocal.OfType <CalculateParam>().Count(); //每组公式的结束时间 DateTimeOffset?endDate = null;; for (int i = 0; i < gCnt; i++) { var item = formulaGroup[i]; var startDate = item.Key; if (endDate != null) { if (startDate != endDate) { throw new Exception(string.Format("起始时间为{0}的公式的开始时间与上一分段公式的结束时间没有衔接,\n即上一段公式的结束时间必须是下一段公式的开始时间", startDate)); } } //检查个数 if (item.Count() != calcParamsCnt) { throw new Exception(string.Format("起始时间为{0}的公式与计算参数的数目不一致", startDate)); } //公式和参数要一一对应,检查对应关系 var compositList = (from ci in paramsLocal.OfType <CalculateParam>() join f in item.AsEnumerable() on ci.Id equals f.ParamId select new { Param = ci, Formula = f }).ToList(); if (compositList.Count != calcParamsCnt) { throw new Exception(string.Format("起始时间为{0}的公式没有与计算参数一一对应", startDate)); } endDate = item.ElementAt(0).EndDate; if (startDate >= endDate) { throw new Exception("分段公式的开始时间必须小于结束时间"); } if (item.Count(s => s.EndDate == endDate) != calcParamsCnt) { throw new Exception(string.Format("起始时间为{0}的公式结束时间不一致", startDate)); } //检查每组公式的计算逻辑 hammergo.caculator.MyList list = new hammergo.caculator.MyList(10); int num = 1;//填充一些数据,测试计算的表达式 foreach (string s in symbolList) { list.add(s, num++); } // 简单的判断公式的依赖关系,只能精确到仪器,不能精确的量(如n01cf14.e的依赖关系,过于复杂) //生成新的图 ALGraph.MyGraph graph = new ALGraph.MyGraph(); hammergo.caculator.CalcFunction calc = new hammergo.caculator.CalcFunction(); //引用测点的编号列表 hammergo.caculator.MyList calcNameList = new hammergo.caculator.MyList(5); foreach (var cp in compositList) { string formulaString = cp.Formula.FormulaExpression; string symbol = cp.Param.ParamSymbol; if (formulaString == null || formulaString.Trim().Length == 0) { throw new Exception(string.Format("计算参数 {0} 的计算公式不能为空", cp.Param.ParamName)); } ArrayList vars = calc.getVaribles(formulaString); intitalVars(_modifiedApp.CalculateName, vars, graph, calcNameList, symbol); } ArrayList toplist = graph.topSort(); if (toplist.Count != graph.Vexnum) { throw new Exception("公式存在循环依赖"); } ArrayList storeTopList = toplist; // 刚才是检查仪器内的循环依赖,现在和所有的仪器一起检查 graph = new ALGraph.MyGraph(); for (int j = 0; j < calcNameList.Length; j++) { graph.addArcNode(new ALGraph.ArcNode(), calcNameList.getKey(j), _modifiedApp.CalculateName); //全部使用计算名称 var loopCheckList = new List <Guid>(5); constructGraph(_modifiedApp, graph, loopCheckList); //递归加入其子结点 } toplist = graph.topSort(); if (toplist.Count != graph.Vexnum) { System.Text.StringBuilder sb = new System.Text.StringBuilder(128); foreach (ALGraph.VNode node in graph.vertices) { sb.Append(node.data).Append(" "); } throw new Exception(sb.ToString() + " 仪器公式存在循环依赖"); } toplist = storeTopList; //图不存在回路 //引用的其它测点,填充 for (int j = 0; j < calcNameList.Length; j++) { string calcName = calcNameList.getKey(j); var refApp = dbcontext.Apps.AsNoTracking().FirstOrDefault(s => s.CalculateName == calcName); if (refApp == null) { throw new Exception(string.Format("计算名称为{0}的仪器不存在!", calcName)); } //ArrayList paList = new ArrayList(12); //paList.AddRange(consBLL.GetListByappName(refApp.AppName)); //paList.AddRange(mesBLL.GetListByappName(refApp.AppName)); //paList.AddRange(calcBLL.GetListByappName(refApp.AppName)); var paList = refApp.AppParams.ToList(); for (int k = 0; k < paList.Count; k++) { var pi = paList[k]; list.add(calcName + "." + pi.ParamSymbol, k + 1); } } foreach (var cp in compositList) { string formula = cp.Formula.FormulaExpression; calc.compute(formula, list); string symbol = cp.Param.ParamSymbol; int index = toplist.IndexOf(symbol); if (index < 0) { //此符号不在拓扑图中,没有公式依赖于它 index = toplist.Count; } //cp.CalculateOrder = (byte)index; //更新计算次序 cp.Formula.CalculateOrder = (byte)index; } } } }