BaseObject.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  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 = key.Contains(".")
  232. ? CoreUtils.GetProperty(GetType(), key)
  233. : GetType().GetRuntimeProperty(key);
  234. if(prop != null)
  235. {
  236. if (prop.SetMethod != null)
  237. {
  238. var val = OriginalValues[key];
  239. // Funky 64bit stuff here?
  240. if (prop.PropertyType == typeof(int) && val?.GetType() == typeof(long))
  241. val = Convert.ToInt32(val);
  242. prop.SetValue(this, val);
  243. }
  244. }
  245. else if (UserProperties.ContainsKey(key))
  246. {
  247. UserProperties[key] = value;
  248. }
  249. else
  250. {
  251. Logger.Send(LogType.Error, "", $"'{key}' is neither a runtime property nor custom property of {GetType().Name}");
  252. }
  253. }
  254. catch (Exception e)
  255. {
  256. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  257. }
  258. }
  259. OriginalValues.Clear();
  260. bChanged = false;
  261. foreach (var oo in DatabaseSchema.GetSubObjects(this)) oo.CancelChanges();
  262. SetObserving(bObs);
  263. bApplyingChanges = false;
  264. }
  265. public void CommitChanges()
  266. {
  267. bApplyingChanges = true;
  268. OriginalValues.Clear();
  269. bChanged = false;
  270. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  271. oo.CommitChanges();
  272. bApplyingChanges = false;
  273. }
  274. public string ChangedValues()
  275. {
  276. var result = new List<string>();
  277. var type = GetType();
  278. try
  279. {
  280. foreach (var (key, _) in OriginalValues)
  281. try
  282. {
  283. if (UserProperties.ContainsKey(key))
  284. {
  285. var obj = UserProperties[key];
  286. result.Add(string.Format("[{0} = {1}]", key, obj != null ? obj.ToString() : "null"));
  287. }
  288. else
  289. {
  290. var prop = DatabaseSchema.Property(type, key);// GetType().GetProperty(key);
  291. if (prop is StandardProperty standard && standard.Loggable != null)
  292. {
  293. /*var attribute = //prop.GetCustomAttributes(typeof(LoggablePropertyAttribute), true).FirstOrDefault();
  294. if (attribute != null)
  295. {*/
  296. //var lpa = (LoggablePropertyAttribute)attribute;
  297. var format = standard.Loggable.Format;
  298. var value = standard.Getter()(this);
  299. if (string.IsNullOrEmpty(format))
  300. result.Add($"[{key} = {value}]");
  301. else
  302. result.Add(string.Format("[{0} = {1:" + format + "}]", key, value));
  303. //}
  304. }
  305. }
  306. }
  307. catch (Exception e)
  308. {
  309. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  310. }
  311. }
  312. catch (Exception e)
  313. {
  314. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  315. }
  316. return string.Join(" ", result);
  317. }
  318. #endregion
  319. #region UserProperties
  320. private UserProperties _userproperties;
  321. [DoNotPersist]
  322. public UserProperties UserProperties
  323. {
  324. get
  325. {
  326. CheckUserProperties();
  327. return _userproperties;
  328. }
  329. set
  330. {
  331. _userproperties = value;
  332. CheckUserProperties();
  333. }
  334. }
  335. private static Dictionary<string, object?>? DefaultProperties;
  336. private void CheckUserProperties()
  337. {
  338. if (_userproperties == null)
  339. {
  340. _userproperties = new UserProperties();
  341. if (DefaultProperties == null)
  342. {
  343. DefaultProperties = new Dictionary<string, object?>();
  344. var props = DatabaseSchema.Properties(GetType()).Where(x => x is CustomProperty).ToArray();
  345. foreach (var field in props)
  346. DefaultProperties[field.Name] = DatabaseSchema.DefaultValue(field.PropertyType);
  347. }
  348. _userproperties.LoadFromDictionary(DefaultProperties);
  349. }
  350. }
  351. #endregion
  352. }
  353. public class BaseObjectSnapshot<T>
  354. where T : BaseObject
  355. {
  356. private readonly List<(IProperty, object?)> Values;
  357. private readonly T Object;
  358. public BaseObjectSnapshot(T obj)
  359. {
  360. Values = new List<(IProperty, object?)>();
  361. foreach(var property in DatabaseSchema.Properties(obj.GetType()))
  362. {
  363. Values.Add((property, property.Getter()(obj)));
  364. }
  365. Object = obj;
  366. }
  367. public void ResetObject()
  368. {
  369. Object.CancelChanges();
  370. var bObs = Object.IsObserving();
  371. Object.SetObserving(false);
  372. foreach(var (prop, value) in Values)
  373. {
  374. var oldValue = prop.Getter()(Object);
  375. prop.Setter()(Object, value);
  376. if(BaseObjectExtensions.HasChanged(oldValue, value))
  377. {
  378. var parent = prop.Parent != null ? prop.Parent.Getter()(Object) as BaseObject : Object;
  379. if(parent != null)
  380. {
  381. var localPropName = prop is StandardProperty stdProp ? stdProp.Property.Name : prop.Name;
  382. parent.OriginalValues[localPropName] = oldValue;
  383. }
  384. }
  385. }
  386. Object.SetObserving(bObs);
  387. }
  388. }
  389. public static class BaseObjectExtensions
  390. {
  391. public static bool HasChanged(object? before, object? after)
  392. {
  393. if ((before == null || before.Equals("")) && (after == null || after.Equals("")))
  394. return false;
  395. if (before == null != (after == null))
  396. return true;
  397. if (!before!.GetType().Equals(after!.GetType()))
  398. return true;
  399. if (before is string[] && after is string[])
  400. return !(before as string[]).SequenceEqual(after as string[]);
  401. return !before.Equals(after);
  402. }
  403. public static bool HasColumn<T>(this T sender, string column)
  404. where T : BaseObject
  405. {
  406. return sender.LoadedColumns.Contains(column);
  407. }
  408. public static bool HasColumn<T, TType>(this T sender, Expression<Func<T, TType>> column)
  409. where T : BaseObject
  410. {
  411. return sender.LoadedColumns.Contains(CoreUtils.GetFullPropertyName(column, "."));
  412. }
  413. public static bool HasOriginalValue<T>(this T sender, string propertyname) where T : BaseObject
  414. {
  415. return sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propertyname);
  416. }
  417. public static TType GetOriginalValue<T, TType>(this T sender, string propertyname) where T : BaseObject
  418. {
  419. return sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propertyname)
  420. ? (TType)CoreUtils.ChangeType(sender.OriginalValues[propertyname], typeof(TType))
  421. : default;
  422. }
  423. /// <summary>
  424. /// Get all database values (i.e., non-calculated, local properties) for a given object <paramref name="sender"/>.
  425. /// If <paramref name="all"/> is <see langword="false"/>, only retrieve values which have changed.
  426. /// </summary>
  427. public static Dictionary<string, object?> GetValues<T>(this T sender, bool all) where T : BaseObject
  428. {
  429. var result = new Dictionary<string, object?>();
  430. foreach(var property in DatabaseSchema.Properties(sender.GetType()))
  431. {
  432. if (property.IsDBColumn)
  433. {
  434. var isLocal = !property.HasParentEntityLink()
  435. || (property.Parent?.HasParentEntityLink() != true && property.Name.EndsWith(".ID"));
  436. if (isLocal)
  437. {
  438. if (all)
  439. {
  440. result[property.Name] = property.Getter()(sender);
  441. }
  442. else
  443. {
  444. if(property is StandardProperty stdProp)
  445. {
  446. var parent = property.Parent != null ? property.Parent.Getter()(sender) as BaseObject : sender;
  447. if(parent?.HasOriginalValue(stdProp.Property.Name) == true)
  448. {
  449. result[property.Name] = property.Getter()(sender);
  450. }
  451. }
  452. else if(property is CustomProperty customProp)
  453. {
  454. if (sender.HasOriginalValue(customProp.Name))
  455. {
  456. result[property.Name] = property.Getter()(sender);
  457. }
  458. }
  459. }
  460. }
  461. }
  462. }
  463. return result;
  464. }
  465. public static Dictionary<string, object?> GetOriginaValues<T>(this T sender) where T : BaseObject
  466. {
  467. var result = new Dictionary<string, object?>();
  468. foreach(var property in DatabaseSchema.Properties(sender.GetType()))
  469. {
  470. if (property.IsDBColumn)
  471. {
  472. var isLocal = !property.HasParentEntityLink()
  473. || (property.Parent?.HasParentEntityLink() != true && property.Name.EndsWith(".ID"));
  474. if (isLocal)
  475. {
  476. var parent = property.Parent != null ? property.Parent.Getter()(sender) as BaseObject : sender;
  477. var localPropName = property is StandardProperty stdProp ? stdProp.Property.Name : property.Name;
  478. if (parent != null)
  479. if (parent.OriginalValues.TryGetValue(localPropName, out var value))
  480. result[property.Name] = value;
  481. }
  482. }
  483. }
  484. return result;
  485. }
  486. public static BaseObjectSnapshot<T> TakeSnapshot<T>(this T obj)
  487. where T : BaseObject
  488. {
  489. return new BaseObjectSnapshot<T>(obj);
  490. }
  491. public static List<string> Compare<T>(this T sender, Dictionary<string, object> original) where T : BaseObject
  492. {
  493. var result = new List<string>();
  494. var current = GetValues(sender, true);
  495. foreach (var key in current.Keys)
  496. if (original.ContainsKey(key))
  497. {
  498. if (current[key] == null)
  499. {
  500. if (original[key] != null)
  501. result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key]));
  502. }
  503. else
  504. {
  505. if (!current[key].Equals(original[key]))
  506. result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key]));
  507. }
  508. }
  509. else
  510. {
  511. result.Add(string.Format("[{0}] not present in previous dictionary!", key));
  512. }
  513. return result;
  514. }
  515. public static bool GetValue<T,TType>(this T sender, Expression<Func<T,TType>> property, bool original, out TType result) where T : BaseObject
  516. {
  517. if (sender.HasOriginalValue<T, TType>(property))
  518. {
  519. if (original)
  520. result = sender.GetOriginalValue<T, TType>(property);
  521. else
  522. {
  523. var expr = property.Compile();
  524. result = expr(sender);
  525. }
  526. return true;
  527. }
  528. result = default(TType);
  529. return false;
  530. }
  531. public static void SetOriginalValue<T, TType>(this T sender, string propertyname, TType value) where T : BaseObject
  532. {
  533. sender.OriginalValues[propertyname] = value;
  534. }
  535. public static bool HasOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property) where T : BaseObject
  536. {
  537. //var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  538. String propname = CoreUtils.GetFullPropertyName(property, ".");
  539. return !String.IsNullOrWhiteSpace(propname) && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propname);
  540. }
  541. public static TType GetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property) where T : BaseObject
  542. {
  543. var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  544. return prop != null && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(prop.Name)
  545. ? (TType)CoreUtils.ChangeType(sender.OriginalValues[prop.Name], typeof(TType))
  546. : default;
  547. }
  548. public static TType GetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType defaultValue) where T : BaseObject
  549. {
  550. var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  551. return prop != null && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(prop.Name)
  552. ? (TType)CoreUtils.ChangeType(sender.OriginalValues[prop.Name], typeof(TType))
  553. : defaultValue;
  554. }
  555. public static void SetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType value) where T : BaseObject
  556. {
  557. var prop = ((MemberExpression)property.Body).Member as PropertyInfo;
  558. sender.OriginalValues[prop.Name] = value;
  559. }
  560. }
  561. /// <summary>
  562. /// An <see cref="IProperty"/> is loggable if it has the <see cref="LoggablePropertyAttribute"/> defined on it.<br/>
  563. /// If it is part of an <see cref="IEntityLink"/>, then it is only loggable if the <see cref="IEntityLink"/> property on the parent class
  564. /// also has <see cref="LoggablePropertyAttribute"/>.
  565. /// </summary>
  566. public class LoggablePropertyAttribute : Attribute
  567. {
  568. public string Format { get; set; }
  569. }
  570. }