BaseObject.cs 24 KB

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