diff --git a/Source/Applications/SystemCenter/Controllers/ExternalDB/LineSegmentWizardController.cs b/Source/Applications/SystemCenter/Controllers/ExternalDB/LineSegmentWizardController.cs index 19e48be60..ee6d9a7e6 100644 --- a/Source/Applications/SystemCenter/Controllers/ExternalDB/LineSegmentWizardController.cs +++ b/Source/Applications/SystemCenter/Controllers/ExternalDB/LineSegmentWizardController.cs @@ -21,15 +21,6 @@ // //****************************************************************************************************** -using GSF; -using GSF.Collections; -using GSF.Data; -using GSF.Data.Model; -using GSF.Security.Model; -using GSF.Web.Model; -using Newtonsoft.Json.Linq; -using openXDA.Model; -using openXDA.Model.SystemCenter; using System; using System.Collections.Generic; using System.Data; @@ -38,9 +29,11 @@ using System.Linq; using System.Reflection; using System.Web.Http; -using System.Windows.Forms; -using SystemCenter.Controllers; -using SystemCenter.Model; +using GSF.Collections; +using GSF.Data; +using GSF.Data.Model; +using openXDA.Model; +using openXDA.Model.SystemCenter; [RoutePrefix("api/LineSegmentWizard")] public class LineSegmentWizardController : ApiController @@ -269,68 +262,86 @@ public IHttpActionResult PostTestConnection() [HttpPost, Route("Save/{id:int}")] public IHttpActionResult PostData(int id, [FromBody] LineConfiguration record) { - if (!base.User.IsInRole(PostRoles)) + if (!User.IsInRole(PostRoles)) return Unauthorized(); - using (AdoDataConnection connection = new AdoDataConnection(Connection)) + using (AdoDataConnection connection = new(Connection)) { - Line line = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", id); + Line line = new TableOperations(connection).QueryRecordWhere("ID = {0}", id) + ?? throw new InvalidOperationException($"Line with ID {id} does not exist"); + line.ConnectionFactory = () => new AdoDataConnection(Connection); - if (line is null) - throw new InvalidOperationException($"Line with ID {id} does not exist"); - int lineToSegmentConnectionID = new TableOperations(connection).QueryRecordWhere("Name = {0}", "Line-LineSegment").ID; - List updatedSegments = record.Sections.SelectMany(s => s.Segments).Where(s => s.ID > 0).ToList(); - TableOperations segmentTbl = new TableOperations(connection); - TableOperations segmentCxnTbl = new TableOperations(connection); - TableOperations assetLocationTbl = new TableOperations(connection); + TableOperations assetConnectionTable = new(connection); + TableOperations assetConnectionTypeTable = new(connection); + TableOperations segmentTbl = new(connection); + TableOperations segmentCxnTbl = new(connection); + TableOperations assetLocationTbl = new(connection); + + List allSegments = [.. record.Sections + .SelectMany(s => s.Segments)]; - // Remove any segments that are no longer there, and update existing Segments - foreach (LineSegment oldSegment in line.Segments) + // Remove any segments that are no longer there + IEnumerable removedSegments = line.Segments + .GroupJoin(allSegments, s => s.ID, s => s.ID, (Segment, grouping) => (Segment, Removed: !grouping.Any())) + .Where(tuple => tuple.Removed) + .Select(tuple => tuple.Segment); + + foreach (LineSegment segment in removedSegments) + connection.ExecuteNonQuery($"EXEC UniversalCascadeDelete 'Asset', 'ID = {segment.ID}'"); + + // Update existing segments + IEnumerable<(LineSegment, Segment)> updatedSegments = line.Segments + .Join(allSegments, s => s.ID, s => s.ID, (old, updated) => (old, updated)); + + foreach ((LineSegment oldSegment, Segment updatedSeg) in updatedSegments) { - Segment updatedSeg = updatedSegments.Find(item => item.ID == oldSegment.ID); - if (updatedSeg is null) - connection.ExecuteNonQuery($"EXEC UniversalCascadeDelete 'Asset', 'ID = {oldSegment.ID}'"); - else - { - oldSegment.R0 = updatedSeg.R0; - oldSegment.R1 = updatedSeg.R1; - oldSegment.X1 = updatedSeg.X1; - oldSegment.X0 = updatedSeg.X0; - oldSegment.Length = updatedSeg.Length; - oldSegment.AssetKey = updatedSeg.AssetKey; - oldSegment.AssetName = updatedSeg.AssetName; - oldSegment.FromBus= updatedSeg.FromBus; - oldSegment.ToBus= updatedSeg.ToBus; - oldSegment.Description= updatedSeg.Description; - oldSegment.ThermalRating= updatedSeg.ThermalRating; - - segmentTbl.UpdateRecord(oldSegment); - oldSegment.connectedSegments.ForEach(cxn => segmentCxnTbl.DeleteRecord(cxn)); - oldSegment.AssetLocations.ForEach(loc => assetLocationTbl.DeleteRecord(loc)); - } + oldSegment.R0 = updatedSeg.R0; + oldSegment.R1 = updatedSeg.R1; + oldSegment.X1 = updatedSeg.X1; + oldSegment.X0 = updatedSeg.X0; + oldSegment.Length = updatedSeg.Length; + oldSegment.AssetKey = updatedSeg.AssetKey; + oldSegment.AssetName = updatedSeg.AssetName; + oldSegment.FromBus = updatedSeg.FromBus; + oldSegment.ToBus = updatedSeg.ToBus; + oldSegment.Description = updatedSeg.Description; + oldSegment.ThermalRating = updatedSeg.ThermalRating; + + segmentTbl.UpdateRecord(oldSegment); + oldSegment.ConnectedSegments.ForEach(cxn => segmentCxnTbl.DeleteRecord(cxn)); + oldSegment.AssetLocations.ForEach(loc => assetLocationTbl.DeleteRecord(loc)); } - Func generatekey = () => + // Add any new segments + int lineToSegmentConnectionID = assetConnectionTypeTable.QueryRecordWhere("Name = {0}", "Line-LineSegment").ID; + IEnumerable newSegments = [.. allSegments.Where(s => s.ID == 0)]; + + IEnumerable GenerateKeys() { + string prefix = $"{line.AssetKey}-S"; + TableOperations assetTable = new TableOperations(connection); + + HashSet segmentKeys = [.. assetTable + .QueryRecordsWhere("AssetKey LIKE {0}", $"{prefix}%") + .Select(asset => asset.AssetKey)]; + int i = 1; - string key = line.AssetKey + "-S" + i.ToString("00"); - TableOperations assetTbl = new TableOperations(connection); - while (assetTbl.QueryRecordCountWhere("Assetkey = {0}", key) > 0) + + while (true) { + string key = $"{prefix}{i:00}"; + + if (!segmentKeys.Contains(key)) + yield return key; + i++; - key = line.AssetKey + "-S" + i.ToString("00"); } - return i.ToString("00"); - }; + } - // Add any new Segments - List newSegments = record.Sections.SelectMany(s => s.Segments).Where(s => s.ID == 0).ToList(); - foreach (Segment newSegment in newSegments) + foreach ((string key, Segment newSegment) in newSegments.Zip(GenerateKeys(), (segment, key) => (key, segment))) { - string num = generatekey(); - string key = line.AssetKey + "-S" + num; - LineSegment segment = new LineSegment() + LineSegment segment = new() { R0 = newSegment.R0, R1 = newSegment.R1, @@ -350,7 +361,7 @@ public IHttpActionResult PostData(int id, [FromBody] LineConfiguration record) newSegment.ID = segmentTbl.QueryRecordWhere("AssetKey = {0}", key).ID; newSegment.AssetKey = key; - new TableOperations(connection).AddNewRecord(new AssetConnection() + assetConnectionTable.AddNewRecord(new AssetConnection() { ChildID = newSegment.ID, ParentID = line.ID, @@ -358,99 +369,79 @@ public IHttpActionResult PostData(int id, [FromBody] LineConfiguration record) }); } - - // Walk through Sections to set up connections + // Walk through sections to set up connections between segments in the same section foreach (Section section in record.Sections) { - if (section.Segments.Count < 2) continue; - int i = 1; - while (i < section.Segments.Count) - { - int previousID = section.Segments[i - 1].ID; - if (previousID == 0) - previousID = newSegments.Find(s => s.AssetKey == section.Segments[i - 1].AssetKey).ID; + IEnumerable<(Segment, Segment)> connections = section.Segments + .Skip(1) + .Zip(section.Segments, (next, previous) => (previous, next)); - int nextID = section.Segments[i].ID; - if (nextID == 0) - nextID = newSegments.Find(s => s.AssetKey == section.Segments[i].AssetKey).ID; - - segmentCxnTbl.AddNewRecord(new LineSegmentConnections() { - ChildSegment = previousID, - ParentSegment = nextID + foreach ((Segment previous, Segment next) in connections) + { + segmentCxnTbl.AddNewRecord(new LineSegmentConnections() + { + ChildSegment = previous.ID, + ParentSegment = next.ID }); - i++; } } - // Walk through Taps to set up connections and FROMBus ends - foreach (Tap tap in record.Taps) + // Walk through taps to set up asset locations + IEnumerable<(Segment, string)> startBuses = record.Sections.Select(section => (section.Segments[0], section.StartBus)); + IEnumerable<(Segment, string)> endBuses = record.Sections.Select(section => (section.Segments.Last(), section.EndBus)); + IEnumerable<(Segment Segment, string Bus)> allBuses = startBuses.Concat(endBuses); + + IEnumerable<(Tap Tap, Segment Segment)> locationMap = record.Taps + .Where(tap => tap.StationID is not null) + .Join(allBuses, tap => tap.Bus, tuple => tuple.Bus, (tap, tuple) => (tap, tuple.Segment)); + + foreach ((Tap tap, Segment segment) in locationMap) + { + assetLocationTbl.AddNewRecord(new AssetLocation() + { + AssetID = segment.ID, + LocationID = tap.StationID.GetValueOrDefault() + }); + } + + // Walk through buses to set up connections between distinct sections + foreach (IGrouping grouping in allBuses.GroupBy(tuple => tuple.Bus, tuple => tuple.Segment)) { - List segments = record.Sections.Where(s => s.StartBus == tap.Bus) - .Select(s => s.Segments.First()) - .Select(s => segmentTbl.QueryRecordWhere("Assetkey = {0}", s.AssetKey)).ToList(); + IEnumerable<(Segment, Segment)> pairs = grouping + .SelectMany(_ => grouping, (Left, Right) => (Left, Right)) + .Where(tuple => tuple.Left.ID < tuple.Right.ID); - - if (!(tap.StationID is null) && segments.Count > 0) + foreach ((Segment child, Segment parent) in pairs) { - segments.ForEach(s => + segmentCxnTbl.AddNewRecord(new LineSegmentConnections() { - s.IsEnd = true; - segmentTbl.UpdateRecord(s); - assetLocationTbl.AddNewRecord(new AssetLocation() - { - AssetID = s.ID, - LocationID = tap.StationID ?? -1 - }); + ChildSegment = child.ID, + ParentSegment = parent.ID }); } + } - segments.AddRange(record.Sections.Where(s => s.EndBus == tap.Bus) - .Select(s => s.Segments.Last()) - .Select(s => segmentTbl.QueryRecordWhere("Assetkey = {0}", s.AssetKey)).ToList()); - + // Recreate the line to reload line segments from the database + Line reloader = new() + { + ID = line.ID, + ConnectionFactory = line.ConnectionFactory + }; - for (int j = 0; j < segments.Count; j++) - for (int k = 0; k < segments.Count; k++) - { - if (k >= j) break; - - int previousID = segments[j].ID; - if (previousID == 0) - previousID = newSegments.Find(s => s.AssetKey == segments[j].AssetKey).ID; + // Walk through buses to set up ends + IEnumerable endSegments = allBuses + .GroupBy(tuple => tuple.Bus, tuple => tuple.Segment) + .Where(grouping => !grouping.Skip(1).Any()) + .SelectMany(grouping => grouping); - int nextID = segments[k].ID; - if (nextID == 0) - nextID = newSegments.Find(s => s.AssetKey == segments[k].AssetKey).ID; + IEnumerable<(LineSegment, bool)> endFlagUpdates = reloader.Segments + .GroupJoin(endSegments, s => s.ID, s => s.ID, (Segment, grouping) => (Segment, IsEnd: grouping.Any())) + .Where(tuple => tuple.Segment.IsEnd != tuple.IsEnd); - segmentCxnTbl.AddNewRecord(new LineSegmentConnections() - { - ChildSegment = previousID, - ParentSegment = nextID - }); - } - - } - - // Walk through Taps to set up ToBus ends - foreach (Tap tap in record.Taps) + foreach ((LineSegment segment, bool isEnd) in endFlagUpdates) { - List segments = record.Sections.Where(s => s.EndBus == tap.Bus) - .Select(s => s.Segments.Last()) - .Select(s => segmentTbl.QueryRecordWhere("Assetkey = {0}", s.AssetKey)).ToList(); - - if ((tap.StationID is null) || segments.Count == 0) - continue; - - segments.ForEach(s => - { - s.IsEnd = true; - segmentTbl.UpdateRecord(s); - assetLocationTbl.AddNewRecord(new AssetLocation() - { - AssetID = s.ID, - LocationID = tap.StationID ?? -1 - }); - }); + segment.IsEnd = isEnd; + segmentTbl.UpdateRecord(segment); } } diff --git a/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDAAssetController.cs b/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDAAssetController.cs index 019ae00e5..4c08ee643 100644 --- a/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDAAssetController.cs +++ b/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDAAssetController.cs @@ -24,7 +24,6 @@ using System; using System.Collections.Generic; using System.Data; -using System.Diagnostics; using System.Linq; using System.Transactions; using System.Web.Http; @@ -852,6 +851,7 @@ private void CreateLineSegmentFromJToken(LineSegment lineSegment, JToken record) lineSegment.X1 = record["X1"].ToObject(); lineSegment.Length = record["Length"].ToObject(); lineSegment.ThermalRating = record["ThermalRating"].ToObject(); + lineSegment.IsEnd = record["IsEnd"].ToObject(); } private void CreateBreakerFromJToken(Breaker breaker, JToken record) diff --git a/Source/Applications/SystemCenter/EventWidgets b/Source/Applications/SystemCenter/EventWidgets index 8203aa8eb..240fa18ea 160000 --- a/Source/Applications/SystemCenter/EventWidgets +++ b/Source/Applications/SystemCenter/EventWidgets @@ -1 +1 @@ -Subproject commit 8203aa8eb88900156f81ec4df027f8bee1d0b460 +Subproject commit 240fa18eaa044ed2e862fd43d24dae964b31f588