BaseObject.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Concurrent;
  4. using System.Collections.Generic;
  5. using System.ComponentModel;
  6. using System.Data;
  7. using System.Linq;
  8. using System.Linq.Expressions;
  9. using System.Reflection;
  10. using System.Runtime.CompilerServices;
  11. using System.Runtime.Serialization;
  12. //using PropertyChanged;
  13. namespace InABox.Core
  14. {
  15. public class DoNotSerialize : Attribute
  16. {
  17. }
  18. public interface IBaseObject
  19. {
  20. public bool IsChanged();
  21. public void CancelChanges();
  22. public void CommitChanges();
  23. public bool IsObserving();
  24. public void SetObserving(bool active);
  25. }
  26. public interface IOriginalValues : IEnumerable<KeyValuePair<string, object?>>
  27. {
  28. public object? this[string key] { get; set; }
  29. public bool ContainsKey(string key);
  30. public void Clear();
  31. public bool TryGetValue(string key, out object? value);
  32. public bool TryAdd(string key, object? value);
  33. public void Remove(string key);
  34. public object? GetValueOrDefault(string key)
  35. {
  36. if(TryGetValue(key, out object? value)) return value;
  37. return null;
  38. }
  39. }
  40. public class OriginalValues : IOriginalValues
  41. {
  42. private readonly ConcurrentDictionary<string, object?> Dictionary = new ConcurrentDictionary<string, object?>();
  43. public object? this[string key] { get => Dictionary[key]; set => Dictionary[key] = value; }
  44. public void Clear()
  45. {
  46. Dictionary.Clear();
  47. }
  48. public bool ContainsKey(string key) => Dictionary.ContainsKey(key);
  49. public IEnumerator<KeyValuePair<string, object?>> GetEnumerator()
  50. {
  51. return Dictionary.GetEnumerator();
  52. }
  53. public bool TryGetValue(string key, out object? value)
  54. {
  55. return Dictionary.TryGetValue(key, out value);
  56. }
  57. public bool TryAdd(string key, object? value)
  58. {
  59. return Dictionary.TryAdd(key, value);
  60. }
  61. public void Remove(string key)
  62. {
  63. (Dictionary as IDictionary<string, object?>).Remove(key);
  64. }
  65. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  66. }
  67. public interface ILoadedColumns
  68. {
  69. public bool Add(string key);
  70. public bool Contains(string key);
  71. }
  72. public class LoadedColumns : ILoadedColumns
  73. {
  74. private HashSet<string> Columns = new HashSet<string>();
  75. public bool Add(string key)
  76. {
  77. return Columns.Add(key);
  78. }
  79. public bool Contains(string key)
  80. {
  81. return Columns.Contains(key);
  82. }
  83. }
  84. /// <summary>
  85. /// Observable object with INotifyPropertyChanged implemented
  86. /// </summary>
  87. public abstract class BaseObject : INotifyPropertyChanged, IBaseObject
  88. {
  89. public BaseObject()
  90. {
  91. SetObserving(false);
  92. DatabaseSchema.InitializeSubObjects(this);
  93. Init();
  94. SetObserving(true);
  95. }
  96. [OnDeserializing]
  97. internal void OnDeserializingMethod(StreamingContext context)
  98. {
  99. if (_observing)
  100. SetObserving(false);
  101. }
  102. [OnDeserialized]
  103. internal void OnDeserializedMethod(StreamingContext context)
  104. {
  105. if (!_observing)
  106. SetObserving(true);
  107. }
  108. protected virtual void Init()
  109. {
  110. LoadedColumns = CreateLoadedColumns();
  111. UserProperties = new UserProperties();
  112. //UserProperties.ParentType = this.GetType();
  113. DatabaseSchema.InitializeObject(this);
  114. UserProperties.OnPropertyChanged += (o, n, b, a) =>
  115. {
  116. if (IsObserving())
  117. OnPropertyChanged(n, b, a);
  118. };
  119. CheckSequence();
  120. }
  121. private void CheckSequence()
  122. {
  123. if (this is ISequenceable seq && seq.Sequence <= 0)
  124. {
  125. seq.Sequence = CoreUtils.GenerateSequence();
  126. }
  127. }
  128. #region Observing Flags
  129. public static bool GlobalObserving = true;
  130. private bool _observing = false;
  131. public bool IsObserving()
  132. {
  133. return GlobalObserving && _observing;
  134. }
  135. public void SetObserving(bool active)
  136. {
  137. bApplyingChanges = true;
  138. _observing = active;
  139. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  140. oo.SetObserving(active);
  141. bApplyingChanges = false;
  142. }
  143. protected virtual void DoPropertyChanged(string name, object? before, object? after)
  144. {
  145. }
  146. public event PropertyChangedEventHandler? PropertyChanged;
  147. private bool bApplyingChanges;
  148. private bool bChanged;
  149. [DoNotPersist]
  150. private IOriginalValues _originalValues;
  151. public IOriginalValues OriginalValues
  152. {
  153. get
  154. {
  155. _originalValues ??= CreateOriginalValues();
  156. return _originalValues;
  157. }
  158. }
  159. [DoNotPersist]
  160. [DoNotSerialize]
  161. public ILoadedColumns LoadedColumns { get; set; }
  162. protected virtual void SetChanged(string name, object? before, object? after)
  163. {
  164. bChanged = true;
  165. if (!bApplyingChanges)
  166. {
  167. try
  168. {
  169. bApplyingChanges = true;
  170. DoPropertyChanged(name, before, after);
  171. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  172. }
  173. catch (Exception e)
  174. {
  175. }
  176. bApplyingChanges = false;
  177. }
  178. }
  179. // This function is *only* meant to be called by EnclosedEntity and EntityLink
  180. internal void CascadePropertyChanged(string name, object? before, object? after)
  181. {
  182. SetChanged(name, before, after);
  183. }
  184. private bool QueryChanged()
  185. {
  186. if (OriginalValues.Any())
  187. return true;
  188. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  189. if (oo.IsChanged())
  190. return true;
  191. return false;
  192. }
  193. public void OnPropertyChanged(string name, object? before, object? after)
  194. {
  195. if (!IsObserving())
  196. return;
  197. if (name.Equals("IsChanged"))
  198. return;
  199. if (name.Equals("Observing"))
  200. return;
  201. if (name.Equals("OriginalValues"))
  202. return;
  203. LoadedColumns.Add(name);
  204. if (!BaseObjectExtensions.HasChanged(before, after))
  205. return;
  206. if (!OriginalValues.ContainsKey(name))
  207. OriginalValues[name] = before;
  208. SetChanged(name, before, after);
  209. }
  210. protected virtual IOriginalValues CreateOriginalValues()
  211. {
  212. return new OriginalValues();
  213. }
  214. protected virtual ILoadedColumns CreateLoadedColumns()
  215. {
  216. return new LoadedColumns();
  217. }
  218. public bool IsChanged()
  219. {
  220. return IsObserving() ? QueryChanged() : bChanged;
  221. }
  222. public void CancelChanges()
  223. {
  224. bApplyingChanges = true;
  225. var bObs = IsObserving();
  226. SetObserving(false);
  227. foreach (var (key, value) in OriginalValues)
  228. {
  229. try
  230. {
  231. var prop = DatabaseSchema.Property(GetType(), key);
  232. if(prop != null)
  233. {
  234. prop.Setter()(this, value);
  235. }
  236. else
  237. {
  238. Logger.Send(LogType.Error, "", $"'{key}' is not a property of {GetType().Name}");
  239. }
  240. }
  241. catch (Exception e)
  242. {
  243. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  244. }
  245. }
  246. OriginalValues.Clear();
  247. bChanged = false;
  248. foreach (var oo in DatabaseSchema.GetSubObjects(this)) oo.CancelChanges();
  249. SetObserving(bObs);
  250. bApplyingChanges = false;
  251. }
  252. public void CommitChanges()
  253. {
  254. bApplyingChanges = true;
  255. OriginalValues.Clear();
  256. bChanged = false;
  257. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  258. oo.CommitChanges();
  259. bApplyingChanges = false;
  260. }
  261. public string ChangedValues()
  262. {
  263. var result = new List<string>();
  264. var type = GetType();
  265. try
  266. {
  267. foreach (var (key, _) in OriginalValues)
  268. try
  269. {
  270. if (UserProperties.ContainsKey(key))
  271. {
  272. var obj = UserProperties[key];
  273. result.Add(string.Format("[{0} = {1}]", key, obj != null ? obj.ToString() : "null"));
  274. }
  275. else
  276. {
  277. var prop = DatabaseSchema.Property(type, key);// GetType().GetProperty(key);
  278. if (prop is StandardProperty standard && standard.Loggable != null)
  279. {
  280. /*var attribute = //prop.GetCustomAttributes(typeof(LoggablePropertyAttribute), true).FirstOrDefault();
  281. if (attribute != null)
  282. {*/
  283. //var lpa = (LoggablePropertyAttribute)attribute;
  284. var format = standard.Loggable.Format;
  285. var value = standard.Getter()(this);
  286. if (string.IsNullOrEmpty(format))
  287. result.Add($"[{key} = {value}]");
  288. else
  289. result.Add(string.Format("[{0} = {1:" + format + "}]", key, value));
  290. //}
  291. }
  292. }
  293. }
  294. catch (Exception e)
  295. {
  296. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  297. }
  298. }
  299. catch (Exception e)
  300. {
  301. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  302. }
  303. return string.Join(" ", result);
  304. }
  305. #endregion
  306. #region UserProperties
  307. private UserProperties _userproperties;
  308. [DoNotPersist]
  309. public UserProperties UserProperties
  310. {
  311. get
  312. {
  313. CheckUserProperties();
  314. return _userproperties;
  315. }
  316. set
  317. {
  318. _userproperties = value;
  319. CheckUserProperties();
  320. }
  321. }
  322. private static Dictionary<string, object?>? DefaultProperties;
  323. private void CheckUserProperties()
  324. {
  325. if (_userproperties == null)
  326. {
  327. _userproperties = new UserProperties();
  328. if (DefaultProperties == null)
  329. {
  330. DefaultProperties = new Dictionary<string, object?>();
  331. var props = DatabaseSchema.Properties(GetType()).Where(x => x is CustomProperty).ToArray();
  332. foreach (var field in props)
  333. DefaultProperties[field.Name] = DatabaseSchema.DefaultValue(field.PropertyType);
  334. }
  335. _userproperties.LoadFromDictionary(DefaultProperties);
  336. }
  337. }
  338. #endregion
  339. }
  340. public class BaseObjectSnapshot<T>
  341. where T : BaseObject
  342. {
  343. private readonly List<(IProperty, object?)> Values;
  344. private readonly T Object;
  345. public BaseObjectSnapshot(T obj)
  346. {
  347. Values = new List<(IProperty, object?)>();
  348. foreach(var property in DatabaseSchema.Properties(obj.GetType()))
  349. {
  350. Values.Add((property, property.Getter()(obj)));
  351. }
  352. Object = obj;
  353. }
  354. public void ResetObject()
  355. {
  356. Object.CancelChanges();
  357. var bObs = Object.IsObserving();
  358. Object.SetObserving(false);
  359. foreach(var (prop, value) in Values)
  360. {
  361. var oldValue = prop.Getter()(Object);
  362. prop.Setter()(Object, value);
  363. if(BaseObjectExtensions.HasChanged(oldValue, value))
  364. {
  365. Object.OriginalValues[prop.Name] = oldValue;
  366. }
  367. }
  368. Object.SetObserving(bObs);
  369. }
  370. }
  371. public static class BaseObjectExtensions
  372. {
  373. public static bool HasChanged(object? before, object? after)
  374. {
  375. if ((before == null || before.Equals("")) && (after == null || after.Equals("")))
  376. return false;
  377. if (before == null != (after == null))
  378. return true;
  379. if (!before!.GetType().Equals(after!.GetType()))
  380. return true;
  381. if (before is string[] && after is string[])
  382. return !(before as string[]).SequenceEqual(after as string[]);
  383. return !before.Equals(after);
  384. }
  385. public static bool HasColumn<T>(this T sender, string column)
  386. where T : BaseObject
  387. {
  388. return sender.LoadedColumns.Contains(column);
  389. }
  390. public static bool HasColumn<T, TType>(this T sender, Expression<Func<T, TType>> column)
  391. where T : BaseObject
  392. {
  393. return sender.LoadedColumns.Contains(CoreUtils.GetFullPropertyName(column, "."));
  394. }
  395. public static bool HasOriginalValue<T>(this T sender, string propertyname) where T : BaseObject
  396. {
  397. return sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propertyname);
  398. }
  399. public static TType GetOriginalValue<T, TType>(this T sender, string propertyname) where T : BaseObject
  400. {
  401. return sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propertyname)
  402. ? (TType)CoreUtils.ChangeType(sender.OriginalValues[propertyname], typeof(TType))
  403. : default;
  404. }
  405. /// <summary>
  406. /// Get all database values (i.e., non-calculated, local properties) for a given object <paramref name="sender"/>.
  407. /// If <paramref name="all"/> is <see langword="false"/>, only retrieve values which have changed.
  408. /// </summary>
  409. public static Dictionary<string, object?> GetValues<T>(this T sender, bool all) where T : BaseObject
  410. {
  411. var result = new Dictionary<string, object?>();
  412. foreach(var property in DatabaseSchema.Properties(sender.GetType()))
  413. {
  414. if (property.IsDBColumn)
  415. {
  416. var isLocal = !property.HasParentEntityLink()
  417. || (property.Parent?.HasParentEntityLink() != true && property.Name.EndsWith(".ID"));
  418. if (isLocal && (all || sender.HasOriginalValue(property.Name)))
  419. {
  420. result[property.Name] = property.Getter()(sender);
  421. }
  422. }
  423. }
  424. return result;
  425. }
  426. public static Dictionary<string, object?> GetOriginaValues<T>(this T sender) where T : BaseObject
  427. {
  428. var result = new Dictionary<string, object?>();
  429. foreach(var property in DatabaseSchema.Properties(sender.GetType()))
  430. {
  431. if (property.IsDBColumn)
  432. {
  433. var isLocal = !property.HasParentEntityLink()
  434. || (property.Parent?.HasParentEntityLink() != true && property.Name.EndsWith(".ID"));
  435. if (isLocal && sender.OriginalValues.TryGetValue(property.Name, out var value))
  436. {
  437. result[property.Name] = value;
  438. }
  439. }
  440. }
  441. return result;
  442. }
  443. public static BaseObjectSnapshot<T> TakeSnapshot<T>(this T obj)
  444. where T : BaseObject
  445. {
  446. return new BaseObjectSnapshot<T>(obj);
  447. }
  448. public static List<string> Compare<T>(this T sender, Dictionary<string, object> original) where T : BaseObject
  449. {
  450. var result = new List<string>();
  451. var current = GetValues(sender, true);
  452. foreach (var key in current.Keys)
  453. if (original.ContainsKey(key))
  454. {
  455. if (current[key] == null)
  456. {
  457. if (original[key] != null)
  458. result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key]));
  459. }
  460. else
  461. {
  462. if (!current[key].Equals(original[key]))
  463. result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key]));
  464. }
  465. }
  466. else
  467. {
  468. result.Add(string.Format("[{0}] not present in previous dictionary!", key));
  469. }
  470. return result;
  471. }
  472. public static bool GetValue<T,TType>(this T sender, Expression<Func<T,TType>> property, bool original, out TType result) where T : BaseObject
  473. {
  474. if (sender.HasOriginalValue<T, TType>(property))
  475. {
  476. if (original)
  477. result = sender.GetOriginalValue<T, TType>(property);
  478. else
  479. {
  480. var expr = property.Compile();
  481. result = expr(sender);
  482. }
  483. return true;
  484. }
  485. result = default(TType);
  486. return false;
  487. }
  488. public static void SetOriginalValue<T, TType>(this T sender, string propertyname, TType value) where T : BaseObject
  489. {
  490. sender.OriginalValues[propertyname] = value;
  491. }
  492. public static bool HasOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property) where T : BaseObject
  493. {
  494. //var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  495. String propname = CoreUtils.GetFullPropertyName(property, ".");
  496. return !String.IsNullOrWhiteSpace(propname) && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propname);
  497. }
  498. public static TType GetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property) where T : BaseObject
  499. {
  500. var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  501. return prop != null && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(prop.Name)
  502. ? (TType)CoreUtils.ChangeType(sender.OriginalValues[prop.Name], typeof(TType))
  503. : default;
  504. }
  505. public static TType GetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType defaultValue) where T : BaseObject
  506. {
  507. var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  508. return prop != null && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(prop.Name)
  509. ? (TType)CoreUtils.ChangeType(sender.OriginalValues[prop.Name], typeof(TType))
  510. : defaultValue;
  511. }
  512. public static void SetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType value) where T : BaseObject
  513. {
  514. var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  515. sender.OriginalValues[prop.Name] = value;
  516. }
  517. }
  518. /// <summary>
  519. /// An <see cref="IProperty"/> is loggable if it has the <see cref="LoggablePropertyAttribute"/> defined on it.<br/>
  520. /// If it is part of an <see cref="IEntityLink"/>, then it is only loggable if the <see cref="IEntityLink"/> property on the parent class
  521. /// also has <see cref="LoggablePropertyAttribute"/>.
  522. /// </summary>
  523. public class LoggablePropertyAttribute : Attribute
  524. {
  525. public string Format { get; set; }
  526. }
  527. }