using DocumentFormat.OpenXml.Drawing.Spreadsheet; using DocumentFormat.OpenXml.Spreadsheet; using Learun.Application.Organization; using Learun.Application.TwoDevelopment.ZZDT_EC; using Learun.Application.TwoDevelopment.ZZDT_EC.Frame; using Learun.Cache.Base; using Learun.Cache.Factory; using Learun.Loger; using Learun.Util; using Learun.Util.SqlSugar; using log4net.Config; using Microsoft.Practices.ObjectBuilder2; using Newtonsoft.Json; using Org.BouncyCastle.Bcpg.OpenPgp; using Pipelines.Sockets.Unofficial.Arenas; using SqlSugar; using System; using System.Collections.Generic; using System.Data.Entity.Infrastructure; using System.Diagnostics; using System.Linq; using System.Web.Http; using static Learun.Application.TwoDevelopment.ZZDT_EC.IO_WorkFlowService; namespace Learun.Application.Web.AppApi { /// /// IO模块(By YuXH) /// [HandlerApiLogin(FilterMode.Ignore)] public class IOModuleApiController : WebApiControllerBase { #region 模块对象 private ec_CableBLL ec_CableBLL = new ec_CableBLL(); private ec_PanelBLL ec_PanelBLL = new ec_PanelBLL(); private ec_PanelStripBLL ec_PanelStripBLL = new ec_PanelStripBLL(); private ec_PanelStripTermBLL ec_PanelStripTermBLL = new ec_PanelStripTermBLL(); private ec_PanelChannelBLL ec_PanelChannelBLL = new ec_PanelChannelBLL(); #endregion #region 信号相关 #endregion /// /// 根据模板自动创建端子排 /// /// /// /// private ec_PanelStripEntity CreatePanelStripByProfile2(string projId, string TSname, string panelId, GlobalEnum.IOType iOType) { var setTb = ProjectSugar.TableName(projId); var allSettings = SqlSugarHelper.Db.Queryable().AS(setTb).ToList(); //IO_CardProfile return new ec_PanelStripEntity(); } /// /// 找到某一个预分配箱子 附近的某一个箱子。且io类型能匹配上 /// /// /// /// 所有柜子的属性 /// Digital,4-20mA,10v,pt100,pulse /// private ec_PanelEntity FindPanelNearby(ec_CableEntity cableObj, List frameLists, List allPanel, Dictionary> allPanelProps, string IOTypeOnCable) { var curPanelId = cableObj.PanelID; double GetPanelXYDistance2Target(string panelId, double targetX, double targetY) { var nearPanel = allPanel.FirstOrDefault(x => x.PanelID == panelId); var nearPanelProps = allPanelProps[nearPanel.EngineerDataID]; var X = nearPanelProps.FirstOrDefault(x => x.PropertyName == GlobalObject.propName_Frame)?.PropertyValue; X = X.Split(new string[] { GlobalObject.enum_separator }, StringSplitOptions.None)[0];//插件端对于下拉列表 都是 name || nameEN var validFrme2 = frameLists.FirstOrDefault(f => f.Num == X); if (validFrme2 == null) { return -1;//无效的肋位号导致的 } var XValue = validFrme2.Value; if (XValue < 400)//null也没事 { // 小于400,我几乎可以认为此时肋位号用的是m这个单位。因为如果用的是mm,400mm的肋位号似乎也太小了。 XValue = 1000 * XValue; // 转成mm } if (double.TryParse(nearPanelProps.FirstOrDefault(x => x.PropertyName == GlobalObject.propName_FrameOff)?.PropertyValue, out double XOffValue)) { } else { return -1;//无效的x off导致的 } if (double.TryParse(nearPanelProps.FirstOrDefault(x => x.PropertyName == GlobalObject.propName_YOff)?.PropertyValue, out double YOffValue)) { } else { return -1;//无效的y off导致的 } var distance = Math.Sqrt(Math.Pow(targetX - XValue - XOffValue, 2) + Math.Pow(targetY - YOffValue, 2)); return distance; } var curPanel = allPanel.FirstOrDefault(x => x.PanelID == curPanelId); var curPanelProps = allPanelProps[curPanel.EngineerDataID]; //当前预分配的箱子的位置 var AssignPanelX = curPanelProps.FirstOrDefault(x => x.PropertyName == GlobalObject.propName_Frame)?.PropertyValue; AssignPanelX = AssignPanelX.Split(new string[] { GlobalObject.enum_separator }, StringSplitOptions.None)[0];//插件端对于下拉列表 都是 name || nameEN var validFrme = frameLists.FirstOrDefault(X => X.Num == AssignPanelX); if (validFrme == null) { return null;//无效的肋位号导致的 } var AssignPanelXValue = validFrme.Value; if (AssignPanelXValue < 400)//null也没事 { // 小于400,我几乎可以认为此时肋位号用的是m这个单位。因为如果用的是mm,400mm的肋位号似乎也太小了。 AssignPanelXValue = 1000 * AssignPanelXValue; // 转成mm } if (double.TryParse(curPanelProps.FirstOrDefault(x => x.PropertyName == GlobalObject.propName_FrameOff)?.PropertyValue, out double AssignPanelXOffValue)) { } else { return null;//无效的x off导致的 } if (double.TryParse(curPanelProps.FirstOrDefault(x => x.PropertyName == GlobalObject.propName_YOff)?.PropertyValue, out double AssignPanelYValue)) { } else { return null;//无效的y off导致的 } double minDistance = 0.1; ec_PanelEntity nearestPanel = null; foreach (var panel in allPanel.Where(x => x.PanelID != curPanelId)) { #region io var IOMatched = true; var IOsOnPanel = panel.allowedIOTypes.Split(',').ToList(); //剩下的感觉都可以是set上的参数? foreach (var set in cableObj.Sets) { #region 判断下io匹配程度 var setNew = ec_CableBLL.SetIOMatchPanel(cableObj.PreAssignIOType, set, IOsOnPanel); if (IOsOnPanel.Contains(setNew.IOType.ToString())) { setNew.IOTypeMatch = true; } else { setNew.IOTypeMatch = false; IOMatched = false; //不匹配 break; } #endregion } #endregion if (IOMatched) { //如果io匹配了,再找附近的 #region distance //拿到每一个的xy属性 //然后和预分配箱子进行对比 var DISTANCE = GetPanelXYDistance2Target(panel.PanelID, AssignPanelXValue + AssignPanelXOffValue, AssignPanelYValue); if (0.1 == minDistance && DISTANCE > 0) { minDistance = DISTANCE; nearestPanel = panel; } else if (DISTANCE < minDistance && DISTANCE > 0) { minDistance = DISTANCE; nearestPanel = panel; } #endregion } } return nearestPanel; } /// /// IO分配主界面查询,查询所有的位置 - 采集箱 - 模块 - 通道 /// /// /// public IHttpActionResult GetPanelTree(string projectId) { //2023 05 22,从用户所在的role的lr base authorize里去查 //UserInfo userInfo = LoginUserInfo.Get(); //UserRelationIBLL userRelationIBLL = new UserRelationBLL(); //var roles = userRelationIBLL.GetObjectIds(userInfo.userId, 1, projectId); var stopWatch = new Stopwatch(); var Locations = GetLocList(projectId); if (Locations == null) { return Fail("系统设置中找不到“系统柜位置”这一项内容。联系管理员"); } var ec_PanelStripTermService = new ec_PanelStripTermBLL(); var ec_WireTerminalService = new ec_WireTerminalService(); var ec_Wire_GroupService = new ec_Wire_GroupBLL(); stopWatch.Start(); var Panels = ec_PanelBLL.GetList("{ProjectId:\"" + projectId + "\"}").OrderBy(x => x.TagNumber).ToList(); stopWatch.Stop(); this.Logger.Info($"Panel查询 {stopWatch.ElapsedMilliseconds}" + "\r\n"); stopWatch.Start(); var Strips = ec_PanelStripBLL.GetList("{ProjectId:\"" + projectId + "\"}").OrderBy(x => x.Panel_Strip_Seq).OrderBy(x => x.StripName); stopWatch.Stop(); this.Logger.Info($"Strip查询 {stopWatch.ElapsedMilliseconds}" + "\r\n"); stopWatch.Start(); var Channels = ec_PanelChannelBLL.GetList("{ProjectId:\"" + projectId + "\"}"); stopWatch.Stop(); this.Logger.Info($"Channel查询 {stopWatch.ElapsedMilliseconds}" + "\r\n"); stopWatch.Start(); var AllTerms = ec_PanelStripTermService.GetList("{ProjectId:\"" + projectId + "\"}"); stopWatch.Stop(); this.Logger.Info($"Term查询 {stopWatch.ElapsedMilliseconds}" + "\r\n"); stopWatch.Start(); var AllWTs = ec_WireTerminalService.GetList("{ProjectId:\"" + projectId + "\"}", true); stopWatch.Stop(); this.Logger.Info($"WireTerminal查询 {stopWatch.ElapsedMilliseconds}" + "\r\n"); stopWatch.Start(); var AllSignals = ec_Wire_GroupService.GetList("{ProjectId:\"" + projectId + "\"}", true); stopWatch.Stop(); this.Logger.Info($"Signals查询 {stopWatch.ElapsedMilliseconds}" + "\r\n"); stopWatch.Start(); List treeList = new List(); foreach (var Loc in Locations) { TreeModel nodeCatalogue = new TreeModel(); nodeCatalogue.id = Loc.DataItemDetailID; nodeCatalogue.text = Loc.DataItemName; nodeCatalogue.value = Loc.DataItemName; nodeCatalogue.nodeType = "0"; nodeCatalogue.showcheck = false; nodeCatalogue.checkstate = 0; nodeCatalogue.isexpand = false; nodeCatalogue.parentId = Loc.UpDataItemDetailID; nodeCatalogue.NodeExtData = Loc; treeList.Add(nodeCatalogue); var listPanel = Panels.Where(x => x.Panel_Loc_ID == Loc.DataItemDetailID && x.systempanel == GlobalEnum.specialType.系统柜.ToString()).ToList(); if (listPanel != null && listPanel.Count > 0) { foreach (var Panel in listPanel) { TreeModel nodeFile = new TreeModel(); nodeFile.id = Panel.PanelID; nodeFile.text = Panel.TagNumber; nodeFile.value = Panel.EngineerDataID; nodeFile.nodeType = "1"; nodeFile.showcheck = false; nodeFile.checkstate = 0; nodeFile.isexpand = false; nodeFile.parentId = Panel.Panel_Loc_ID; //后面会根据变为tree nodeFile.NodeExtData = Panel; //判断下是不是有效的类型 treeList.Add(nodeFile); var StripsInPanel = Strips.Where(x => x.PanelID == Panel.PanelID).ToList(); if (StripsInPanel != null && StripsInPanel.Count > 0) { //StripsInPanel = StripsInPanel.OrderBy(x => x.Panel_Strip_Seq).ToList(); foreach (var Strip in StripsInPanel) { #region 带上汇总信息 var ChannelsInStrip = Channels.Where(x => x.StripID == Strip.StripID).ToList();//("{ProjectId:\"" + projectId + "\",StripID:\"" + Strip.StripID + "\",PanelID:\"" + Strip.PanelID + "\"}"); //Strip.Channels = ChannelsInStrip.ToList(); foreach (var Channel in ChannelsInStrip) { var Terms = AllTerms.Where(x => x.StripID == Strip.StripID && x.ChannelID == Channel.ChannelID).ToList();//ec_PanelStripTermService.GetList("{ProjectId:\"" + projectId + "\",StripID:\"" + Strip.StripID + "\",ChannelID:\"" + Channel.ChannelID + "\"}"); //wire termianl var conns = AllWTs.Where(x => Terms.Select(y => y.TermID).ToList().Contains(x.TermID)).ToList(); //.GetList(("{ProjectId:\"" + projectId + "\",PanelID:\"" + Strip.PanelID + "\",StripID:\"" + Strip.StripID + "\"}")); bool bUsed = false; if (conns != null && conns.Count() > 0) { bUsed = true; } //foreach (ec_PanelStripTermEntity term in Terms) //{ // var conn = conns.FirstOrDefault(x => x.TermID == term.TermID); // if (conn != null) // { // bUsed = true; // break; // } //} if (bUsed) { //wire group var signal = AllSignals.FirstOrDefault(x => x.ChannelID == Channel.ChannelID);//.GetList("{ProjectId:\"" + projectId + "\",StripID:\"" + Strip.StripID + "\",ChannelID:\"" + Channel.ChannelID + "\"}").FirstOrDefault(); if (signal != null) { Strip.ChannelsUsed += 1; } else { Strip.ChannelsUsedNoSignal += 1; } } else { Strip.ChannelsSpared += 1; } } #endregion TreeModel nodeStrip = new TreeModel(); nodeStrip.id = Strip.StripID; nodeStrip.text = Strip.StripName; nodeStrip.value = Strip.Panel_Strip_Seq; nodeStrip.nodeType = "1"; nodeStrip.showcheck = false; nodeStrip.checkstate = 0; nodeStrip.isexpand = false; nodeStrip.parentId = Strip.PanelID; nodeStrip.NodeExtData = Strip; treeList.Add(nodeStrip); } } } } } stopWatch.Stop(); this.Logger.Info($"循环位置耗时 {stopWatch.ElapsedMilliseconds}" + "\r\n"); return Success(treeList.ToTree()); } /// /// 根据ID拿。只查询ec panel表本身 /// /// /// /// [HttpGet] public IHttpActionResult GetPanel(string PanelID, string projId) { var res = ec_PanelBLL.GetEntity(projId, PanelID); return Success(res); } /// /// 根据EngineerDataID拿。包括set wire,以及from to信息 /// /// /// /// /// /// [HttpGet] public IHttpActionResult GetCableByEngID(string EngineerDataID, string projId, bool OnlySelf = false, bool PreAssign = false) { if (PreAssign) { var res = ec_CableBLL.GetCablePreAssign(projId, EngineerDataID); return Success(res); } else { var res = ec_CableBLL.GetCableByEngID(projId, EngineerDataID, OnlySelf); return Success(res); } } ICache redisObj = CacheFactory.CaChe(); /// /// 自动分配通道(点表信号自动分配)。 /// /// /// /// /// 需要分配的电缆cableid,不是enginedataId,逗号分开 /// [HttpPost] public IHttpActionResult AutoAssignCable2Channel_step1(string projId, bool AcceptNearbyPanel, [FromBody] List CableIds) { #region 数据准备 var frameBll = new FrameBll(); var allFrames = frameBll.GetFrameList(projId); var cbll = new ec_CableBLL(); var panelTable = ProjectSugar.TableName(projId); var stripTable = ProjectSugar.TableName(projId); var tagTable = ProjectSugar.TableName(projId); var propTable = ProjectSugar.TableName(projId); var typeTable = ProjectSugar.TableName(projId); #endregion #region 先要知道有哪些待分配的信号 端子排 if (CableIds == null || CableIds.Count == 0) { return Success("OK"); } var cablesNeedAssigned = cbll.GetCablesPreAssign(projId, CableIds, true); cablesNeedAssigned = cablesNeedAssigned.Where(x => CableIds.Contains(x.CableID)).ToList(); #endregion //涉及到哪些箱子 List allPanel = SqlSugarHelper.Db.Queryable().AS(panelTable). InnerJoin((a, b) => a.EngineerDataID == b.EngineDataID).AS(tagTable). InnerJoin((a, b, c) => b.ObjectTypeID == c.ObjectTypeID).AS(typeTable). Where((a, b, c) => c.specialType == GlobalEnum.specialType.系统柜). Select((a, b, c) => new ec_PanelEntity { allowedIOTypes = a.allowedIOTypes, DefaultBreakerType = a.DefaultBreakerType, EngineerDataID = a.EngineerDataID, MaxStripNumber = a.MaxStripNumber, ObjectTypeName = c.ObjectTypeName, PanelID = a.PanelID, Panel_Loc_ID = a.Panel_Loc_ID, TagNumber = b.TagNumber, }). ToList();//这里要过滤一下,根据object type里的specialType,而不是所有的panel var allPanelProp = SqlSugarHelper.Db.Queryable().AS(propTable) .Where(x => allPanel.Select(xx => xx.EngineerDataID).Contains(x.EngineDataID)) .ToList().GroupBy(x => x.EngineDataID).ToDictionary(x => x.Key, x => x.ToList()); var panelsNeed = allPanel.Where(x => cablesNeedAssigned.Select(xx => xx.PanelID).Contains(x.PanelID)).ToList();//用户指定的预分配的箱子 var stripsNeed = SqlSugarHelper.Db.Queryable().AS(stripTable). Where(x => panelsNeed.Select(xx => xx.PanelID).Contains(x.PanelID)). ToList(); #region 1.1判断(信号预分配选择的采集箱可能并没有该电缆需要的信号类型) var cablesNotMatchIO = cablesNeedAssigned.Where(x => x.IOTypesNotMatchedList.Count > 0).ToList(); // GetCablesPreAssign 查询时已经处理过io是否匹配了 if (cablesNotMatchIO != null && cablesNotMatchIO.Count > 0) { if (AcceptNearbyPanel)//允许进行 { //有不匹配的,但是用户允许继续 //如果选是,则在之后的自动分配过程中会自动寻找匹配的采集箱,原则上从就近的开始找,如果没有匹配的采集箱,则提示“未找到具有XX类型的采集箱,请新增后再次分配,是否取消自动分配进程?” foreach (var cable in cablesNotMatchIO) { var nearPanel = FindPanelNearby(cable, allFrames, allPanel, allPanelProp, cable.PreAssignIOType); cable.AssignedPanel = nearPanel; } var cableNotFoundNearPanel = cablesNotMatchIO.FindAll(x => x.AssignedPanel == null); if (cableNotFoundNearPanel != null && cableNotFoundNearPanel.Count > 0) { redisObj.Remove("IOModule_AutoAssign2Ch_" + projId, CacheId.IOModule_AutoAssign2Ch); redisObj.Write>("IOModule_AutoAssign2Ch_" + projId, cablesNeedAssigned, CacheId.IOModule_AutoAssign2Ch); return Fail($"在电缆{string.Join(",", cableNotFoundNearPanel.Select(x => x.TagNumber))}附近未找到具有IO类型匹配的采集箱,请新增后再次分配,是否取消自动分配进程?"); //之后插件端进行选择。 //如果不想新增,则选择否,继续自动分配,没有分配到的信号在备注上填写由于何种原因没有被分配,弹出未被分配的信号列表供查看。 //也就是这里要等所有循环结束后才能return结果,而不是目前一个nearByFound = false时就退出了。 } } else { //如果选否,则不继续自动分配,中断进程,等添加完正确的模块后再继续自动分配 var cableNamesNotMatched = string.Join(",", cablesNotMatchIO.Select(x => x.TagNumber).ToList()); var panelIOMissing = new List(); foreach (ec_CableEntity cableNotMatchIO in cablesNotMatchIO) { panelIOMissing.Add(cableNotMatchIO.PanelID + "缺失IO: " + string.Join(",", cableNotMatchIO.IOTypesNotMatchedList)); } return Fail($"预分配结果中,以下电缆【{cableNamesNotMatched}】的IO类型和其预分配采集箱的IO类型无法匹配。等添加完正确的模块后再继续自动分配。\r\n" + $"需要添加的正确模块有:{string.Join(",", panelIOMissing)}"); } } else { //没有不匹配的,可以继续进行 } //必要的数据存入redis,以便后续步骤使用 redisObj.Remove("IOModule_AutoAssign2Ch_" + projId, CacheId.IOModule_AutoAssign2Ch); redisObj.Write>("IOModule_AutoAssign2Ch_" + projId, cablesNeedAssigned, CacheId.IOModule_AutoAssign2Ch); return Success("OK"); #endregion } /// /// 根据step1的初步采集箱判断情况,来进行分配预览。 /// /// /// [HttpPost] public IHttpActionResult AutoAssignCable2Channel_step2(string projId) { ICache redisObj = CacheFactory.CaChe(); var cablesNeedAssigned = redisObj.Read>("IOModule_AutoAssign2Ch_" + projId, CacheId.IOModule_AutoAssign2Ch); var setTb = ProjectSugar.TableName(projId); var allSettings = SqlSugarHelper.Db.Queryable().AS(setTb).ToList(); //IO_CardProfile var signalTb = ProjectSugar.TableName(projId); var allSignals = SqlSugarHelper.Db.Queryable().AS(signalTb).ToList(); var connTb = ProjectSugar.TableName(projId); var termTb = ProjectSugar.TableName(projId); var allConnedTerms = SqlSugarHelper.Db.Queryable().AS(connTb) .InnerJoin((a, b) => a.TermID == b.TermID).AS(termTb) .Select((a, b) => b.ChannelID).Distinct().ToList(); var allUsedCH = allSignals.Where(x => !string.IsNullOrEmpty(x.ChannelID)).Select(x => x.ChannelID).Distinct().ToList(); allUsedCH.AddRange(allConnedTerms); //??这里有问题。通道是否被占用,需要看signal和set双重(在io分配界面) //1. 信号有, 电缆set有,占了 //2.信号no,电缆set有,占了 //3.信号有,电缆setno,没占 //4.信号no,电缆setno,没占 //1.2 流程图 分组原则为同一信号类型、同一系统的为一组 var cablesGrouped = cablesNeedAssigned.OrderBy(x => x.PanelID).ThenBy(x => x.PreAssignIOType).ThenBy(x => x.System).ToList(); cablesGrouped = cablesGrouped.Where(x => x.Sets != null && x.Sets.Count() > 0 && x.AssignedPanel != null).ToList();//过滤掉没有set的,或者没有找到箱子的 cablesGrouped = cablesGrouped.Where(x => x.Sets.Where(xx => !string.IsNullOrEmpty(xx.PreAssignGroup_Desc)).Count() > 0).ToList();//过滤掉set没有分配信号的 var allPanelIds = cablesGrouped.Select(x => x.PanelID).Distinct(); var stripBll = new ec_PanelStripBLL(); var allStrips = stripBll.GetList("{ProjectId:\"" + projId + "\"}", OnlySelf: false) .Where(x => allPanelIds.Contains(x.PanelID)) .GroupBy(x => x.PanelID) .ToDictionary(x => x.Key, x => x.ToList()); //感觉逻辑上用panel来循环会更合理 foreach (var curPanelId in allPanelIds) { int newTSSeq = 10001; var curStrips = allStrips[curPanelId]; var cablesOnThisPanel = cablesGrouped.Where(x => x.PanelID == curPanelId).ToList(); if (cablesOnThisPanel == null || cablesOnThisPanel.Count == 0) { continue;//next panel } //var lastCableSystemAndIOType = cablesOnThisPanel.First().System + cablesOnThisPanel.First().PreAssignIOType;//用于判断是否是同一个组 var lastUsedStrip = (ec_PanelStripEntity)null; foreach (var cable in cablesOnThisPanel) { //1.2.2 分组原则为同一信号类型、同一系统的为一组,系统属性从电缆的from端上的设备中取设备的系统,同组的优先放到同一个箱子的同一个模块里面, //如果一组放完后发现模块还有多余的通道可以放则下一个系统继续从这个模块开始分配。 //???总感觉这句话,总结后:可以无脑用上一个模块,直到模块满了再用下一个模块。 #region 判断是否是同一组 //bool sameGroup = true; //if (lastCableSystemAndIOType != cable.System + cable.PreAssignIOType) //{ // sameGroup = false;//换组了 //} #endregion //1.2 流程图 读取有提前选好箱子的信号 var setsSpared = cable.Sets.Where(x => string.IsNullOrEmpty(x.PreAssignGroup_Desc) || string.IsNullOrEmpty(x.PreAssignInOrOut)).ToList(); var setsIn = cable.Sets.Where(x => !string.IsNullOrEmpty(x.PreAssignGroup_Desc) && x.PreAssignInOrOut == SWS.Share.Enum.inOrOut.输入.ToString()).ToList(); var setsOut = cable.Sets.Where(x => !string.IsNullOrEmpty(x.PreAssignGroup_Desc) && x.PreAssignInOrOut == SWS.Share.Enum.inOrOut.输出.ToString()).ToList(); #region 1.2.4 同一根电缆,如果带公共端的报警信号,必须接到一个模块里面 if (cable.PreAssignIOType.ToLower() == GlobalEnum.signalType.Digital.ToString().ToLower()) { if (setsIn.Count > 0 && setsOut.Count > 0) { //from cjj 25 09 23 wechat:一根电缆可能会出现既有输入也有输出,如果碰到就放到两个端子排里面,公共端不会出现一个输出,一个输入的 continue; } //todo } #endregion #region inputNew if (setsIn.Count > 0) { var resIn = AutoAssignCore(GlobalEnum.inOrOut.输入, setsIn, setsSpared); } #endregion #region input //ec_PanelStripEntity curStrip = null; //var matchedStrips = curStrips.Where(x => x.IO_TYPE == ioTypeOnCable.ToString()).ToList(); //if (matchedStrips != null && lastUsedStrip != null && lastUsedStrip.IO_TYPE == ioTypeOnCable.ToString()) //{ // //1.2.2 优先使用上一个模块,直到模块满了再用下一个模块。 // matchedStrips.Insert(0, lastUsedStrip); //} //if (setsIn.Count == 0 ) //{ // //相当于没有 //} //else if (setsIn.Count + setsSpared.Count() > 10) //{ // //另外如果电缆对数大于10对,即像12*2*0.75这种,无论它预分配了多少根电缆对,永远预留4个以下的空白通道。 // //意思就是一个模块里面够4个或4个以上空白通道就留4个空白通道, // //如果不够4个就留4个以下即可。 //} //else //{ // string PanelName = cable.AssignedPanel.TagNumber;//当前电缆预分配的箱子名称 // switch (cable.PreAssignIOType.ToLower()) // { // case "digital": // //数字量 // ioTypeOnCable = GlobalEnum.IOType.DI; // break; // case "4~20ma": // //模拟量4-20mA // ioTypeOnCable = GlobalEnum.IOType.AI; // break; // case "10v": // ioTypeOnCable = GlobalEnum.IOType.TenVolt; // break; // case "pt100": // ioTypeOnCable = GlobalEnum.IOType.PT100; // break; // case "pulse": // ioTypeOnCable = GlobalEnum.IOType.PULSE; // break; // default: // //通讯类 485 422啥的 // continue; // break; // } // //1.2 流程图 箱子里是否已经存在端子排(io匹配) // bool alreadyInsertNewTs = false; // if (matchedStrips == null || matchedStrips.Count == 0) // { // //没有端子排 // //1.2 流程图 根据信号数里自动新建端子排,端子排通道数里根据箱子模板中的默认值获得 // var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnCable.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnCable); // alreadyInsertNewTs = true; // matchedStrips.Add(newTS); // } // #region 1.2 流程图 空的通道数够不够 // if (cable.CableClass == SWS.Share.Enum.cableClass.homerun.ToString()) // { // //1.2.3(2)如果模块是通讯信号(像RS485这种就是通讯信号),则不用管预留空白通道这个事情,看到有空的通道就放。 // //当前这个端子排就是可用的 // curStrip = matchedStrips.First(); // } // else // { // //1.2.3 (1) 非通讯,如果空的通道大于整个模块的5%,则可以利用,利用到小于5%了则停止利用,如果本身空通道数占的比例就小于5%,则不利用。 // curStrip = FindNextAvailableTS(matchedStrips, allUsedCH); // if (curStrip == null) // { // var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnCable.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnCable); // alreadyInsertNewTs = true; // matchedStrips.Add(newTS); // curStrip = newTS; // } // } // var usedChs = curStrip.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); // var notUsedChs = curStrip.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); // #endregion // #region 1.2 流程图 按规则先放几个信号进去 // if (notUsedChs.Count < setsIn.Count + setsSpared.Count()) // { // //不够放,又要新建? // if (alreadyInsertNewTs) // { // //压根就有问题,说明电缆的set太多了,就算是新建的模块,一个channel都没有使用的情况下,都塞不下。 // continue; // } // else // { // //相当于前面5%的判断过了,但是呢放不下所有需要的set // var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnCable.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnCable); // alreadyInsertNewTs = true; // matchedStrips.Add(newTS); // curStrip = newTS; // usedChs = curStrip.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); // notUsedChs = curStrip.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); // if (notUsedChs.Count < setsIn.Count + setsSpared.Count()) // { // //压根就有问题,说明电缆的set太多了,就算是新建的模块,一个channel都没有使用的情况下,都塞不下。 // continue; // } // } // } // lastUsedStrip = curStrip; // //能放下 // for (int i = 0; i < setsIn.Count; i++) // { // var set = setsIn[i]; // var ch = notUsedChs[i]; // set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):{curStrip.StripName}/通道:{ch.ChannelName}"; // //更新全局已使用通道 // allUsedCH.Add(ch.ChannelID); // } // var chOffIdx = setsIn.Count; // for (int i = 0; i < setsSpared.Count(); i++) // { // var set = setsSpared[i]; // var ch = notUsedChs[chOffIdx + i]; // set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):{curStrip.StripName}/通道:{ch.ChannelName}/冗余"; // //更新全局已使用通道 // allUsedCH.Add(ch.ChannelID); // } // #endregion //} #endregion #region output if (setsOut.Count > 0) { var resOut = AutoAssignCore(GlobalEnum.inOrOut.输出, setsOut, setsSpared); } #endregion //in 和 out都会执行这个 bool AutoAssignCore(GlobalEnum.inOrOut inOrOut, List sets, List sets_Spared) { GlobalEnum.IOType ioTypeOnC = default; switch (cable.PreAssignIOType.ToLower()) { case "digital": //数字量 if (inOrOut == GlobalEnum.inOrOut.输入) { ioTypeOnC = GlobalEnum.IOType.DI; } else { ioTypeOnC = GlobalEnum.IOType.DO; } break; case "4~20ma": //模拟量4-20mA if (inOrOut == GlobalEnum.inOrOut.输入) { ioTypeOnC = GlobalEnum.IOType.AI; } else { ioTypeOnC = GlobalEnum.IOType.AO; } break; case "10v": ioTypeOnC = GlobalEnum.IOType.TenVolt; break; case "pt100": ioTypeOnC = GlobalEnum.IOType.PT100; break; case "pulse": ioTypeOnC = GlobalEnum.IOType.PULSE; break; default: //通讯类 485 422啥的 return false; } ec_PanelStripEntity curStrip = null; var matchedStrips = curStrips.Where(x => x.IO_TYPE == ioTypeOnC.ToString()).ToList(); if (matchedStrips != null && lastUsedStrip != null && lastUsedStrip.IO_TYPE == ioTypeOnC.ToString()) { //1.2.2 优先使用上一个模块,直到模块满了再用下一个模块。 matchedStrips.Insert(0, lastUsedStrip); } string PanelName = cable.AssignedPanel.TagNumber;//当前电缆预分配的箱子名称 if (sets.Count == 0) { //相当于没有 } else { var totalSets = sets.Count + setsSpared.Count(); //1.2 流程图 箱子里是否已经存在端子排(io匹配) bool alreadyInsertNewTs = false; if (matchedStrips == null || matchedStrips.Count == 0) { //没有端子排 //1.2 流程图 根据信号数里自动新建端子排,端子排通道数里根据箱子模板中的默认值获得 var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnC.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnC); alreadyInsertNewTs = true; matchedStrips.Add(newTS); } #region 1.2 流程图 空的通道数够不够 if (cable.CableClass == SWS.Share.Enum.cableClass.homerun.ToString()) { //1.2.3(2)如果模块是通讯信号(像RS485这种就是通讯信号),则不用管预留空白通道这个事情,看到有空的通道就放。 //当前这个端子排就是可用的 curStrip = matchedStrips.First(); } else { //1.2.3 (1) 非通讯,如果空的通道大于整个模块的5%,则可以利用,利用到小于5%了则停止利用,如果本身空通道数占的比例就小于5%,则不利用。 curStrip = FindNextAvailableTS(cable.Sets.Count,totalSets, matchedStrips, allUsedCH); if (curStrip == null) { var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnC.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnC); alreadyInsertNewTs = true; matchedStrips.Add(newTS); curStrip = newTS; } } var usedChs = curStrip.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); var notUsedChs = curStrip.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); #endregion #region 1.2 流程图 按规则先放几个信号进去 if (notUsedChs.Count < totalSets) { //不够放,又要新建? if (alreadyInsertNewTs) { //压根就有问题,说明电缆的set太多了,就算是新建的模块,一个channel都没有使用的情况下,都塞不下。 return false; } else { //相当于前面5%的判断过了,但是呢放不下所有需要的set var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnC.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnC); alreadyInsertNewTs = true; matchedStrips.Add(newTS); curStrip = newTS; usedChs = curStrip.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); notUsedChs = curStrip.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); if (notUsedChs.Count < sets.Count + setsSpared.Count()) { //压根就有问题,说明电缆的set太多了,就算是新建的模块,一个channel都没有使用的情况下,都塞不下。 return false; } } } lastUsedStrip = curStrip; for (int i = 0; i < sets.Count; i++) { var set = sets[i]; var ch = notUsedChs[i]; set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):{curStrip.StripName}/通道:{ch.ChannelName}"; //更新全局已使用通道 allUsedCH.Add(ch.ChannelID); } var chOffIdx = sets.Count; for (int i = 0; i < setsSpared.Count(); i++) { var set = setsSpared[i]; var ch = notUsedChs[chOffIdx + i]; set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):{curStrip.StripName}/通道:{ch.ChannelName}/冗余"; //更新全局已使用通道 allUsedCH.Add(ch.ChannelID); } #endregion } return true; } } } return Success(cablesNeedAssigned); } /// /// 找到下一个可用的端子排 /// /// 电缆总set /// 所有的端子排 /// 使用过的channelId /// private ec_PanelStripEntity FindNextAvailableTS(int setCount,int setPreAssigned, List TSs, List allUsedCH) { foreach (var TS in TSs) { var validChs = TS.Channels.Where(x => x.lock_flg == 0).ToList();//过滤掉锁定的通道 var usedChs = validChs.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); var notUsedChs = validChs.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); double sparedRate = notUsedChs.Count * 1.0 / validChs.Count * 1.0; if (sparedRate < 0.05 || notUsedChs.Count < setPreAssigned) { //没有空闲通道 next //或者空闲通道不够放下这个电缆的所有set } else { TS.Channels = validChs.OrderBy(X => X.Channel_Seq).ToList(); var seq = TSs.IndexOf(TS); if (setCount > 10) { //另外如果电缆对数大于10对,即像12 * 2 * 0.75这种,无论它预分配了多少根电缆对,永远预留4个以下的空白通道。意思就是一个模块里面够4个或4个以上空白通道就留4个空白通道,如果不够4个就留4个以下即可。 if (notUsedChs.Count < 4) { //没有足够的空闲通道 next continue; } } return TS;//找到了 } } return null;//都没有 } /// /// 根据step2的预分配结果,进行实际的分配,修改数据库。 /// /// /// 传入有效的cable即可,未成功分配的cable不需要 /// [HttpPost] public IHttpActionResult AutoAssignCable2Channel_step3(string projId, [FromBody] List cables) { //大原则:同一根电缆的信号不能跨采集箱,跨模块可以。 return Success(cables); } /// /// 把电缆分配到空余的模块的通道上。 /// /// /// /// [HttpPost] public IHttpActionResult AssignCable2Channel(string projId, bool execute = false) { var asyncContent = Request.Content.ReadAsStringAsync().Result; var cableIds = asyncContent.ToObject>(); return Success("res"); } /// /// 查询所有需要预分配的电缆 /// /// /// [HttpGet] public IHttpActionResult GetCablePreAssignPreview(string projId) { var bll = new ec_CableBLL(); var res = bll.GetCablesPreAssign(projId, null); return Success(res); } /// /// 查询所有的电缆 /// /// /// true或false /// [HttpGet] public IHttpActionResult GetCables(string projId, bool homerun = true) { var res = ec_CableBLL.GetCables(projId, homerun);//这有个问题 如果一开始不知道要关联到那个通道 就不会有ec cable出来 return Success(res); } /// /// PanelStrip参考库 /// /// /// false时,带上下面的通道和端子。true时,就是返回端子排自己 /// [HttpGet] public IHttpActionResult GetPanelStrips(string projId, bool OnlySelf = false) { var res = ec_PanelStripBLL.GetList("{ProjectId:\"" + projId + "\",OnlySelf:\"" + OnlySelf + "\"}"); return Success(res); } /// /// 在IO分配界面,从左侧目录树中选择一个“端子排”后,进行查询 /// /// /// /// false时,带上下面的通道和端子。true时,就是返回端子排自己 /// [HttpGet] public IHttpActionResult GetPanelStrip(string StripID, string projId, bool OnlySelf = false, bool HaveChannel = true) { var res = ec_PanelStripBLL.GetEntity(projId, StripID, OnlySelf, HaveChannel); return Success(res); } /// /// 新增或者修改。 /// 新建相当于强行,因为正常流程是和engineerdata一起新建出来的。 /// 改的话,只是改location和端子排数量这些。常规属性依旧通过objectTypeAPI去做 /// /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SavePanel(ec_PanelEntity entity, string projId) { try { ec_PanelBLL.SaveEntity(projId, entity.PanelID, entity); return Success("OK"); } catch (System.Exception E) { return Fail(E.Message); } } /// /// SaveConnections 专用 /// private class Connections { public string ID { get; set; } public List Conns { get; set; } } /// /// 处理 接线关系,同时对cableset和wire的个别属性进行修改 /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SaveConnections(string projId) { try { var asyncContent = Request.Content.ReadAsStringAsync().Result; var entity = asyncContent.ToObject(); var res = entity.Conns; new ec_WireTerminalBLL().SaveConnections(projId, ref res); return Success(res); } catch (System.Exception E) { return Fail(E.Message); } } /// /// /// /// /// [HttpGet] public IHttpActionResult GetConnections(string projId, string ID) { try { var res = new ec_WireTerminalService().GetEntity(projId, ID); return Success(res); } catch (System.Exception E) { return Fail(E.Message); } } /// /// /// /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SaveCable(ec_CableEntity entity, string projId) { try { if (!string.IsNullOrEmpty(entity.CableID)) { ec_CableBLL.SaveEntity(projId, entity.CableID, entity); } return Success("OK"); } catch (System.Exception E) { return Fail(E.Message); } } /// /// 通过预设模式,批量创建端子排 通道 端子。此时strip数里 set数里已经根据profile给定好了。 /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult CreatePanelStripByProfile(string projId) { try { var asyncContent = Request.Content.ReadAsStringAsync().Result; var entity = asyncContent.ToObject(); string NewID = ""; ec_PanelStripBLL.SaveEntity(projId, "", entity, out NewID); var res = ec_PanelStripBLL.GetEntity(projId, NewID); return Success(res); } catch (System.Exception e) { return Fail(e.Message); } } ///// ///// 通过预设模式,批量创建端子排 端子(不需要了,因为预设模式只有对IO有通道的那边才有用) ///// ///// ///// //[HttpPost] //[HandlerApiLogin(FilterMode.Enforce)] //public IHttpActionResult CreatePanelStripByProfileNoChannel(string projId, ec_PanelStripEntity entity) //{ // try // { // string NewID = ""; // ec_PanelStripBLL.SaveEntity(projId, "", entity, out NewID, false); // var res = ec_PanelStripBLL.GetEntity(projId, NewID); // return Success(res); // } // catch (System.Exception e) // { // return Fail(e.Message); // } //} /// /// GetCableByEngID先去判断这个cable是否已经有了IO Module这边的记录 /// 电缆-端子连接,从图面上选择一个电缆对象 /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult CreateCableByProfile(string projId) { try { var asyncContent = Request.Content.ReadAsStringAsync().Result; var entity = asyncContent.ToObject(); ec_CableBLL.SaveEntity(projId, entity.CableID, entity); var res = ec_CableBLL.GetCableByEngID(projId, entity.EngineerDataID); return Success(res); } catch (System.Exception e) { return Fail(e.Message); } } /// /// 批量保存电缆的set wire 名字等 /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SaveCables(string projId) { try { var asyncContent = Request.Content.ReadAsStringAsync().Result; var entitys = asyncContent.ToObject>(); foreach (var entity in entitys) { ec_CableBLL.SaveEntity(projId, entity.CableID, entity); } return Success(entitys); } catch (System.Exception e) { return Fail(e.Message); } } /// /// 创建、修改 端子排和端子(一般是为非监测设备的端子排) /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SavePanelStripNoChannel(string projId, ec_PanelStripEntity entity) { try { string NewID = ""; ec_PanelStripBLL.SaveEntity(projId, entity.StripID, entity, out NewID, false); var res = ec_PanelStripBLL.GetEntity(projId, NewID, false, false); return Success(res); } catch (System.Exception e) { return Fail(e.Message); } } /// /// 保存端子排 通道 端子的信息(不管信号和接线关系)。 /// 迁移通道也是这里。 /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SavePanelStrip(string projId, ec_PanelStripEntity entity) { XmlConfigurator.Configure(); var log = log4net.LogManager.GetLogger("Debug"); //参数就是config里logger节点的名字 try { //var asyncContent = Request.Content.ReadAsStringAsync().Result; //var entity = asyncContent.ToObject(); ec_PanelStripBLL.SaveEntity(projId, entity.StripID, entity, out string NewID); var res = ec_PanelStripBLL.GetEntity(projId, NewID); return Success(res); } catch (System.Exception e) { log.Debug($"★★★{DateTime.Now.ToString()}★★★{e}★★★"); return Fail(e.Message); } } /// /// /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SavePanelStripTerm(ec_PanelStripTermEntity entity, string projId) { try { ec_PanelStripTermBLL.SaveEntity(projId, entity.TermID, entity); return Success("OK"); } catch (System.Exception e) { return Fail(e.Message); } } /// /// /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SavePanelChannel(ec_PanelChannelEntity entity, string projId) { try { var newId = ec_PanelChannelBLL.SaveEntity(projId, entity.ChannelID, entity); foreach (var term in entity.Terms) { ec_PanelStripTermBLL.SaveEntity(projId, term.TermID, term); } var res = ec_PanelChannelBLL.GetEntity(projId, newId); return Success(res); } catch (System.Exception e) { return Fail(e.Message); } } /// /// 调端子排所属Panel /// /// 端子排ID /// PanelID /// 项目ID [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SwitchPanelStrip(string StripID, string PanelID, string ProjectId) { try { ec_PanelStripBLL.SwitchPanelStrip(StripID, PanelID, ProjectId); return Success("OK"); } catch (System.Exception e) { return Fail(e.Message); } } /// /// 调换电缆的from/to信息 /// /// Cable的工程ID /// 项目ID [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SwitchCableFromTo(string ProjectId) { try { var asyncContent = Request.Content.ReadAsStringAsync().Result; var entityCol = asyncContent.ToObject>(); var res = new ec_enginedata_relService().SwitchCableFromTo(entityCol, ProjectId); if (res) { return Success("OK"); } else { return Fail("From/To 交换出现问题。"); } } catch (System.Exception e) { return Fail(e.Message); } } /// /// 比如 移动通道时,进行保存 /// /// /// /// [Obsolete] [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult SavePanelChannels(List entitys, string projId) { try { foreach (var entity in entitys) { var newId = ec_PanelChannelBLL.SaveEntity(projId, entity.ChannelID, entity); foreach (var term in entity.Terms) { ec_PanelStripTermBLL.SaveEntity(projId, term.TermID, term); } } return Success("OK"); } catch (System.Exception e) { return Fail(e.Message); } } /// /// 根据某个数据字典的编号来。默认有个id=1的枚举 /// /// /// private List GetLocList(string ProjectId) { var ec_dataitemBLL = new ec_dataitemBLL(); var settings = new ec_projectSettingsBLL(); var ListFlg = settings.GetEntity(GlobalObject.projSetting_enumlist_IORackPosition, ProjectId); if (ListFlg != null) { List frameLists = new List(); frameLists.Add(new ec_dataitemdetailEntity() { DataItemName = "默认", DataItemCode = "默认", DataItemDetailID = "1", UpDataItemDetailID = "", DataItemID = "" }); //{ Name = "默认", Desc = "默认", Id = "1", ParentID = "" }); var ListName = ec_dataitemBLL.GetList("{ProjectId:\"" + ProjectId + "\",keyword:\"" + ListFlg.SettingValue + "\"}").FirstOrDefault(); if (ListName == null) { return null; } else { frameLists[0].DataItemID = ListName.DataItemID; } var res = ec_dataitemBLL.GetDetailList(ListFlg.SettingValue, "", ProjectId, false); //区域 foreach (var item in res) { //frameLists.Add(new ec_dataitemdetailEntity() //{ // Name = item.DataItemName, // ParentID = item.UpDataItemDetailID, // Desc = item.DataItemCode, // Id = item.DataItemDetailID, // DataItemId = item.DataItemID //}); frameLists.Add(item); } return frameLists; } else { return null; } } /// /// 位置。缺省都放到“默认”下 /// /// /// [HttpGet] public IHttpActionResult GetLocationList(string ProjectId) { var RES = GetLocList(ProjectId); if (RES != null) { return Success(RES); } else { return Fail("未找到合适的数据字典,可以当做位置"); } } /// /// 根据id来删除 /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult DeletePanel(string PanelID, string ProjectId) { new ec_enginedataBLL().DeleteEntity(PanelID, ProjectId); return Success("删除成功!"); } /// /// 根据id来删除 /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult DeleteCableProfile(string CableID, string ProjectId) { ec_CableBLL.DeleteEntity(ProjectId, CableID); return Success("OK"); } /// /// 根据id来删除 /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult DeletePanelStrip(string StripID, string ProjectId) { ec_PanelStripBLL.DeleteEntity(ProjectId, StripID); return Success("删除成功!"); } /// /// 根据id来删除 /// /// /// [HttpPost] [HandlerApiLogin(FilterMode.Enforce)] public IHttpActionResult DeletePanelChannel(string ChannelID, string ProjectId) { ec_PanelChannelBLL.DeleteEntity(ProjectId, ChannelID); return Success("OK"); } } /// /// /// //public class Panel_Location //{ /// /// /// //public string ParentID { get; set; } /// /// /// //public string Id { set; get; } /// /// 所在数据字典的ID /// // public string DataItemId { set; get; } /// /// /// //public string Name { get; set; } /// /// 数据字典里的code /// //public string Desc { get; set; } //} }