//****************************************************************************************************** // OutputStreamDeviceAnalog.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/5/2011 - Aniket Salver // Generated original version of source code. // 09/16/2011 - Mehulbhai P Thakkar // Fixed load method to filter data correctly. // 09/19/2011 - Mehulbhai P Thakkar // Added OnPropertyChanged() on all properties to reflect changes on UI. // Fixed database queries and collection population. // Fixed Load() and GetLookupList() static methods. // 06/27/2012- Vijay Sukhavasi // Modified Delete() to delete measurements associated with analog // 08/14/2012 - Aniket Salver // Added paging and sorting technique. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Collections.ObjectModel; 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 OutputStreamDeviceAnalog : DataModelBase { #region [ Members ] private Guid m_nodeID; private int m_outputStreamDeviceID; private int m_id; private string m_label; private int m_type; private int m_scalingValue; private int m_loadOrder; #endregion #region [ Properties ] /// /// Gets or sets the current 's NodeID. /// [Required(ErrorMessage = "OutputStreamDeviceAnalog NodeID is a required field, please provide value.")] public Guid NodeID { get => m_nodeID; set { m_nodeID = value; OnPropertyChanged("NodeID"); } } /// /// Gets or sets the current 's OutputStreamDeviceID. /// [Required(ErrorMessage = "OutputStreamDeviceAnalog OutputStreamDeviceID is a required field, please provide value.")] public int OutputStreamDeviceID { get => m_outputStreamDeviceID; set { m_outputStreamDeviceID = value; OnPropertyChanged("OutputStreamDeviceID"); } } /// /// Gets or sets the current 's ID. /// // Field is populated by database via auto-increment and has no screen interaction, so no validation attributes are applied public int ID { get => m_id; set { m_id = value; OnPropertyChanged("ID"); } } /// /// Gets or sets the current 's Label. /// [Required(ErrorMessage = "OutputStreamDeviceAnalog Label is a required field, please provide value.")] [StringLength(200, ErrorMessage = "OutputStreamDeviceAnalog Label cannot exceed 200 characters.")] public string Label { get => m_label; set { m_label = value is null || value.Length <= 200 ? value : value.Substring(0, 200); OnPropertyChanged("Label"); } } /// /// Gets or sets the current 's Type. /// [Required(ErrorMessage = "OutputStreamDeviceAnalog Type is a required field, please provide value.")] public int Type { get => m_type; set { m_type = value; OnPropertyChanged("Type"); } } /// /// Gets or sets the current 's ScalingValue. /// [Required(ErrorMessage = "OutputStreamDeviceAnalog ScalingValue is a required field, please provide value.")] public int ScalingValue { get => m_scalingValue; set { m_scalingValue = value; OnPropertyChanged("ScalingValue"); } } /// /// Gets or sets the current 's LoadOrder. /// [Required(ErrorMessage = "OutputStreamDeviceAnalog LoadOrder is a required field, please provide value.")] public int LoadOrder { get => m_loadOrder; set { m_loadOrder = value; OnPropertyChanged("LoadOrder"); } } /// /// Gets the current 's TypeName. /// public string TypeName { get; private set; } /// /// Gets or sets the Date or Time the current was created on. /// // 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 the Date or Time when the current was updated on. /// // 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 outputStreamDeviceAnalogList = new List(); string sortClause = string.Empty; if (!string.IsNullOrEmpty(sortMember)) sortClause = $"ORDER BY {sortMember} {sortDirection}"; DataTable outputStreamDeviceAnalogTable = database.Connection.RetrieveData(database.AdapterType, $"SELECT ID FROM OutputStreamDeviceAnalog WHERE OutputStreamDeviceID = {outputStreamDeviceID} {sortClause}"); foreach (DataRow row in outputStreamDeviceAnalogTable.Rows) outputStreamDeviceAnalogList.Add((row.ConvertField("ID"))); return outputStreamDeviceAnalogList; } 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); OutputStreamDeviceAnalog[] outputStreamDeviceAnalogList = null; if (keys is not null && keys.Count > 0) { string commaSeparatedKeys = keys.Select(key => $"{key}").Aggregate((str1, str2) => $"{str1},{str2}"); string query = database.ParameterizedQueryString($"SELECT NodeID, OutputStreamDeviceID, ID, Label, Type, ScalingValue, LoadOrder FROM OutputStreamDeviceAnalog WHERE ID IN ({commaSeparatedKeys})"); DataTable outputStreamDeviceAnalogTable = database.Connection.RetrieveData(database.AdapterType, query, DefaultTimeout); outputStreamDeviceAnalogList = new OutputStreamDeviceAnalog[outputStreamDeviceAnalogTable.Rows.Count]; foreach (DataRow row in outputStreamDeviceAnalogTable.Rows) { int id = row.ConvertField("ID"); outputStreamDeviceAnalogList[keys.IndexOf(id)] = new OutputStreamDeviceAnalog() { NodeID = database.Guid(row, "NodeID"), OutputStreamDeviceID = row.ConvertField("OutputStreamDeviceID"), ID = id, Label = row.Field("Label"), Type = row.ConvertField("Type"), ScalingValue = row.ConvertField("ScalingValue"), LoadOrder = row.ConvertField("LoadOrder"), TypeName = row.ConvertField("Type") == 0 ? "Single point-on-wave" : row.ConvertField("Type") == 1 ? "RMS of analog input" : "Peak of analog input" }; } } return new ObservableCollection(outputStreamDeviceAnalogList ?? new OutputStreamDeviceAnalog[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 Name of OutputStreamDeviceAnalog defined in the database. public static Dictionary GetLookupList(AdoDataConnection database, int outputStreamDeviceID, bool isOptional = false) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); Dictionary OutputStreamDeviceAnalogList = new(); if (isOptional) OutputStreamDeviceAnalogList.Add(0, "Select OutputStreamDeviceAnalog"); string query = database.ParameterizedQueryString("SELECT ID, Label FROM OutputStreamDeviceAnalog WHERE OutputStreamDeviceID = {0} ORDER BY LoadOrder", "outputStreamDeviceID"); DataTable OutputStreamDeviceAnalogTable = database.Connection.RetrieveData(database.AdapterType, query, DefaultTimeout, outputStreamDeviceID); foreach (DataRow row in OutputStreamDeviceAnalogTable.Rows) OutputStreamDeviceAnalogList[row.ConvertField("ID")] = row.Field("Label"); return OutputStreamDeviceAnalogList; } 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, OutputStreamDeviceAnalog outputStreamDeviceAnalog) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); string query; if (outputStreamDeviceAnalog.ID == 0) { query = database.ParameterizedQueryString("INSERT INTO OutputStreamDeviceAnalog (NodeID, OutputStreamDeviceID, Label, Type, ScalingValue, LoadOrder, " + "UpdatedBy, UpdatedOn, CreatedBy, CreatedOn) VALUES ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9})", "nodeID", "outputStreamDeviceID", "label", "type", "scalingValue", "loadOrder", "updatedBy", "updatedOn", "createdBy", "createdOn"); // TypeName, "typeName", database.Connection.ExecuteNonQuery(query, DefaultTimeout, database.CurrentNodeID(), outputStreamDeviceAnalog.OutputStreamDeviceID, outputStreamDeviceAnalog.Label, outputStreamDeviceAnalog.Type, outputStreamDeviceAnalog.ScalingValue, outputStreamDeviceAnalog.LoadOrder, CommonFunctions.CurrentUser, database.UtcNow, CommonFunctions.CurrentUser, database.UtcNow); //outputStreamDeviceAnalog.TypeName, } else { query = database.ParameterizedQueryString("UPDATE OutputStreamDeviceAnalog SET NodeID = {0}, OutputStreamDeviceID = {1}, Label = {2}, Type = {3}, " + "ScalingValue = {4}, LoadOrder = {5}, UpdatedBy = {6}, UpdatedOn = {7} WHERE ID = {8}", "nodeID", "outputStreamDeviceID", "label", "type", "scalingValue", "loadOrder", "updatedBy", "updatedOn", "id"); // TypeName= {6}, "typeName", database.Connection.ExecuteNonQuery(query, DefaultTimeout, outputStreamDeviceAnalog.NodeID, outputStreamDeviceAnalog.OutputStreamDeviceID, outputStreamDeviceAnalog.Label, outputStreamDeviceAnalog.Type, outputStreamDeviceAnalog.ScalingValue, outputStreamDeviceAnalog.LoadOrder, CommonFunctions.CurrentUser, database.UtcNow, outputStreamDeviceAnalog.ID); } // OutputStreamDeviceAnalog.TypeName, return "OutputStreamDeviceAnalog 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 outputStreamDeviceAnalogID) { bool createdConnection = false; string nextAnalogSignalReference = string.Empty; string lastAffectedMeasurementMessage = string.Empty; try { createdConnection = CreateConnection(ref database); // Setup current user context for any delete triggers CommonFunctions.SetCurrentUserContext(database); GetDeleteMeasurementDetails(database, outputStreamDeviceAnalogID, out string analogSignalReference, out int adapterID, out int outputStreamDeviceID); database.Connection.ExecuteNonQuery(database.ParameterizedQueryString("DELETE FROM OutputStreamMeasurement WHERE SignalReference = {0} AND AdapterID = {1}", "signalReference", "adapterID"), DefaultTimeout, analogSignalReference, adapterID); int presentDeviceAnalogCount = Convert.ToInt32(database.Connection.ExecuteScalar(database.ParameterizedQueryString("SELECT COUNT(*) FROM OutputStreamDeviceAnalog WHERE OutputStreamDeviceID = {0}", "outputStreamDeviceID"), DefaultTimeout, outputStreamDeviceID)); // Using signal reference of measurement deleted build the next signal reference (increment by 1) int.TryParse(Regex.Match(analogSignalReference, @"\d+$").Value, out int deletedSignalReferenceIndex); string signalReferenceBase = Regex.Replace(analogSignalReference, @"\d+$", ""); for (int i = deletedSignalReferenceIndex; i <= presentDeviceAnalogCount; i++) { // We will be modifying the measurement with signal reference index i+1 to have signal reference index i. string previousAnalogSignalReference = $"{signalReferenceBase}{i}"; nextAnalogSignalReference = $"{signalReferenceBase}{i + 1}"; // Obtain details of measurements of the deleted measurements, then modify the signal reference (decrement by 1) and put it back OutputStreamMeasurement outputStreamMeasurement = GetOutputMeasurementDetails(database, nextAnalogSignalReference, adapterID); outputStreamMeasurement.SignalReference = previousAnalogSignalReference; OutputStreamMeasurement.Save(database, outputStreamMeasurement); } database.Connection.ExecuteNonQuery(database.ParameterizedQueryString("DELETE FROM OutputStreamDeviceAnalog WHERE ID = {0}", "outputStreamDeviceAnalogID"), DefaultTimeout, outputStreamDeviceAnalogID); return "OutputStreamDeviceAnalog deleted successfully"; } catch (Exception ex) { if (!string.IsNullOrEmpty(nextAnalogSignalReference)) lastAffectedMeasurementMessage = $"{Environment.NewLine}(Last affected measurement: {nextAnalogSignalReference})"; CommonFunctions.LogException(database, "OutputStreamDeviceAnalog.Delete", ex); MessageBoxResult dialogResult = MessageBox.Show($"Could not delete or modify measurements.{Environment.NewLine}Do you still wish to delete this Analog?{lastAffectedMeasurementMessage}", "", MessageBoxButton.YesNo); if ((dialogResult == MessageBoxResult.Yes)) { database.Connection.ExecuteNonQuery(database.ParameterizedQueryString("DELETE FROM OutputStreamDeviceAnalog WHERE ID = {0}", "outputStreamDeviceAnalogID"), DefaultTimeout, outputStreamDeviceAnalogID); return "OutputStreamDeviceAnalog deleted successfully but failed to delete all measurements"; } else { Exception exception = ex.InnerException ?? ex; return $"Delete OutputStreamDeviceAnalog 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, "OutputStreamDeviceAnalog.GetOutputMeasurementDetails", ex); throw; } finally { if (createdConnection) database?.Dispose(); } } private static void GetDeleteMeasurementDetails(AdoDataConnection database, int outputStreamDeviceAnalogID, out string analogSignalReference, out int adapterID, out int outputStreamDeviceID) { const string outputAnalogFormat = "SELECT Label, OutputStreamDeviceID FROM OutputStreamDeviceAnalog 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 AlternateTag = '{1}' AND SignalTypeSuffix = 'AV'"; const string outputMeasurementDetailFormat = "SELECT SignalReference FROM OutputStreamMeasurementDetail WHERE SourcePointTag = '{0}'"; bool createdConnection = false; try { createdConnection = CreateConnection(ref database); DataRow outputAnalogRecord = database.Connection.RetrieveData(database.AdapterType, string.Format(outputAnalogFormat, outputStreamDeviceAnalogID)).Rows[0]; string labelName = outputAnalogRecord.Field("Label"); outputStreamDeviceID = outputAnalogRecord.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 analogPointTag = database.Connection.ExecuteScalar(string.Format(measurementDetailFormat, deviceName, labelName)).ToNonNullString(); analogSignalReference = database.Connection.ExecuteScalar(string.Format(outputMeasurementDetailFormat, analogPointTag)).ToNonNullString(); } catch (Exception ex) { CommonFunctions.LogException(database, "OutputStreamDeviceAnalog.GetDeleteMeasurementDetails", ex); throw; } finally { if (createdConnection) database?.Dispose(); } } #endregion } }