BaseObject.cs 24 KB

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