123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602 |
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using System.Runtime.Serialization;
- //using PropertyChanged;
- namespace InABox.Core
- {
- public class DoNotSerialize : Attribute
- {
- }
- public interface IBaseObject
- {
- public bool IsChanged();
- public void CancelChanges();
- public void CommitChanges();
- public bool IsObserving();
- public void SetObserving(bool active);
- }
- /// <summary>
- /// Observable object with INotifyPropertyChanged implemented
- /// </summary>
- public abstract class BaseObject : INotifyPropertyChanged, IBaseObject
- {
- public BaseObject()
- {
- SetObserving(false);
- DatabaseSchema.InitializeSubObjects(this);
- Init();
- SetObserving(true);
- }
- [OnDeserializing]
- internal void OnDeserializingMethod(StreamingContext context)
- {
- if (_observing)
- SetObserving(false);
- }
- [OnDeserialized]
- internal void OnDeserializedMethod(StreamingContext context)
- {
- if (!_observing)
- SetObserving(true);
- }
- protected virtual void Init()
- {
- CheckOriginalValues();
- LoadedColumns = new HashSet<string>();
- UserProperties = new UserProperties();
- //UserProperties.ParentType = this.GetType();
- DatabaseSchema.InitializeObject(this);
- UserProperties.OnPropertyChanged += (o, n, b, a) =>
- {
- if (IsObserving())
- OnPropertyChanged(n, b, a);
- };
-
- CheckSequence();
- }
- private void CheckSequence()
- {
- if (this is ISequenceable seq && seq.Sequence <= 0)
- {
- seq.Sequence = CoreUtils.GenerateSequence();
- }
- }
- #region Observing Flags
- public static bool GlobalObserving = true;
- private bool _observing = true;
- public bool IsObserving()
- {
- return GlobalObserving && _observing;
- }
- public void SetObserving(bool active)
- {
- bApplyingChanges = true;
- _observing = active;
- foreach (var oo in DatabaseSchema.GetSubObjects(this))
- oo.SetObserving(active);
- bApplyingChanges = false;
- }
- protected virtual void DoPropertyChanged(string name, object? before, object? after)
- {
- }
- public event PropertyChangedEventHandler? PropertyChanged;
- private bool bApplyingChanges;
- private bool bChanged;
- [DoNotPersist]
- public ConcurrentDictionary<string, object?> OriginalValues { get; set; }
- [DoNotPersist]
- [DoNotSerialize]
- public HashSet<string> LoadedColumns { get; set; }
- protected virtual void SetChanged(string name, object? before, object? after)
- {
- bChanged = true;
- if (!bApplyingChanges)
- {
- try
- {
- bApplyingChanges = true;
- DoPropertyChanged(name, before, after);
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
- }
- catch (Exception e)
- {
-
- }
- bApplyingChanges = false;
- }
- }
- private bool QueryChanged()
- {
- CheckOriginalValues();
- if (OriginalValues.Count > 0)
- return true;
- foreach (var oo in DatabaseSchema.GetSubObjects(this))
- if (oo.IsChanged())
- return true;
- return false;
- }
- public void OnPropertyChanged(string name, object? before, object? after)
- {
- if (!IsObserving())
- return;
- if (name.Equals("IsChanged"))
- return;
- if (name.Equals("Observing"))
- return;
- if (name.Equals("OriginalValues"))
- return;
- LoadedColumns.Add(name);
- if (!BaseObjectExtensions.HasChanged(before, after))
- return;
- CheckOriginalValues();
- if (!OriginalValues.ContainsKey(name))
- OriginalValues[name] = before;
- SetChanged(name, before, after);
- }
- private void CheckOriginalValues()
- {
- if (OriginalValues == null)
- {
- var bObserving = _observing;
- _observing = false;
- OriginalValues = new ConcurrentDictionary<string, object?>();
- _observing = bObserving;
- }
- }
- public bool IsChanged()
- {
- return IsObserving() ? QueryChanged() : bChanged;
- }
- public void CancelChanges()
- {
- bApplyingChanges = true;
- var bObs = IsObserving();
- SetObserving(false);
- CheckOriginalValues();
- foreach (var key in OriginalValues.Keys.ToArray())
- {
- try
- {
- var prop = key.Contains(".")
- ? CoreUtils.GetProperty(GetType(),key)
- : GetType().GetRuntimeProperty(key);
- if(prop != null)
- {
- if (prop.SetMethod != null)
- {
- var val = OriginalValues[key];
- // Funky 64bit stuff here?
- if (prop.PropertyType == typeof(int) && val?.GetType() == typeof(long))
- val = Convert.ToInt32(val);
- prop.SetValue(this, val);
- }
- }
- else if (UserProperties.ContainsKey(key))
- {
- UserProperties[key] = OriginalValues[key];
- }
- else
- {
- Logger.Send(LogType.Error, "", $"'{key}' is neither a runtime property nor custom property of {GetType().Name}");
- }
- }
- catch (Exception e)
- {
- Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
- }
- }
- OriginalValues.Clear();
- bChanged = false;
- foreach (var oo in DatabaseSchema.GetSubObjects(this)) oo.CancelChanges();
- SetObserving(bObs);
- bApplyingChanges = false;
- }
- public void CommitChanges()
- {
- bApplyingChanges = true;
- CheckOriginalValues();
- OriginalValues.Clear();
- bChanged = false;
- foreach (var oo in DatabaseSchema.GetSubObjects(this))
- oo.CommitChanges();
- bApplyingChanges = false;
- }
- public string ChangedValues()
- {
- var result = new List<string>();
- var type = GetType();
- try
- {
- CheckOriginalValues();
- foreach (var key in OriginalValues.Keys)
- try
- {
- if (UserProperties.ContainsKey(key))
- {
- var obj = UserProperties[key];
- result.Add(string.Format("[{0} = {1}]", key, obj != null ? obj.ToString() : "null"));
- }
- else
- {
- var prop = DatabaseSchema.Property(type, key);// GetType().GetProperty(key);
- if (prop is StandardProperty standard && standard.Loggable != null)
- {
- /*var attribute = //prop.GetCustomAttributes(typeof(LoggablePropertyAttribute), true).FirstOrDefault();
- if (attribute != null)
- {*/
- //var lpa = (LoggablePropertyAttribute)attribute;
- var format = standard.Loggable.Format;
- var value = standard.Getter()(this);
- if (string.IsNullOrEmpty(format))
- result.Add($"[{key} = {value}]");
- else
- result.Add(string.Format("[{0} = {1:" + format + "}]", key, value));
- //}
- }
- }
- }
- catch (Exception e)
- {
- Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
- }
- }
- catch (Exception e)
- {
- Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
- }
- return string.Join(" ", result);
- }
- #endregion
- #region UserProperties
- private UserProperties _userproperties;
- [DoNotPersist]
- public UserProperties UserProperties
- {
- get
- {
- CheckUserProperties();
- return _userproperties;
- }
- set
- {
- _userproperties = value;
- CheckUserProperties();
- }
- }
- private static Dictionary<string, object?>? DefaultProperties;
- private void CheckUserProperties()
- {
- if (_userproperties == null)
- {
- _userproperties = new UserProperties();
- if (DefaultProperties == null)
- {
- DefaultProperties = new Dictionary<string, object?>();
- var props = DatabaseSchema.Properties(GetType()).Where(x => x is CustomProperty).ToArray();
- foreach (var field in props)
- DefaultProperties[field.Name] = DatabaseSchema.DefaultValue(field.PropertyType);
- }
- _userproperties.LoadFromDictionary(DefaultProperties);
- }
- }
- #endregion
- }
- public class BaseObjectSnapshot<T>
- where T : BaseObject
- {
- private readonly List<(IProperty, object?)> Values;
- private readonly T Object;
- public BaseObjectSnapshot(T obj)
- {
- Values = new List<(IProperty, object?)>();
- foreach(var property in DatabaseSchema.Properties(obj.GetType()))
- {
- Values.Add((property, property.Getter()(obj)));
- }
- Object = obj;
- }
- public void ResetObject()
- {
- Object.CancelChanges();
- var bObs = Object.IsObserving();
- Object.SetObserving(false);
- foreach(var (prop, value) in Values)
- {
- var oldValue = prop.Getter()(Object);
- prop.Setter()(Object, value);
- if(BaseObjectExtensions.HasChanged(oldValue, value))
- {
- var parent = prop.Parent != null ? prop.Parent.Getter()(Object) as BaseObject : Object;
- if(parent != null)
- {
- var localPropName = prop is StandardProperty stdProp ? stdProp.Property.Name : prop.Name;
- parent.OriginalValues[localPropName] = oldValue;
- }
- }
- }
- Object.SetObserving(bObs);
- }
- }
- public static class BaseObjectExtensions
- {
- public static bool HasChanged(object? before, object? after)
- {
- if ((before == null || before.Equals("")) && (after == null || after.Equals("")))
- return false;
- if (before == null != (after == null))
- return true;
- if (!before!.GetType().Equals(after!.GetType()))
- return true;
- if (before is string[] && after is string[])
- return !(before as string[]).SequenceEqual(after as string[]);
- return !before.Equals(after);
- }
- public static bool HasColumn<T>(this T sender, string column)
- where T : BaseObject
- {
- return sender.LoadedColumns.Contains(column);
- }
- public static bool HasColumn<T, TType>(this T sender, Expression<Func<T, TType>> column)
- where T : BaseObject
- {
- return sender.LoadedColumns.Contains(CoreUtils.GetFullPropertyName(column, "."));
- }
- public static bool HasOriginalValue<T>(this T sender, string propertyname) where T : BaseObject
- {
- return sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propertyname);
- }
- public static TType GetOriginalValue<T, TType>(this T sender, string propertyname) where T : BaseObject
- {
- return sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propertyname)
- ? (TType)CoreUtils.ChangeType(sender.OriginalValues[propertyname], typeof(TType))
- : default;
- }
- /// <summary>
- /// Get all database values (i.e., non-calculated, local properties) for a given object <paramref name="sender"/>.
- /// If <paramref name="all"/> is <see langword="false"/>, only retrieve values which have changed.
- /// </summary>
- public static Dictionary<string, object?> GetValues<T>(this T sender, bool all) where T : BaseObject
- {
- var result = new Dictionary<string, object?>();
- foreach(var property in DatabaseSchema.Properties(sender.GetType()))
- {
- if (property.IsDBColumn)
- {
- var isLocal = !property.HasParentEntityLink()
- || (property.Parent?.HasParentEntityLink() != true && property.Name.EndsWith(".ID"));
- if (isLocal)
- {
- if (all)
- {
- result[property.Name] = property.Getter()(sender);
- }
- else
- {
- if(property is StandardProperty stdProp)
- {
- var parent = property.Parent != null ? property.Parent.Getter()(sender) as BaseObject : sender;
- if(parent?.HasOriginalValue(stdProp.Property.Name) == true)
- {
- result[property.Name] = property.Getter()(sender);
- }
- }
- else if(property is CustomProperty customProp)
- {
- if (sender.HasOriginalValue(customProp.Name))
- {
- result[property.Name] = property.Getter()(sender);
- }
- }
- }
- }
- }
- }
- return result;
- }
- public static Dictionary<string, object?> GetOriginaValues<T>(this T sender) where T : BaseObject
- {
- var result = new Dictionary<string, object?>();
- foreach(var property in DatabaseSchema.Properties(sender.GetType()))
- {
- if (property.IsDBColumn)
- {
- var isLocal = !property.HasParentEntityLink()
- || (property.Parent?.HasParentEntityLink() != true && property.Name.EndsWith(".ID"));
- if (isLocal)
- {
- var parent = property.Parent != null ? property.Parent.Getter()(sender) as BaseObject : sender;
- var localPropName = property is StandardProperty stdProp ? stdProp.Property.Name : property.Name;
- if (parent != null)
- if (parent.OriginalValues.TryGetValue(localPropName, out var value))
- result[property.Name] = value;
- }
- }
- }
- return result;
- }
- public static BaseObjectSnapshot<T> TakeSnapshot<T>(this T obj)
- where T : BaseObject
- {
- return new BaseObjectSnapshot<T>(obj);
- }
- public static List<string> Compare<T>(this T sender, Dictionary<string, object> original) where T : BaseObject
- {
- var result = new List<string>();
- var current = GetValues(sender, true);
- foreach (var key in current.Keys)
- if (original.ContainsKey(key))
- {
- if (current[key] == null)
- {
- if (original[key] != null)
- result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key]));
- }
- else
- {
- if (!current[key].Equals(original[key]))
- result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key]));
- }
- }
- else
- {
- result.Add(string.Format("[{0}] not present in previous dictionary!", key));
- }
- return result;
- }
- public static bool GetValue<T,TType>(this T sender, Expression<Func<T,TType>> property, bool original, out TType result) where T : BaseObject
- {
- if (sender.HasOriginalValue<T, TType>(property))
- {
- if (original)
- result = sender.GetOriginalValue<T, TType>(property);
- else
- {
- var expr = property.Compile();
- result = expr(sender);
- }
- return true;
- }
- result = default(TType);
- return false;
- }
-
- public static void SetOriginalValue<T, TType>(this T sender, string propertyname, TType value) where T : BaseObject
- {
- sender.OriginalValues[propertyname] = value;
- }
- public static bool HasOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property) where T : BaseObject
- {
- //var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
- String propname = CoreUtils.GetFullPropertyName(property, ".");
- return !String.IsNullOrWhiteSpace(propname) && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propname);
- }
- public static TType GetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property) where T : BaseObject
- {
- var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
- return prop != null && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(prop.Name)
- ? (TType)CoreUtils.ChangeType(sender.OriginalValues[prop.Name], typeof(TType))
- : default;
- }
-
- public static TType GetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType defaultValue) where T : BaseObject
- {
- var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
- return prop != null && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(prop.Name)
- ? (TType)CoreUtils.ChangeType(sender.OriginalValues[prop.Name], typeof(TType))
- : defaultValue;
- }
- public static void SetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType value) where T : BaseObject
- {
- var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
- sender.OriginalValues[prop.Name] = value;
- }
- }
- /// <summary>
- /// An <see cref="IProperty"/> is loggable if it has the <see cref="LoggablePropertyAttribute"/> defined on it.<br/>
- /// If it is part of an <see cref="IEntityLink"/>, then it is only loggable if the <see cref="IEntityLink"/> property on the parent class
- /// also has <see cref="LoggablePropertyAttribute"/>.
- /// </summary>
- public class LoggablePropertyAttribute : Attribute
- {
- public string Format { get; set; }
- }
- }
|