// // Class: IDatabaseEntity // Current CLR: 4.0.30319.34209 // System: Microsoft Visual Studio 10.0 // Author: dani // Created: 3/2/2015 9:08:47 PM // // Copyright (c) 2015 Informatikbüro Daniel Schick. All rights reserved. using System; using System.Data; using System.Data.SqlClient; using System.Linq; using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; using System.Xml.Serialization; using System.IO; using Newtonsoft.Json; using log4net; using System.ComponentModel; using System.Runtime.Serialization; namespace bsmd.database { [XmlInclude(typeof(Message))] [XmlInclude(typeof(MessageCore))] [XmlInclude(typeof(AGNT))] [DataContract] public abstract class DatabaseEntity : IDatabaseEntity, IMessageParagraph, IEquatable, ICloneable { protected Guid? id; protected string tablename; private Guid instance_id = Guid.NewGuid(); // Comparison id in case entity has not been saved private static readonly ILog _log = LogManager.GetLogger(typeof(DatabaseEntity)); #region delegate / events // Ein etwas hakeliger Mechanismus, damit ein abhängiges Projekt (ReportGenerator) die Werte ersetzen kann ohne dass die ganze Logik // hier in die Basisklasse muss public delegate string ReplaceValFunc(string propertyName, string value); public static event ReplaceValFunc ReportReplacer; #endregion #region enum ValidationBlock public enum ValidationBlock { BLOCK1, BLOCK2 } #endregion #region Properties /// /// Nachrichtentyp der abgeleiteten Meldeklassen /// [JsonIgnore] [Browsable(false)] public Message.NotificationClass MessageNotificationClass { get; set; } [JsonIgnore] [Browsable(false)] public string MessageNotificationClassDisplay { get { return this.MessageNotificationClass.ToString(); } } /// /// Referenz zur eigentlichen Schiffankunft /// [JsonIgnore] [Browsable(false)] public MessageCore MessageCore { get; set; } /// /// gemeinschaftliche Daten /// [JsonIgnore] [Browsable(false)] public Message MessageHeader { get; set; } /// /// SQL Table name to construct queries /// [JsonIgnore] [Browsable(false)] public virtual string Tablename { get { return this.tablename; } } /// /// primary key /// [JsonIgnore] [Browsable(false)] public Guid? Id { get { return this.id; } } /// /// IsNew Flag /// [JsonIgnore] [Browsable(false)] public bool IsNew { get { return !this.id.HasValue; } } /// /// Flag zeigt an ob das Objekt geändert wurde /// [JsonIgnore] [Browsable(false)] public bool IsDirty { get; set; } #endregion #region public funcs /// /// when it's time (during save), create id /// public void CreateId() { this.id = Guid.NewGuid(); } public abstract void PrepareSave(IDbCommand cmd); public virtual void PrepareDelete(IDbCommand cmd) { SqlCommand scmd = cmd as SqlCommand; scmd.CommandText = string.Format("DELETE FROM {0} WHERE Id = @ID", Tablename); scmd.Parameters.AddWithValue("@ID", this.Id); } /// /// Ergebnismenge begrenzen: NULL = kein Limit. Abgeleitete Klassen *können* diesen Parameter berücksichtigen /// [JsonIgnore] public int? ResultLimit { get; set; } public abstract void PrepareLoadCommand(IDbCommand cmd, Message.LoadFilter filter, params object[] criteria); public abstract List LoadList(IDataReader reader); public virtual void PrepareCountCmd(IDbCommand cmd, Message.LoadFilter filter, params object[] criteria) { SqlCommand scmd = cmd as SqlCommand; scmd.CommandText = string.Format("SELECT COUNT(*) FROM {0}", Tablename); } /// /// Überprüft eingegangene Meldeklassen auf Fehlerkonditionen /// /// /// public virtual void Validate(List errors, List violations) { } /// /// Diese Methode sollte eigentlich nie einen Effekt haben und dient nur dazu, dass keine Situation /// auftreten kann in der ein Insert/Update an fehlender Datenvalidierung kracht /// /// public virtual void TruncateFields(List truncated, List fieldNames) { List props = new List(); // add flagged properties to check list props.AddRange(this.GetType().GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(MaxLengthAttribute)))); foreach (PropertyInfo property in props) { object propValue = property.GetValue(this, null); string value = (propValue == null) ? string.Empty : propValue.ToString(); MaxLengthAttribute maxLengthAttribute = Attribute.GetCustomAttribute(property, typeof(MaxLengthAttribute)) as MaxLengthAttribute; if(value.Length > maxLengthAttribute.MaxLength) // truncate situation { string maxLengthValue = value.Substring(0, maxLengthAttribute.MaxLength); truncated.Add(string.Format("[{0} ({1})]: {2}", property.Name, maxLengthAttribute.MaxLength, value)); property.SetValue(this, maxLengthValue); fieldNames.Add(string.Format("{0}.{1}", this.Tablename, property.Name)); } } } /// /// Kann überschrieben werden, wenn abhängig von den Daten (einem Flag) andere Regeln gelten (Bsp. wenn schon eine /// MDH Meldung abgegeben wurde) /// /// public virtual ValidationBlock GetValidationBlock() { return ValidationBlock.BLOCK1; } /// /// Diese Methode überschreibt die Properties dieses Objekts mit den (gleichnamigen) Properties eines /// anderen Objekts. Vorausgesetzt wird, dass die beiden Objekte vom selben Typ sind. Properties, die mit /// "JSONIgnore" dekoriert sind werden dabei übersprungen /// /// Quell-Objekt public virtual void OverwriteWith(DatabaseEntity otherEntity) { try { DatabaseEntity.CopyProperties(this, otherEntity); } catch(Exception ex) { _log.ErrorFormat("Error copying properties: {0}", ex.Message); } } public static void CopyProperties(DatabaseEntity entityTarget, DatabaseEntity entitySource) { foreach (PropertyInfo propertyInfo in entityTarget.GetType().GetProperties()) { bool hasJsonIgnoreAttribute = (propertyInfo.GetCustomAttribute(typeof(JsonIgnoreAttribute)) != null); if (!hasJsonIgnoreAttribute) { PropertyInfo otherProperty = entitySource.GetType().GetProperty(propertyInfo.Name); propertyInfo.SetValue(entityTarget, otherProperty.GetValue(entitySource)); } } DBManager.Instance.Save(entityTarget); } #endregion #region public static funcs /// /// Wenn Positionen manuell (ENI-2) angelegt werden, kann mit dieser Funktion der neue "Identifier" /// festgelegt werden /// /// Vorhandene Position /// Name des neuen Identifiers, der so nicht in der Liste vorkommt public static string GetNewIdentifier(IEnumerable sublist, string prefix=null) { int maxVal = -1; string maxString = null; // das sieht riskanter aus als es ist, diese Funktion wird aber nur für Elemente angewendet, die // ISublistElement implementieren foreach(ISublistElement element in sublist.Cast()) { if (element.Identifier.IsNullOrEmpty()) continue; int elementIdent; // TODO: Beim Parsen auch Text- und allerlei bunte Erweiterungen tolerieren // (z.B. MARPOL-000001) Regex re = new Regex(@"\d+"); Match m = re.Match(element.Identifier); if (m.Success) { elementIdent = Int32.Parse(m.Value); if (elementIdent > maxVal) { maxVal = elementIdent; maxString = element.Identifier.Replace(m.Value, (maxVal + 1).ToString()); } } } if (maxVal == -1) { // Spezialbalkone try { if (prefix != null) return string.Format("{0}1", prefix); } catch { } return 1.ToString(); } return maxString; } public static void ResetIdentifiers(IList sublist) { List tmpList = new List(sublist); sublist.Clear(); foreach(ISublistElement element in tmpList.Cast()) { element.Identifier = DatabaseEntity.GetNewIdentifier(sublist); sublist.Add(element as DatabaseEntity); } } #endregion #region IEquatable implementation public bool Equals(DatabaseEntity other) { if (other == null) return false; if (this.Id.HasValue && other.Id.HasValue && (this.Id.Value != Guid.Empty) && (other.Id.Value != Guid.Empty)) return this.Id == other.Id; return this.GetHashCode() == other.GetHashCode(); } public override bool Equals(object obj) { return this.Equals(obj as DatabaseEntity); } public override int GetHashCode() { if (!this.Id.HasValue || this.Id.Value == Guid.Empty) return this.instance_id.GetHashCode(); return this.Id.GetHashCode(); } #endregion #region IMessageParagraph implementation [JsonIgnore] [Browsable(false)] public virtual string Title { get { string name = this.GetType().Name; return name; } } [JsonIgnore] [Browsable(false)] public virtual string Subtitle { get { return string.Empty; } } [JsonIgnore] [Browsable(false)] public virtual bool ShowChildrenAsTable { get { return false; } } /// /// must be overridden if it must make sense /// [JsonIgnore] [Browsable(false)] public virtual List> MessageText { get { List> result = new List>(); Type objType = this.GetType(); var props = objType.GetProperties().Where( prop => Attribute.IsDefined(prop, typeof(ShowReportAttribute))); foreach (PropertyInfo property in props) { string displayName = property.Name; if(Attribute.IsDefined(property, typeof(ReportDisplayNameAttribute))) { ReportDisplayNameAttribute reportDisplayNameAttribute = Attribute.GetCustomAttribute(property, typeof(ReportDisplayNameAttribute)) as ReportDisplayNameAttribute; displayName = reportDisplayNameAttribute.DisplayName; } result.Add(new KeyValuePair(displayName, this.GetDisplayValue(property))); } return result; } } public virtual string GetDisplayValue(PropertyInfo property) { bool isDouble = property.PropertyType == typeof(double?); bool isDateTime = property.PropertyType == typeof(DateTime?); object propValue = property.GetValue(this, null); if (propValue == null) return ""; string value = propValue.ToString(); if(isDouble) { value = ((double)propValue).ToString("N2"); } else if(isDateTime) { if(Attribute.IsDefined(property, typeof(DateOnlyAttribute))) { value = ((DateTime)propValue).ToShortDateString(); } else { value = ((DateTime)propValue).ToLocalTime().ToString("g"); // perform UTC-LocalTime Conversion (no seconds) } } else { if(ReportReplacer != null) { value = ReportReplacer(property.Name, value); } } return value; } [JsonIgnore] [Browsable(false)] public virtual List ChildParagraphs { get { return null; } } #endregion #region ICloneable implementation public virtual object Clone() { DatabaseEntity entity = this.MemberwiseClone() as DatabaseEntity; entity.id = null; return entity; } #endregion } }