123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Reflection;
- 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);
- 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();
- UserProperties = new UserProperties();
- //UserProperties.ParentType = this.GetType();
- UserProperties.OnPropertyChanged += (o, n, b, a) =>
- {
- if (IsObserving())
- OnPropertyChanged(n, b, a);
- };
- DatabaseSchema.InitializeObject(this);
- }
- #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;
- //UserProperties.ParentType = this.GetType();
- _observing = active;
- foreach (var oo in CoreUtils.GetChildren<IBaseObject>(this))
- oo.SetObserving(active);
- /*UserProperties.OnPropertyChanged += (o, n, b, a) =>
- {
- if (_observing)
- OnPropertyChanged(n, b, a);
- };*/
- 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; }
- protected virtual void SetChanged(string name, object before, object after)
- {
- bChanged = true;
- if (!bApplyingChanges)
- {
- bApplyingChanges = true;
- DoPropertyChanged(name, before, after);
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
- bApplyingChanges = false;
- }
- }
- private bool QueryChanged()
- {
- CheckOriginalValues();
- if (OriginalValues.Count > 0)
- return true;
- foreach (var oo in CoreUtils.GetChildren<IBaseObject>(this))
- if (oo.IsChanged())
- return true;
- return false;
- }
- private 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 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;
- if (!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 = 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 CoreUtils.GetChildren<IBaseObject>(this)) oo.CancelChanges();
- SetObserving(bObs);
- bApplyingChanges = false;
- }
- public void CommitChanges()
- {
- bApplyingChanges = true;
- CheckOriginalValues();
- OriginalValues.Clear();
- bChanged = false;
- foreach (var oo in CoreUtils.GetChildren<IBaseObject>(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;
- 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 static class BaseObjectExtensions
- {
- public static bool HasOriginalValue<T>(this T sender, string propertyname) where T : BaseObject
- {
- return sender.OriginalValues != null ? sender.OriginalValues.ContainsKey(propertyname) : false;
- }
- 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;
- }
- public static Dictionary<string, object> GetValues<T>(this T sender, bool all) where T : BaseObject
- {
- var result = new Dictionary<string, object>();
- LoadValues(sender, result, "", all, false);
- return result;
- }
- public static Dictionary<string, object> GetOriginaValues<T>(this T sender, bool all) where T : BaseObject
- {
- var result = new Dictionary<string, object>();
- LoadValues(sender, result, "", all, true);
- return result;
- }
- private static void LoadValues(BaseObject sender, Dictionary<string, object> values, string prefix, bool all, bool original)
- {
- try
- {
- var props = sender.GetType().GetProperties().Where(x =>
- x.GetCustomAttribute<DoNotSerialize>() == null
- && x.GetCustomAttribute<DoNotPersist>() == null
- && x.GetCustomAttribute<AggregateAttribute>() == null
- && x.GetCustomAttribute<FormulaAttribute>() == null
- && x.GetCustomAttribute<ConditionAttribute>() == null
- && x.CanWrite);
- foreach (var prop in props)
- if (prop.PropertyType.GetInterfaces().Contains(typeof(IEnclosedEntity)))
- {
- var child = prop.GetValue(sender) as BaseObject;
- if (child != null)
- LoadValues(child, values, string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name, all, original);
- }
- else if (prop.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)))
- {
- var child = prop.GetValue(sender) as BaseObject;
- if (child != null)
- {
- if (all)
- {
- values[string.IsNullOrWhiteSpace(prefix) ? prop.Name + ".ID" : prefix + "." + prop.Name + ".ID"] =
- original
- ? child.OriginalValues.ContainsKey("ID") ? child.OriginalValues["ID"] : Guid.Empty
- : CoreUtils.GetPropertyValue(child, "ID");
- }
- else
- {
- if (child.HasOriginalValue("ID"))
- values[string.IsNullOrWhiteSpace(prefix) ? prop.Name + ".ID" : prefix + "." + prop.Name + ".ID"] =
- original
- ? child.OriginalValues.ContainsKey("ID") ? child.OriginalValues["ID"] : Guid.Empty
- : CoreUtils.GetPropertyValue(child, "ID");
- }
- }
- }
- else if (prop.PropertyType.GetInterfaces().Contains(typeof(IBaseObject)))
- {
- var child = prop.GetValue(sender) as IBaseObject;
- if (child != null)
- {
- if (all)
- {
- values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] = child;
- }
- else
- {
- if (child.IsChanged())
- values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] = child;
- }
- }
- }
- else
- {
- if (all)
- {
- values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] =
- original
- ? sender.OriginalValues.ContainsKey(prop.Name) ? sender.OriginalValues[prop.Name] : prop.PropertyType.GetDefault()
- : prop.GetValue(sender);
- }
- else
- {
- if (sender.HasOriginalValue(prop.Name))
- values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] =
- original
- ? sender.OriginalValues.ContainsKey(prop.Name)
- ? sender.OriginalValues[prop.Name]
- : prop.PropertyType.GetDefault()
- : prop.GetValue(sender);
- }
- }
- var iprops = DatabaseSchema.Properties(sender.GetType()).Where(x => x is CustomProperty);
- foreach (var iprop in iprops)
- if (all || sender.HasOriginalValue(iprop.Name))
- values[string.IsNullOrWhiteSpace(prefix) ? iprop.Name : prefix + "." + iprop.Name] =
- original
- ? sender.OriginalValues.ContainsKey(iprop.Name)
- ? sender.OriginalValues[iprop.Name]
- : iprop.PropertyType.GetDefault()
- : sender.UserProperties[iprop.Name];
- }
- catch (Exception e)
- {
- Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
- }
- }
-
- 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;
- return prop != null && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(prop.Name);
- }
- 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 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; }
- }
- }
|