123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734 |
- using AutoProperties;
- using Newtonsoft.Json;
- using System;
- using System.Collections;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using System.Runtime.Serialization;
- 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);
- }
- public interface IOriginalValues : IEnumerable<KeyValuePair<string, object?>>
- {
- public object? this[string key] { get; set; }
- public bool ContainsKey(string key);
- public void Clear();
- public bool TryGetValue(string key, out object? value);
- public bool TryAdd(string key, object? value);
- public void Remove(string key);
- public object? GetValueOrDefault(string key)
- {
- if(TryGetValue(key, out object? value)) return value;
- return null;
- }
- }
- public class OriginalValues : IOriginalValues
- {
- public OriginalValues()
- {
- }
- public ConcurrentDictionary<string, object?> Dictionary { get; set; } = new ConcurrentDictionary<string, object?>();
- public object? this[string key] { get => Dictionary[key]; set => Dictionary[key] = value; }
- public void Clear()
- {
- Dictionary.Clear();
- }
- public bool ContainsKey(string key) => Dictionary.ContainsKey(key);
- public IEnumerator<KeyValuePair<string, object?>> GetEnumerator()
- {
- return Dictionary.GetEnumerator();
- }
- public bool TryGetValue(string key, out object? value)
- {
- return Dictionary.TryGetValue(key, out value);
- }
- public bool TryAdd(string key, object? value)
- {
- return Dictionary.TryAdd(key, value);
- }
- public void Remove(string key)
- {
- (Dictionary as IDictionary<string, object?>).Remove(key);
- }
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
- }
- public interface ILoadedColumns : IEnumerable<string>
- {
- public bool Add(string key);
- public bool Contains(string key);
- }
- public class LoadedColumns : ILoadedColumns
- {
- private HashSet<string> Columns = new HashSet<string>();
- public bool Add(string key)
- {
- return Columns.Add(key);
- }
- public bool Contains(string key)
- {
- return Columns.Contains(key);
- }
- public IEnumerator<string> GetEnumerator()
- {
- return Columns.GetEnumerator();
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return Columns.GetEnumerator();
- }
- }
- /// <summary>
- /// Observable object with INotifyPropertyChanged implemented
- /// </summary>
- public abstract class BaseObject : INotifyPropertyChanged, IBaseObject
- {
- public BaseObject()
- {
- SetObserving(false);
- Init();
- SetObserving(true);
- }
- internal bool _disabledInterceptor;
- protected T InitializeField<T>(ref T? field, [CallerMemberName] string name = "")
- where T : BaseObject, ISubObject, new()
- {
- if(field is null)
- {
- var value = new T();
- value.SetLinkedParent(this);
- value.SetLinkedPath(name);
- value.SetObserving(_observing);
- field = value;
- }
- return field;
- }
- [GetInterceptor]
- protected T GetValue<T>(Type propertyType, ref T field, string name)
- {
- if (_disabledInterceptor) return field;
- if(field is null && propertyType.HasInterface<ISubObject>() && !propertyType.IsAbstract)
- {
- var value = Activator.CreateInstance<T>();
- var subObj = (value as ISubObject)!;
- subObj.SetLinkedParent(this);
- subObj.SetLinkedPath(name);
- if(subObj is BaseObject obj)
- {
- obj.SetObserving(_observing);
- }
- field = value;
- }
- return field;
- }
- [SetInterceptor]
- protected void SetValue<T>(ref T field, T newValue)
- {
- field = newValue;
- }
- [OnDeserializing]
- internal void OnDeserializingMethod(StreamingContext context)
- {
- if (_observing)
- SetObserving(false);
- }
- [OnDeserialized]
- internal void OnDeserializedMethod(StreamingContext context)
- {
- if (!_observing)
- SetObserving(true);
- }
- protected virtual void Init()
- {
- LoadedColumns = CreateLoadedColumns();
-
- 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 = false;
- public bool IsObserving()
- {
- return GlobalObserving && _observing;
- }
- public void SetObserving(bool active)
- {
- bApplyingChanges = true;
- _observing = active;
- _disabledInterceptor = true;
- foreach (var oo in DatabaseSchema.GetSubObjects(this))
- oo.SetObserving(active);
- _disabledInterceptor = false;
- bApplyingChanges = false;
- }
- protected virtual void DoPropertyChanged(string name, object? before, object? after)
- {
- }
- public event PropertyChangedEventHandler? PropertyChanged;
- private bool bApplyingChanges;
- private bool bChanged;
- private IOriginalValues? _originalValues;
- [DoNotPersist]
- [DoNotSerialize]
- public IOriginalValues OriginalValueList
- {
- get
- {
- _originalValues ??= CreateOriginalValues();
- return _originalValues;
- }
- }
- [DoNotPersist]
- public ConcurrentDictionary<string, object?>? OriginalValues
- {
- get
- {
- if(OriginalValueList is OriginalValues v)
- {
- return v.Dictionary;
- }
- else
- {
- return null;
- }
- }
- set
- {
- if(value != null && OriginalValueList is OriginalValues v)
- {
- v.Dictionary = value;
- }
- }
- }
- [DoNotPersist]
- [DoNotSerialize]
- [JsonIgnore]
- public ILoadedColumns 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)
- {
-
- }
- bApplyingChanges = false;
- }
- }
- // This function is *only* meant to be called by EnclosedEntity and EntityLink
- internal void CascadePropertyChanged(string name, object? before, object? after)
- {
- SetChanged(name, before, after);
- }
- private bool QueryChanged()
- {
- if (OriginalValueList.Any())
- return true;
- _disabledInterceptor = true;
- foreach (var oo in DatabaseSchema.GetSubObjects(this))
- if (oo.IsChanged())
- {
- _disabledInterceptor = false;
- return true;
- }
- _disabledInterceptor = false;
- return false;
- }
- public void OnPropertyChanged(string name, object? before, object? after)
- {
- if (!IsObserving())
- return;
- if (name.Equals("IsChanged") || name.Equals("Observing") || name.Equals("OriginalValues"))
- return;
- LoadedColumns.Add(name);
- if (!BaseObjectExtensions.HasChanged(before, after))
- return;
- if (!OriginalValueList.ContainsKey(name))
- OriginalValueList[name] = before;
- SetChanged(name, before, after);
- }
- protected virtual IOriginalValues CreateOriginalValues()
- {
- return new OriginalValues();
- }
- protected virtual ILoadedColumns CreateLoadedColumns()
- {
- return new LoadedColumns();
- }
- public bool IsChanged()
- {
- return IsObserving() ? QueryChanged() : bChanged;
- }
- public void CancelChanges()
- {
- bApplyingChanges = true;
- var bObs = IsObserving();
- SetObserving(false);
- foreach (var (key, value) in OriginalValueList)
- {
- try
- {
- var prop = DatabaseSchema.Property(GetType(), key);
- if(prop != null)
- {
- prop.Setter()(this, value);
- }
- else
- {
- Logger.Send(LogType.Error, "", $"'{key}' is not a property of {GetType().Name}");
- }
- }
- catch (Exception e)
- {
- Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
- }
- }
- OriginalValueList.Clear();
- bChanged = false;
- _disabledInterceptor = true;
- foreach (var oo in DatabaseSchema.GetSubObjects(this))
- {
- oo.CancelChanges();
- }
- _disabledInterceptor = false;
- SetObserving(bObs);
- bApplyingChanges = false;
- }
- public void CommitChanges()
- {
- bApplyingChanges = true;
- OriginalValueList.Clear();
- bChanged = false;
- _disabledInterceptor = true;
- foreach (var oo in DatabaseSchema.GetSubObjects(this))
- oo.CommitChanges();
- _disabledInterceptor = false;
- bApplyingChanges = false;
- }
- public string ChangedValues()
- {
- var result = new List<string>();
- var type = GetType();
- try
- {
- foreach (var (key, _) in OriginalValueList)
- 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;
- private static readonly Dictionary<Type, Dictionary<string, object?>> DefaultProperties = new Dictionary<Type, Dictionary<string, object?>>();
- [DoNotPersist]
- public UserProperties UserProperties
- {
- get
- {
- if (_userproperties == null)
- {
- _userproperties = new UserProperties();
- var type = GetType();
- if (!DefaultProperties.TryGetValue(type, out var defaultProps))
- {
- defaultProps = new Dictionary<string, object?>();
- var props = DatabaseSchema.Properties(type).Where(x => x is CustomProperty);
- foreach (var field in props)
- defaultProps[field.Name] = DatabaseSchema.DefaultValue(field.PropertyType);
- DefaultProperties[type] = defaultProps;
- }
- _userproperties.LoadFromDictionary(defaultProps);
- _userproperties.OnPropertyChanged += (o, n, b, a) =>
- {
- if (IsObserving())
- OnPropertyChanged(n, b, a);
- };
- }
- return _userproperties;
- }
- }
- #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))
- {
- Object.OriginalValueList[prop.Name] = oldValue;
- }
- }
- Object.SetObserving(bObs);
- }
- }
- public static class BaseObjectExtensions
- {
- public static T Clone<T>(this T obj) where T : BaseObject, new()
- {
- var newObj = new T();
- obj._disabledInterceptor = true;
- foreach(var property in DatabaseSchema.Properties(obj.GetType()))
- {
- if (property.Parent != null && property.Parent.NullSafeGetter()(obj) is null) continue;
- property.Setter()(newObj, property.Getter()(obj));
- }
- obj._disabledInterceptor = false;
- return newObj;
- }
- public static BaseObject Clone(BaseObject obj)
- {
- var newObj = (Activator.CreateInstance(obj.GetType()) as BaseObject)!;
- obj._disabledInterceptor = true;
- foreach(var property in DatabaseSchema.Properties(obj.GetType()))
- {
- if (property.Parent != null && property.Parent.NullSafeGetter()(obj) is null) continue;
- property.Setter()(newObj, property.Getter()(obj));
- }
- obj._disabledInterceptor = false;
- return newObj;
- }
- 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.OriginalValueList != null && sender.OriginalValueList.ContainsKey(propertyname);
- }
- public static TType GetOriginalValue<T, TType>(this T sender, string propertyname) where T : BaseObject
- {
- return sender.OriginalValueList != null && sender.OriginalValueList.ContainsKey(propertyname)
- ? (TType)CoreUtils.ChangeType(sender.OriginalValueList[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 && (all || sender.HasOriginalValue(property.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 && sender.OriginalValueList.TryGetValue(property.Name, 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.OriginalValueList[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.OriginalValueList != null && sender.OriginalValueList.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.OriginalValueList != null && sender.OriginalValueList.ContainsKey(prop.Name)
- ? (TType)CoreUtils.ChangeType(sender.OriginalValueList[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.OriginalValueList != null && sender.OriginalValueList.ContainsKey(prop.Name)
- ? (TType)CoreUtils.ChangeType(sender.OriginalValueList[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.OriginalValueList[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; }
- }
- }
|