433 lines
15 KiB
C#
433 lines
15 KiB
C#
//
|
|
// 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<DatabaseEntity>, 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
|
|
|
|
/// <summary>
|
|
/// Nachrichtentyp der abgeleiteten Meldeklassen
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
[Browsable(false)]
|
|
public Message.NotificationClass MessageNotificationClass { get; set; }
|
|
|
|
[JsonIgnore]
|
|
[Browsable(false)]
|
|
public string MessageNotificationClassDisplay
|
|
{
|
|
get
|
|
{
|
|
return this.MessageNotificationClass.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Referenz zur eigentlichen Schiffankunft
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
[Browsable(false)]
|
|
public MessageCore MessageCore { get; set; }
|
|
|
|
/// <summary>
|
|
/// gemeinschaftliche Daten
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
[Browsable(false)]
|
|
public Message MessageHeader { get; set; }
|
|
|
|
/// <summary>
|
|
/// SQL Table name to construct queries
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
[Browsable(false)]
|
|
public virtual string Tablename { get { return this.tablename; } }
|
|
|
|
/// <summary>
|
|
/// primary key
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
[Browsable(false)]
|
|
public Guid? Id { get { return this.id; } }
|
|
|
|
/// <summary>
|
|
/// IsNew Flag
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
[Browsable(false)]
|
|
public bool IsNew { get { return !this.id.HasValue; } }
|
|
|
|
/// <summary>
|
|
/// Flag zeigt an ob das Objekt geändert wurde
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
[Browsable(false)]
|
|
public bool IsDirty { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region public funcs
|
|
|
|
/// <summary>
|
|
/// when it's time (during save), create id
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ergebnismenge begrenzen: NULL = kein Limit. Abgeleitete Klassen *können* diesen Parameter berücksichtigen
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public int? ResultLimit { get; set; }
|
|
|
|
public abstract void PrepareLoadCommand(IDbCommand cmd, Message.LoadFilter filter, params object[] criteria);
|
|
|
|
public abstract List<DatabaseEntity> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Überprüft eingegangene Meldeklassen auf Fehlerkonditionen
|
|
/// </summary>
|
|
/// <param name="errors"></param>
|
|
/// <param name="violations"></param>
|
|
public virtual void Validate(List<MessageError> errors, List<MessageViolation> violations)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="truncated"></param>
|
|
public virtual void TruncateFields(List<string> truncated, List<string> fieldNames)
|
|
{
|
|
List<PropertyInfo> props = new List<PropertyInfo>();
|
|
|
|
// 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));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Kann überschrieben werden, wenn abhängig von den Daten (einem Flag) andere Regeln gelten (Bsp. wenn schon eine
|
|
/// MDH Meldung abgegeben wurde)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual ValidationBlock GetValidationBlock()
|
|
{
|
|
return ValidationBlock.BLOCK1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="otherEntity">Quell-Objekt</param>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Wenn Positionen manuell (ENI-2) angelegt werden, kann mit dieser Funktion der neue "Identifier"
|
|
/// festgelegt werden
|
|
/// </summary>
|
|
/// <param name="sublist">Vorhandene Position</param>
|
|
/// <returns>Name des neuen Identifiers, der so nicht in der Liste vorkommt</returns>
|
|
public static string GetNewIdentifier(IEnumerable<DatabaseEntity> 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<ISublistElement>())
|
|
{
|
|
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<DatabaseEntity> sublist)
|
|
{
|
|
List<DatabaseEntity> tmpList = new List<DatabaseEntity>(sublist);
|
|
sublist.Clear();
|
|
foreach(ISublistElement element in tmpList.Cast<ISublistElement>())
|
|
{
|
|
element.Identifier = DatabaseEntity.GetNewIdentifier(sublist);
|
|
sublist.Add(element as DatabaseEntity);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IEquatable<T> 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; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// must be overridden if it must make sense
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
[Browsable(false)]
|
|
public virtual List<KeyValuePair<string, string>> MessageText
|
|
{
|
|
get {
|
|
List<KeyValuePair<string, string>> result = new List<KeyValuePair<string, string>>();
|
|
|
|
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<string, string>(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<IMessageParagraph> ChildParagraphs { get { return null; } }
|
|
|
|
#endregion
|
|
|
|
#region ICloneable implementation
|
|
|
|
public virtual object Clone()
|
|
{
|
|
DatabaseEntity entity = this.MemberwiseClone() as DatabaseEntity;
|
|
entity.id = null;
|
|
return entity;
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|