//****************************************************************************************************** // DataModelBase.cs - Gbtc // // Copyright © 2012, 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: // ---------------------------------------------------------------------------------------------------- // 03/25/2011 - Mehulbhai P Thakkar // Generated original version of source code. // 04/08/2011 - J. Ritchie Carroll // Modified class to use reflection to load default values and use entity property attributes. // 12/20/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using GSF.Collections; using GSF.Data; using GSF.Reflection; namespace GSF.TimeSeries.UI { /// /// Represents an abstract base class for all entity model objects. /// public abstract class DataModelBase : IDataModel { #region [ Members ] // Constants /// /// The default timeout duration used for executing SQL statements. /// public const int DefaultTimeout = DataExtensions.DefaultTimeoutDuration; // Events /// /// Raised when a property on this object has a new value. /// public event PropertyChangedEventHandler PropertyChanged; // Fields private readonly ConcurrentDictionary m_propertyErrors; private BindingFlags m_memberAccessBindingFlags; private bool m_requireEntityPropertyAttribute; private bool m_lastIsValidState; private static bool m_messageFlag; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// protected DataModelBase() : this(false) { } /// /// Creates a new instance of the class. /// /// /// Assigns flag that determines if is required /// to exist before a property is included as a field in the data model. /// protected DataModelBase(bool requireEntityPropertyAttribute) : this(true, requireEntityPropertyAttribute) { } /// /// Creates a new instance of the class. /// /// /// Determines whether to load default values into the properties using reflection. /// /// /// Assigns flag that determines if is required /// to exist before a property is included as a field in the data model. /// protected DataModelBase(bool loadDefaults, bool requireEntityPropertyAttribute) { Action propertyAction = property => { object defaultValue = DeriveDefaultValue(property.Name, property.GetValue(this, null)); if (!Common.IsDefaultValue(defaultValue)) property.SetValue(this, defaultValue, null); OnPropertyChanged(property.Name); }; m_propertyErrors = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); m_memberAccessBindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; m_requireEntityPropertyAttribute = requireEntityPropertyAttribute; if (loadDefaults) { // Load all default values for properties ExecuteActionForProperties(propertyAction, property => (object)property.GetGetMethod() != null && (object)property.GetSetMethod() != null); } } #endregion #region [ Properties ] /// /// Indicates if the values associated with this object are valid. /// [EntityProperty(false)] public bool IsValid { get { // If any of the properties have errors, values are not valid m_lastIsValidState = m_propertyErrors.All(kvPair => string.IsNullOrWhiteSpace(kvPair.Value)); return m_lastIsValidState; } } /// /// Gets an error message indicating what is wrong with this object. /// /// /// An error message indicating what is wrong with this object. The default is an empty string (""). /// [EntityProperty(false)] public string Error { get { // Cumulate any properties with errors using line feed as delimiter return m_propertyErrors.Where(kvPair => !string.IsNullOrWhiteSpace(kvPair.Value)).ToDelimitedString(Environment.NewLine); } } /// /// Gets the error message for the property with the given name. /// /// The name of the property whose error message to get. /// The error message for the property. The default is an empty string (""). public string this[string propertyName] { get { string error; if (!m_propertyErrors.TryGetValue(propertyName, out error)) error = ""; return error; } } /// /// Gets or sets flag that determines if is /// required to exist before a property is included as a field in the data model; /// defaults to false. /// [Browsable(false), EntityProperty(false)] public bool RequireEntityPropertyAttribute { get { return m_requireEntityPropertyAttribute; } set { m_requireEntityPropertyAttribute = value; } } /// /// Gets or sets used to access properties of derived class. /// /// /// Value defaults to | | . /// [EditorBrowsable(EditorBrowsableState.Advanced), EntityProperty(false)] protected virtual BindingFlags MemberAccessBindingFlags { get { return m_memberAccessBindingFlags; } set { m_memberAccessBindingFlags = value; } } /// /// gets or sets when the current 's Message Flag /// public static bool MessageFlag { get { return m_messageFlag; } set { m_messageFlag = value; } } #endregion #region [ Methods ] /// /// Raises the event. /// /// Property name that has changed. protected virtual void OnPropertyChanged(string propertyName) { if (string.Compare(propertyName, "IsValid", true) != 0) ValidateProperty(propertyName); if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); if (m_lastIsValidState != IsValid) OnPropertyChanged("IsValid"); } /// /// Validates current instance properties using Data Annotations. /// /// This instance property to validate. /// Relevant error string on validation failure or on validation success. protected virtual void ValidateProperty(string propertyName) { if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("Invalid property name", propertyName); string error = ""; object value = GetType().GetProperty(propertyName).GetValue(this, null); List results = new List(1); ValidationContext validationContext = new ValidationContext(this, null, null) { MemberName = propertyName }; if (!Validator.TryValidateProperty(value, validationContext, results)) { ValidationResult validationResult = results.First(); error = validationResult.ErrorMessage; } m_propertyErrors[propertyName] = error; } /// /// Gets the default value specified by , if any, applied to the specified property. /// /// Property name. /// Default value applied to specified property; or null if one does not exist. /// cannot be null or empty. public object GetDefaultValue(string propertyName) { if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("name cannot be null or empty"); return GetAttributeValue(propertyName, null, attribute => attribute.Value); } /// /// Attempts to get best default value for given member. /// /// Property name. /// Current property value. /// /// If is equal to its default(type) value, then any value derived from will be used instead. /// /// The object that is the best default value. [EditorBrowsable(EditorBrowsableState.Advanced)] protected object DeriveDefaultValue(string propertyName, object value) { // See if value is equal to its default value (i.e., uninitialized) if (Common.IsDefaultValue(value)) { // See if any value exists in a DefaultValueAttribute object defaultValue = GetDefaultValue(propertyName); if (defaultValue != null) return defaultValue; } return value; } /// /// Executes specified action over all public derived class properties. /// /// Action to execute for all properties. /// Filter used to select which properties to execute the action. [EditorBrowsable(EditorBrowsableState.Advanced)] protected void ExecuteActionForProperties(Action propertyAction, Func propertyFilter) { PropertyInfo[] members = GetType() .GetProperties(m_memberAccessBindingFlags) .Where(propertyFilter) .ToArray(); ExecuteActionForMembers(property => { // Make sure only non-indexer properties are used for settings if (property.GetIndexParameters().Length == 0) propertyAction(property); }, members); } // Execute specified action over specified members private void ExecuteActionForMembers(Action memberAction, T[] members) where T : MemberInfo { EntityPropertyAttribute attribute; // Execute action for each member foreach (T member in members) { // See if serialize setting attribute exists if (member.TryGetAttribute(out attribute)) { // Found entity property attribute, perform action if include is true if (attribute.Include) memberAction(member); } else if (!m_requireEntityPropertyAttribute) { // Didn't find entity property attribute and it's not required, so we perform action (i.e., assume include) memberAction(member); } } } /// /// Attempts to find specified attribute and return specified value. /// /// Type of to find. /// Type of value attribute delegate returns. /// Name of property to search for attribute. /// Default value to return if attribute doesn't exist. /// Function delegate used to return desired attribute property. /// Specified attribute value if it exists; otherwise default value. [EditorBrowsable(EditorBrowsableState.Advanced)] protected TValue GetAttributeValue(string propertyName, TValue defaultValue, Func attributeValue) where TAttribute : Attribute { TAttribute attribute; // See if property exists with specified name PropertyInfo property = this.GetType().GetProperty(propertyName, m_memberAccessBindingFlags); if (property != null) { // See if attribute exists on property if (property.TryGetAttribute(out attribute)) { // Return value as specified by delegate return attributeValue(attribute); } // Attribute wasn't found, return default value return defaultValue; } // Return default value return defaultValue; } #endregion #region [ Static ] // Static Methods /// /// Creates and instance of if it is null. /// /// Reference parameter representing object. /// Boolean, true if new instance of is created otherwise false. public static bool CreateConnection(ref AdoDataConnection database) { if (database == null) { database = new AdoDataConnection(CommonFunctions.DefaultSettingsCategory); return true; //try //{ // database = new AdoDataConnection(CommonFunctions.DefaultSettingsCategory); // return true; //} //catch (Exception ex) //{ // throw; // if (!MessageFlag) // { // m_messageFlag = true; // MessageBox.Show("ERROR: " + ex.Message, "Create Database Connection", MessageBoxButton.OK); // m_messageFlag = false; // } // return false; //} } return false; } #endregion } }