//****************************************************************************************************** // OutputStreamDevicePhasor.cs - Gbtc // // Copyright © 2010, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 08/15/2011 - Aniket Salver // Generated original version of source code. // 09/16/2011 - Mehulbhai P Thakkar // Fixed issue in Load() method. // 09/19/2011 - Mehulbhai P Thakkar // Added OnPropertyChanged() on all properties to reflect changes on UI. // Fixed Load() and GetLookupList() static methods. // 06/27/2012- Vijay Sukhavasi // Modified Delete() to delete measurements associated with phasor // 08/15/2012 - Aniket Salver // Added paging and sorting technique. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Data; using System.Linq; using System.Text.RegularExpressions; using System.Windows; using GSF.Data; using GSF.TimeSeries.UI; namespace GSF.PhasorProtocols.UI.DataModels { /// /// Represents a record of information as defined in the database. /// public class OutputStreamDevicePhasor : DataModelBase { #region [ Members ] private Guid m_nodeID; private int m_outputStreamDeviceID; private int m_id; private string m_label; private string m_type; private string m_phase; private int m_scalingValue; private int m_loadOrder; #endregion #region [ Properties ] /// /// Gets or sets NodeID. /// [Required(ErrorMessage = "OutputStreamDevicePhasor NodeID is a required field, please provide value.")] public Guid NodeID { get => m_nodeID; set { m_nodeID = value; OnPropertyChanged("NodeID"); } } /// /// Gets or sets OutputStreamDeviceID. /// [Required(ErrorMessage = "OutputStreamDevicePhasor OutputStreamDeviceID is a required field, please provide value.")] public int OutputStreamDeviceID { get => m_outputStreamDeviceID; set { m_outputStreamDeviceID = value; OnPropertyChanged("OutputStreamDeviceID"); } } /// /// Gets or sets ID. /// // Field is populated by database via auto-increment and has no screen interaction, so no validation attributes are applied [Required(ErrorMessage = "OutputStreamDevicePhasor ID is a required field, please provide value.")] public int ID { get => m_id; set { m_id = value; OnPropertyChanged("ID"); } } /// /// Gets or sets Label. /// [Required(ErrorMessage = "OutputStreamDevicePhasor Label is a required field, please provide value.")] [StringLength(200, ErrorMessage = "OutputStreamDevicePhasor Label cannot exceed 200 characters.")] public string Label { get => m_label; set { m_label = value; OnPropertyChanged("Label"); } } /// /// Gets or sets Type. /// [Required(ErrorMessage = "OutputStreamDevicePhasor Type is a required field, please provide value.")] [DefaultValue("V")] public string Type { get => m_type; set { m_type = value; OnPropertyChanged("Type"); } } /// /// Gets or sets Phase. /// [Required(ErrorMessage = "OutputStreamDevicePhasor Phase is a required field, please provide value.")] [DefaultValue("+")] public string Phase { get => m_phase; set { m_phase = value; OnPropertyChanged("Phase"); } } /// /// Gets or sets ScalingValue . /// [Required(ErrorMessage = "OutputStreamDevicePhasor ScalingValue is a required field, please provide value.")] public int ScalingValue { get => m_scalingValue; set { m_scalingValue = value; OnPropertyChanged("ScalingValue"); } } /// /// Gets or sets LoadOrder . /// [Required(ErrorMessage = "OutputStreamDevicePhasor LoadOrder is a required field, please provide value.")] public int LoadOrder { get => m_loadOrder; set { m_loadOrder = value; OnPropertyChanged("LoadOrder"); } } /// /// Gets or sets PhasorType. /// public string PhasorType { get; private set; } /// /// Gets or sets PhaseType. /// public string PhaseType { get; private set; } /// /// Gets or sets when the current was created. /// // Field is populated by trigger and has no screen interaction, so no validation attributes are applied public DateTime CreatedOn { get; set; } /// /// Gets or sets who the current was created by. /// // Field is populated by trigger and has no screen interaction, so no validation attributes are applied public string CreatedBy { get; set; } /// /// Gets or sets when the current was updated. /// // Field is populated by trigger and has no screen interaction, so no validation attributes are applied public DateTime UpdatedOn { get; set; } /// /// Gets or sets who the current was updated by. /// // Field is populated by trigger and has no screen interaction, so no validation attributes are applied public string UpdatedBy { get; set; } #endregion #region [ Static ] // Static Methods /// /// LoadKeys information as an style list. /// /// to connection to database. /// ID of the output stream device to filter data. /// The field to sort by. /// ASC or DESC for ascending or descending respectively. /// Collection of . public static IList LoadKeys(AdoDataConnection database, int outputStreamDeviceID, string sortMember, string sortDirection) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); IList outputStreamDevicePhasorList = new List(); string sortClause = string.Empty; if (!string.IsNullOrEmpty(sortMember)) sortClause = $"ORDER BY {sortMember} {sortDirection}"; DataTable OutputStreamDevicePhasorTable = database.Connection.RetrieveData(database.AdapterType, $"SELECT ID FROM OutputStreamDevicePhasor WHERE OutputStreamDeviceID = {outputStreamDeviceID} {sortClause}"); foreach (DataRow row in OutputStreamDevicePhasorTable.Rows) outputStreamDevicePhasorList.Add((row.ConvertField("ID"))); return outputStreamDevicePhasorList; } finally { if (createdConnection) database?.Dispose(); } } /// /// Loads information as an style list. /// /// to connection to database. /// Keys of the measurements to be loaded from the database /// Collection of . public static ObservableCollection Load(AdoDataConnection database, IList keys) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); OutputStreamDevicePhasor[] outputStreamDevicePhasorList = null; if (keys is not null && keys.Count > 0) { string commaSeparatedKeys = keys.Select(key => "" + key.ToString() + "").Aggregate((str1, str2) => str1 + "," + str2); string query = database.ParameterizedQueryString("SELECT NodeID, OutputStreamDeviceID, ID, Label, Type, Phase, ScalingValue, LoadOrder " + $"FROM OutputStreamDevicePhasor WHERE ID IN ({commaSeparatedKeys})"); DataTable outputStreamDevicePhasorTable = database.Connection.RetrieveData(database.AdapterType, query); outputStreamDevicePhasorList = new OutputStreamDevicePhasor[outputStreamDevicePhasorTable.Rows.Count]; foreach (DataRow row in outputStreamDevicePhasorTable.Rows) { int id = row.ConvertField("ID"); outputStreamDevicePhasorList[keys.IndexOf(id)] = new OutputStreamDevicePhasor() { NodeID = database.Guid(row, "NodeID"), OutputStreamDeviceID = row.ConvertField("OutputStreamDeviceID"), ID = id, Label = row.Field("Label"), Type = row.Field("Type"), Phase = row.Field("Phase"), ScalingValue = row.ConvertField("ScalingValue"), LoadOrder = row.ConvertField("LoadOrder"), PhaseType = row.Field("Phase") == "+" ? "Positive Sequence" : row.Field("Phase") == "-" ? "Negative Sequence" : row.Field("Phase") == "0" ? "Zero Sequence" : row.Field("Phase") == "A" ? "Phase A" : row.Field("Phase") == "B" ? "Phase B" : "Phase C", PhasorType = row.Field("Type") == "V" ? "Voltage" : "Current" }; } } return new ObservableCollection(outputStreamDevicePhasorList ?? new OutputStreamDevicePhasor[0]); } finally { if (createdConnection) database?.Dispose(); } } /// /// Gets a style list of information. /// /// to connection to database. /// ID of the output stream device to filter data. /// Indicates if selection on UI is optional for this collection. /// containing ID and Label of OutputStreamDevicePhasors defined in the database. public static Dictionary GetLookupList(AdoDataConnection database, int outputStreamDeviceID, bool isOptional = false) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); Dictionary OutputStreamDevicePhasorList = new(); if (isOptional) OutputStreamDevicePhasorList.Add(0, "Select OutputStreamDevicePhasor"); string query = database.ParameterizedQueryString("SELECT ID, Label FROM OutputStreamDevicePhasor " + "WHERE OutputStreamDeviceID = {0} ORDER BY LoadOrder", "outputStreamDeviceID"); DataTable OutputStreamDevicePhasorTable = database.Connection.RetrieveData(database.AdapterType, query, DefaultTimeout, outputStreamDeviceID); foreach (DataRow row in OutputStreamDevicePhasorTable.Rows) OutputStreamDevicePhasorList[row.ConvertField("ID")] = row.Field("Label"); return OutputStreamDevicePhasorList; } finally { if (createdConnection) database?.Dispose(); } } /// /// Saves information to database. /// /// to connection to database. /// Information about . /// String, for display use, indicating success. public static string Save(AdoDataConnection database, OutputStreamDevicePhasor outputStreamDevicePhasor) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); string query; if (outputStreamDevicePhasor.ID == 0) { query = database.ParameterizedQueryString("INSERT INTO OutputStreamDevicePhasor (NodeID, OutputStreamDeviceID, Label, Type, Phase, ScalingValue, " + "LoadOrder, UpdatedBy, UpdatedOn, CreatedBy, CreatedOn) VALUES ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10})", "nodeID", "outputStreamDeviceID", "label", "type", "phase", "scalingValue", "loadOrder", "updatedBy", "updatedOn", "createdBy", "createdOn"); database.Connection.ExecuteNonQuery(query, DefaultTimeout, database.CurrentNodeID(), outputStreamDevicePhasor.OutputStreamDeviceID, outputStreamDevicePhasor.Label, outputStreamDevicePhasor.Type, outputStreamDevicePhasor.Phase, outputStreamDevicePhasor.ScalingValue, outputStreamDevicePhasor.LoadOrder, CommonFunctions.CurrentUser, database.UtcNow, CommonFunctions.CurrentUser, database.UtcNow); // PhasorType, PhaseType @phasorName, @phaseType, OutputStreamDevicePhasor.PhasorType, OutputStreamDevicePhasor.PhaseType } else { query = "UPDATE OutputStreamDevicePhasor SET NodeID = {0}, OutputStreamDeviceID = {1}, Label = {2}, Type = {3}, " + "Phase = {4}, ScalingValue = {5}, LoadOrder = {6}, UpdatedBy = {7}, UpdatedOn = {8} WHERE ID = {9}"; database.ExecuteNonQuery(DefaultTimeout, query, outputStreamDevicePhasor.NodeID, outputStreamDevicePhasor.OutputStreamDeviceID, outputStreamDevicePhasor.Label, outputStreamDevicePhasor.Type, outputStreamDevicePhasor.Phase, outputStreamDevicePhasor.ScalingValue, outputStreamDevicePhasor.LoadOrder, CommonFunctions.CurrentUser, database.UtcNow, outputStreamDevicePhasor.ID); //PhasorType = @typeName, PhaseType = @PhaseType" OutputStreamDevicePhasor.PhasorType, OutputStreamDevicePhasor.PhaseType, } return "OutputStreamDevicePhasor information saved successfully"; } finally { if (createdConnection) database?.Dispose(); } } /// /// Deletes specified record and its associated measurements from database. /// /// to connection to database. /// ID of the record to be deleted. /// String, for display use, indicating success. public static string Delete(AdoDataConnection database, int outputStreamDevicePhasorID) { bool createdConnection = false; string nextAngleSignalReference = string.Empty; string nextMagnitudeSignalReference = string.Empty; string lastAffectedMeasurementsMessage = string.Empty; try { createdConnection = CreateConnection(ref database); // Setup current user context for any delete triggers CommonFunctions.SetCurrentUserContext(database); GetDeleteMeasurementDetails(database, outputStreamDevicePhasorID, out string angleSignalReference, out string magnitudeSignalReference, out int adapterID, out int outputStreamDeviceID); // Delete angle/magnitude of measurements if they exist database.Connection.ExecuteNonQuery(database.ParameterizedQueryString("DELETE FROM OutputStreamMeasurement WHERE SignalReference = {0} AND AdapterID = {1}", "signalReference", "adapterID"), DefaultTimeout, angleSignalReference, adapterID); database.Connection.ExecuteNonQuery(database.ParameterizedQueryString("DELETE FROM OutputStreamMeasurement WHERE SignalReference = {0} AND AdapterID = {1}", "signalReference", "adapterID"), DefaultTimeout, magnitudeSignalReference, adapterID); int presentDevicePhasorCount = Convert.ToInt32(database.Connection.ExecuteScalar(database.ParameterizedQueryString("SELECT COUNT(*) FROM OutputStreamDevicePhasor WHERE OutputStreamDeviceID = {0}", "outputStreamDeviceID"), DefaultTimeout, outputStreamDeviceID)); // Using signal reference angle/mag deleted build the next signal reference (increment by 1) int.TryParse(Regex.Match(magnitudeSignalReference, @"\d+$").Value, out int deletedSignalReferenceIndex); string angleSignalReferenceBase = Regex.Replace(angleSignalReference, @"\d+$", ""); string magnitudeSignalReferenceBase = Regex.Replace(magnitudeSignalReference, @"\d+$", ""); for (int i = deletedSignalReferenceIndex; i < presentDevicePhasorCount; i++) { // We will be modifying the measurements with signal reference index i+1 to have signal reference index i. string previousAngleSignalReference = $"{angleSignalReferenceBase}{i}"; nextAngleSignalReference = $"{angleSignalReferenceBase}{i + 1}"; string previousMagnitudeSignalReference = $"{magnitudeSignalReferenceBase}{i}"; nextMagnitudeSignalReference = $"{magnitudeSignalReferenceBase}{i + 1}"; // For angle... // Obtain details of measurements after the deleted measurements, then modify the signal reference (decrement by 1) and put it back OutputStreamMeasurement outputStreamMeasurement = GetOutputMeasurementDetails(database, nextAngleSignalReference, adapterID); outputStreamMeasurement.SignalReference = previousAngleSignalReference; OutputStreamMeasurement.Save(database, outputStreamMeasurement); // For magnitude... outputStreamMeasurement = GetOutputMeasurementDetails(database, nextMagnitudeSignalReference, adapterID); outputStreamMeasurement.SignalReference = previousMagnitudeSignalReference; OutputStreamMeasurement.Save(database, outputStreamMeasurement); } database.Connection.ExecuteNonQuery(database.ParameterizedQueryString("DELETE FROM OutputStreamDevicePhasor WHERE ID = {0}", "outputStreamDevicePhasorID"), DefaultTimeout, outputStreamDevicePhasorID); return "OutputStreamDevicePhasor deleted successfully"; } catch (Exception ex) { if (!string.IsNullOrEmpty(nextMagnitudeSignalReference)) lastAffectedMeasurementsMessage = $"{Environment.NewLine}(Last affected measurements: {nextMagnitudeSignalReference} {nextAngleSignalReference})"; CommonFunctions.LogException(database, "OutputStreamDevicePhasor.Delete", ex); MessageBoxResult dialogResult = MessageBox.Show($"Could not delete or modify measurements.{Environment.NewLine}Do you still wish to delete this Phasor?{lastAffectedMeasurementsMessage}", "", MessageBoxButton.YesNo); if (dialogResult == MessageBoxResult.Yes) { database.Connection.ExecuteNonQuery(database.ParameterizedQueryString("DELETE FROM OutputStreamDevicePhasor WHERE ID = {0}", "outputStreamDevicePhasorID"), DefaultTimeout, outputStreamDevicePhasorID); return "OutputStreamDevicePhasor deleted successfully but failed to modify all measurements "; } else { Exception exception = ex.InnerException ?? ex; return $"Delete OutputStreamDevicePhasor was unsuccessful: {exception.Message}"; } } finally { if (createdConnection) database?.Dispose(); } } private static OutputStreamMeasurement GetOutputMeasurementDetails(AdoDataConnection database, string signalReference, int adapterID) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); string query = database.ParameterizedQueryString("SELECT * FROM OutputStreamMeasurement WHERE SignalReference = {0} AND AdapterID = {1}", "signalReference", "adapterID"); DataRow row = database.Connection.RetrieveData(database.AdapterType, query, DefaultTimeout, signalReference, adapterID).Rows[0]; OutputStreamMeasurement outputStreamMeasurement = new() { NodeID = row.ConvertField("NodeID"), AdapterID = row.Field("AdapterID"), ID = row.Field("ID"), HistorianID = row.Field("HistorianID"), PointID = row.Field("PointID"), SignalReference = row.ConvertField("SignalReference"), CreatedOn = row.ConvertField("CreatedOn"), CreatedBy = row.Field("CreatedBy"), UpdatedOn = row.ConvertField("UpdatedOn"), UpdatedBy = row.Field("UpdatedBy") }; return outputStreamMeasurement; } catch (Exception ex) { CommonFunctions.LogException(database, "OutputStreamDevicePhasor.GetOutputMeasurementDetails", ex); throw; } finally { if (createdConnection) database?.Dispose(); } } private static void GetDeleteMeasurementDetails(AdoDataConnection database, int outputStreamDevicePhasorID, out string angleSignalReference, out string magnitudeSignalReference, out int adapterID, out int outputStreamDeviceID) { const string outputPhasorFormat = "SELECT Label, OutputStreamDeviceID FROM OutputStreamDevicePhasor WHERE ID = {0}"; const string outputDeviceFormat = "SELECT Acronym, AdapterID FROM OutputStreamDevice WHERE ID = {0}"; const string measurementDetailFormat = "SELECT PointTag FROM MeasurementDetail WHERE DeviceAcronym = '{0}' AND PhasorLabel = '{1}' AND SignalTypeSuffix = '{2}'"; const string outputMeasurementDetailFormat = "SELECT SignalReference FROM OutputStreamMeasurementDetail WHERE SourcePointTag = '{0}'"; bool createdConnection = false; try { createdConnection = CreateConnection(ref database); DataRow outputPhasorRecord = database.Connection.RetrieveData(database.AdapterType, string.Format(outputPhasorFormat, outputStreamDevicePhasorID)).Rows[0]; string labelName = outputPhasorRecord.Field("Label"); outputStreamDeviceID = outputPhasorRecord.ConvertField("OutputStreamDeviceID"); DataRow outputDeviceRecord = database.Connection.RetrieveData(database.AdapterType, string.Format(outputDeviceFormat, outputStreamDeviceID)).Rows[0]; string deviceName = outputDeviceRecord.Field("Acronym"); adapterID = outputDeviceRecord.ConvertField("AdapterID"); string anglePointTag = database.Connection.ExecuteScalar(string.Format(measurementDetailFormat, deviceName, labelName, "PA")).ToNonNullString(); angleSignalReference = database.Connection.ExecuteScalar(string.Format(outputMeasurementDetailFormat, anglePointTag)).ToNonNullString(); string magnitudePointTag = database.Connection.ExecuteScalar(string.Format(measurementDetailFormat, deviceName, labelName, "PM")).ToNonNullString(); magnitudeSignalReference = database.Connection.ExecuteScalar(string.Format(outputMeasurementDetailFormat, magnitudePointTag)).ToNonNullString(); } catch (Exception ex) { CommonFunctions.LogException(database, "OutputStreamDevicePhasor.GetDeleteMeasurementDetails", ex); throw; } finally { if (createdConnection) database?.Dispose(); } } #endregion } }