BaseObject.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  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.Diagnostics.CodeAnalysis;
  8. using System.Linq;
  9. using System.Linq.Expressions;
  10. using System.Reflection;
  11. using System.Runtime.CompilerServices;
  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, IJsonOnDeserializing, IJsonOnDeserialized, IJsonOnSerialized, IJsonOnSerializing
  99. {
  100. public BaseObject()
  101. {
  102. SetObserving(false);
  103. Init();
  104. SetObserving(true);
  105. }
  106. internal bool _disabledInterceptor;
  107. private bool _deserialising;
  108. protected T InitializeField<T>(ref T? field, [CallerMemberName] string name = "")
  109. where T : BaseObject, ISubObject, new()
  110. {
  111. if(field is null)
  112. {
  113. var value = new T();
  114. value.SetLinkedParent(this);
  115. value.SetLinkedPath(name);
  116. value.SetObserving(_observing);
  117. field = value;
  118. }
  119. return field;
  120. }
  121. [GetInterceptor]
  122. protected T GetValue<T>(Type propertyType, ref T field, string name)
  123. {
  124. if (_disabledInterceptor || _deserialising) return field;
  125. if(field is null && propertyType.HasInterface<ISubObject>() && !propertyType.IsAbstract)
  126. {
  127. var value = Activator.CreateInstance<T>();
  128. var subObj = (value as ISubObject)!;
  129. subObj.SetLinkedParent(this);
  130. subObj.SetLinkedPath(name);
  131. if(subObj is BaseObject obj)
  132. {
  133. obj.SetObserving(_observing);
  134. }
  135. field = value;
  136. }
  137. return field;
  138. }
  139. [SetInterceptor]
  140. protected void SetValue<T>(Type propertyType, ref T field, string name, T newValue)
  141. {
  142. if (_disabledInterceptor || !_deserialising)
  143. {
  144. field = newValue;
  145. return;
  146. }
  147. if(field is null && newValue is ISubObject subObj && !propertyType.IsAbstract)
  148. {
  149. subObj.SetLinkedParent(this);
  150. subObj.SetLinkedPath(name);
  151. if(subObj is BaseObject obj)
  152. {
  153. obj.SetObserving(_observing);
  154. }
  155. field = newValue;
  156. }
  157. else
  158. {
  159. field = newValue;
  160. }
  161. }
  162. public void OnSerializing()
  163. {
  164. _disabledInterceptor = true;
  165. }
  166. public void OnSerialized()
  167. {
  168. _disabledInterceptor = false;
  169. }
  170. public void OnDeserializing()
  171. {
  172. _deserialising = true;
  173. if (_observing)
  174. SetObserving(false);
  175. }
  176. public void OnDeserialized()
  177. {
  178. _deserialising = false;
  179. if (!_observing)
  180. SetObserving(true);
  181. }
  182. protected virtual void Init()
  183. {
  184. LoadedColumns = CreateLoadedColumns();
  185. CheckSequence();
  186. }
  187. private void CheckSequence()
  188. {
  189. if (this is ISequenceable seq && seq.Sequence <= 0)
  190. {
  191. seq.Sequence = CoreUtils.GenerateSequence();
  192. }
  193. }
  194. #region Observing Flags
  195. public static bool GlobalObserving = true;
  196. internal bool _observing = false;
  197. internal bool _observingOnlyCascades = false;
  198. public bool IsObserving()
  199. {
  200. return GlobalObserving && _observing;
  201. }
  202. void IBaseObject.SetObserving(bool active) => SetObserving(active);
  203. public void SetObserving(bool active, bool? onlyCascades = null)
  204. {
  205. bApplyingChanges = true;
  206. _observing = active;
  207. _observingOnlyCascades = onlyCascades ?? _observingOnlyCascades;
  208. _disabledInterceptor = true;
  209. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  210. oo.SetObserving(active, onlyCascades: onlyCascades);
  211. _disabledInterceptor = false;
  212. bApplyingChanges = false;
  213. }
  214. protected virtual void DoPropertyChanged(string name, object? before, object? after)
  215. {
  216. }
  217. protected virtual void DoPropertyCascaded(string name, object? before, object? after)
  218. {
  219. }
  220. /// <summary>
  221. /// Triggered for any property change, but is not triggered if that property change triggers any other property changes.
  222. /// </summary>
  223. /// <remarks>
  224. /// See also <seealso cref="PropertyChanged"/>.
  225. /// </remarks>
  226. public event PropertyChangedEventHandler? PropertyEdited;
  227. /// <summary>
  228. /// Triggered for any property change, including property changes triggered by other properties changing.
  229. /// </summary>
  230. /// <remarks>
  231. /// Should be used for data binding and stuff.
  232. /// </remarks>
  233. public event PropertyChangedEventHandler? PropertyChanged;
  234. private bool bApplyingChanges;
  235. private bool bChanged;
  236. private IOriginalValues? _originalValues;
  237. [DoNotPersist]
  238. [DoNotSerialize]
  239. public IOriginalValues OriginalValueList
  240. {
  241. get
  242. {
  243. _originalValues ??= CreateOriginalValues();
  244. return _originalValues;
  245. }
  246. }
  247. [DoNotPersist]
  248. public ConcurrentDictionary<string, object?>? OriginalValues
  249. {
  250. get
  251. {
  252. if(OriginalValueList is OriginalValues v)
  253. {
  254. return v.Dictionary;
  255. }
  256. else
  257. {
  258. return null;
  259. }
  260. }
  261. set
  262. {
  263. if(value != null && OriginalValueList is OriginalValues v)
  264. {
  265. v.Dictionary = value;
  266. }
  267. }
  268. }
  269. [DoNotPersist]
  270. [DoNotSerialize]
  271. [JsonIgnore]
  272. public ILoadedColumns LoadedColumns { get; set; }
  273. protected virtual void SetChanged(string name, object? before, object? after)
  274. {
  275. bChanged = true;
  276. if (!bApplyingChanges)
  277. {
  278. try
  279. {
  280. bApplyingChanges = true;
  281. DoPropertyChanged(name, before, after);
  282. PropertyEdited?.Invoke(this, new PropertyChangedEventArgs(name));
  283. }
  284. catch (Exception e)
  285. {
  286. CoreUtils.LogException("", e);
  287. }
  288. bApplyingChanges = false;
  289. }
  290. try
  291. {
  292. DoPropertyCascaded(name, before, after);
  293. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  294. }
  295. catch (Exception e)
  296. {
  297. CoreUtils.LogException("", e);
  298. }
  299. }
  300. // This function is *only* meant to be called by EnclosedEntity and EntityLink
  301. internal void CascadePropertyChanged(string name, object? before, object? after)
  302. {
  303. SetChanged(name, before, after);
  304. }
  305. private bool QueryChanged()
  306. {
  307. if (OriginalValueList.Any())
  308. return true;
  309. _disabledInterceptor = true;
  310. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  311. if (oo.IsChanged())
  312. {
  313. _disabledInterceptor = false;
  314. return true;
  315. }
  316. _disabledInterceptor = false;
  317. return false;
  318. }
  319. public void OnPropertyChanged(string name, object? before, object? after)
  320. {
  321. if (!IsObserving())
  322. {
  323. if (_observingOnlyCascades && BaseObjectExtensions.HasChanged(before, after))
  324. {
  325. try
  326. {
  327. DoPropertyCascaded(name, before, after);
  328. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  329. }
  330. catch (Exception e)
  331. {
  332. CoreUtils.LogException("", e);
  333. }
  334. }
  335. return;
  336. }
  337. if (name.Equals("IsChanged") || name.Equals("Observing") || name.Equals("OriginalValues"))
  338. return;
  339. LoadedColumns.Add(name);
  340. if (!BaseObjectExtensions.HasChanged(before, after))
  341. return;
  342. if (!OriginalValueList.ContainsKey(name))
  343. OriginalValueList[name] = before;
  344. SetChanged(name, before, after);
  345. }
  346. protected virtual IOriginalValues CreateOriginalValues()
  347. {
  348. return new OriginalValues();
  349. }
  350. protected virtual ILoadedColumns CreateLoadedColumns()
  351. {
  352. return new LoadedColumns();
  353. }
  354. public bool IsChanged()
  355. {
  356. return IsObserving() ? QueryChanged() : bChanged;
  357. }
  358. public void CancelChanges()
  359. {
  360. bApplyingChanges = true;
  361. var bObs = _observing;
  362. var onlyCascades = _observingOnlyCascades;
  363. SetObserving(false, onlyCascades: true);
  364. foreach (var (key, value) in OriginalValueList)
  365. {
  366. try
  367. {
  368. var prop = DatabaseSchema.Property(GetType(), key);
  369. if(prop != null)
  370. {
  371. prop.Setter()(this, value);
  372. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key));
  373. }
  374. else
  375. {
  376. Logger.Send(LogType.Error, "", $"'{key}' is not a property of {GetType().Name}");
  377. }
  378. }
  379. catch (Exception e)
  380. {
  381. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  382. }
  383. }
  384. OriginalValueList.Clear();
  385. bChanged = false;
  386. _disabledInterceptor = true;
  387. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  388. {
  389. oo.CancelChanges();
  390. }
  391. _disabledInterceptor = false;
  392. SetObserving(bObs, onlyCascades: onlyCascades);
  393. bApplyingChanges = false;
  394. }
  395. public void CommitChanges()
  396. {
  397. bApplyingChanges = true;
  398. OriginalValueList.Clear();
  399. bChanged = false;
  400. _disabledInterceptor = true;
  401. foreach (var oo in DatabaseSchema.GetSubObjects(this))
  402. oo.CommitChanges();
  403. _disabledInterceptor = false;
  404. bApplyingChanges = false;
  405. }
  406. public string ChangedValues()
  407. {
  408. var result = new List<string>();
  409. var type = GetType();
  410. try
  411. {
  412. foreach (var (key, _) in OriginalValueList)
  413. try
  414. {
  415. if (UserProperties.ContainsKey(key))
  416. {
  417. var obj = UserProperties[key];
  418. result.Add(string.Format("[{0} = {1}]", key, obj != null ? obj.ToString() : "null"));
  419. }
  420. else
  421. {
  422. var prop = DatabaseSchema.Property(type, key);// GetType().GetProperty(key);
  423. if (prop is StandardProperty standard && standard.Loggable != null)
  424. {
  425. /*var attribute = //prop.GetCustomAttributes(typeof(LoggablePropertyAttribute), true).FirstOrDefault();
  426. if (attribute != null)
  427. {*/
  428. //var lpa = (LoggablePropertyAttribute)attribute;
  429. var format = standard.Loggable.Format;
  430. var value = standard.Getter()(this);
  431. if (string.IsNullOrEmpty(format))
  432. result.Add($"[{key} = {value}]");
  433. else
  434. result.Add(string.Format("[{0} = {1:" + format + "}]", key, value));
  435. //}
  436. }
  437. }
  438. }
  439. catch (Exception e)
  440. {
  441. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  442. }
  443. }
  444. catch (Exception e)
  445. {
  446. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  447. }
  448. return string.Join(" ", result);
  449. }
  450. #endregion
  451. #region UserProperties
  452. private UserProperties? _userproperties;
  453. private static readonly Dictionary<Type, Dictionary<string, object?>> DefaultProperties = new Dictionary<Type, Dictionary<string, object?>>();
  454. [DoNotPersist]
  455. public UserProperties UserProperties
  456. {
  457. get
  458. {
  459. if (_userproperties == null)
  460. {
  461. _userproperties = new UserProperties();
  462. var type = GetType();
  463. if (!DefaultProperties.TryGetValue(type, out var defaultProps))
  464. {
  465. defaultProps = new Dictionary<string, object?>();
  466. var props = DatabaseSchema.Properties(type).Where(x => x is CustomProperty);
  467. foreach (var field in props)
  468. defaultProps[field.Name] = DatabaseSchema.DefaultValue(field.PropertyType);
  469. DefaultProperties[type] = defaultProps;
  470. }
  471. _userproperties.LoadFromDictionary(defaultProps);
  472. _userproperties.OnPropertyChanged += (o, n, b, a) =>
  473. {
  474. if (IsObserving())
  475. OnPropertyChanged(n, b, a);
  476. };
  477. }
  478. return _userproperties;
  479. }
  480. }
  481. #endregion
  482. }
  483. public class BaseObjectSnapshot<T>
  484. where T : BaseObject
  485. {
  486. private readonly List<(IProperty, object?)> Values;
  487. private readonly T Object;
  488. public BaseObjectSnapshot(T obj)
  489. {
  490. Values = new List<(IProperty, object?)>();
  491. foreach(var property in DatabaseSchema.Properties(obj.GetType()))
  492. {
  493. Values.Add((property, property.Getter()(obj)));
  494. }
  495. Object = obj;
  496. }
  497. public void ResetObject()
  498. {
  499. Object.CancelChanges();
  500. var bObs = Object._observing;
  501. var onlyCascades = Object._observingOnlyCascades;
  502. Object.SetObserving(false, true);
  503. foreach(var (prop, value) in Values)
  504. {
  505. var oldValue = prop.Getter()(Object);
  506. prop.Setter()(Object, value);
  507. if(BaseObjectExtensions.HasChanged(oldValue, value))
  508. {
  509. Object.OriginalValueList[prop.Name] = oldValue;
  510. }
  511. }
  512. Object.SetObserving(bObs, onlyCascades: onlyCascades);
  513. }
  514. }
  515. public static class BaseObjectExtensions
  516. {
  517. public static T Clone<T>(this T obj) where T : BaseObject, new()
  518. {
  519. var newObj = new T();
  520. obj._disabledInterceptor = true;
  521. foreach(var property in DatabaseSchema.Properties(obj.GetType()))
  522. {
  523. if (property.Parent != null && property.Parent.NullSafeGetter()(obj) is null) continue;
  524. property.Setter()(newObj, property.Getter()(obj));
  525. }
  526. obj._disabledInterceptor = false;
  527. return newObj;
  528. }
  529. public static BaseObject Clone(BaseObject obj)
  530. {
  531. var newObj = (Activator.CreateInstance(obj.GetType()) as BaseObject)!;
  532. obj._disabledInterceptor = true;
  533. foreach(var property in DatabaseSchema.Properties(obj.GetType()))
  534. {
  535. if (property.Parent != null && property.Parent.NullSafeGetter()(obj) is null) continue;
  536. property.Setter()(newObj, property.Getter()(obj));
  537. }
  538. obj._disabledInterceptor = false;
  539. return newObj;
  540. }
  541. public static bool HasChanged(object? before, object? after)
  542. {
  543. if ((before == null || before.Equals("")) && (after == null || after.Equals("")))
  544. return false;
  545. if (before == null != (after == null))
  546. return true;
  547. if (!before!.GetType().Equals(after!.GetType()))
  548. return true;
  549. if (before is string[] && after is string[])
  550. return !(before as string[]).SequenceEqual(after as string[]);
  551. return !before.Equals(after);
  552. }
  553. public static bool HasColumn<T>(this T sender, string column)
  554. where T : BaseObject
  555. {
  556. return sender.LoadedColumns.Contains(column);
  557. }
  558. public static bool HasColumn<T, TType>(this T sender, Expression<Func<T, TType>> column)
  559. where T : BaseObject
  560. {
  561. return sender.LoadedColumns.Contains(CoreUtils.GetFullPropertyName(column, "."));
  562. }
  563. public static bool HasOriginalValue<T>(this T sender, string propertyname) where T : BaseObject
  564. {
  565. return sender.OriginalValueList != null && sender.OriginalValueList.ContainsKey(propertyname);
  566. }
  567. public static TType GetOriginalValue<T, TType>(this T sender, string propertyname) where T : BaseObject
  568. {
  569. return sender.OriginalValueList != null && sender.OriginalValueList.ContainsKey(propertyname)
  570. ? (TType)CoreUtils.ChangeType(sender.OriginalValueList[propertyname], typeof(TType))
  571. : default;
  572. }
  573. /// <summary>
  574. /// Get all database values (i.e., non-calculated, local properties) for a given object <paramref name="sender"/>.
  575. /// If <paramref name="all"/> is <see langword="false"/>, only retrieve values which have changed.
  576. /// </summary>
  577. public static Dictionary<string, object?> GetValues<T>(this T sender, bool all) where T : BaseObject
  578. {
  579. var result = new Dictionary<string, object?>();
  580. foreach(var property in DatabaseSchema.Properties(sender.GetType()))
  581. {
  582. if (property.IsDBColumn && (all || sender.HasOriginalValue(property.Name)))
  583. {
  584. result[property.Name] = property.Getter()(sender);
  585. }
  586. }
  587. return result;
  588. }
  589. public static Dictionary<string, object?> GetOriginaValues<T>(this T sender) where T : BaseObject
  590. {
  591. var result = new Dictionary<string, object?>();
  592. foreach(var property in DatabaseSchema.Properties(sender.GetType()))
  593. {
  594. if (property.IsDBColumn && sender.OriginalValueList.TryGetValue(property.Name, out var value))
  595. {
  596. result[property.Name] = value;
  597. }
  598. }
  599. return result;
  600. }
  601. public static BaseObjectSnapshot<T> TakeSnapshot<T>(this T obj)
  602. where T : BaseObject
  603. {
  604. return new BaseObjectSnapshot<T>(obj);
  605. }
  606. public static List<string> Compare<T>(this T sender, Dictionary<string, object> original) where T : BaseObject
  607. {
  608. var result = new List<string>();
  609. var current = GetValues(sender, true);
  610. foreach (var key in current.Keys)
  611. if (original.ContainsKey(key))
  612. {
  613. if (current[key] == null)
  614. {
  615. if (original[key] != null)
  616. result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key]));
  617. }
  618. else
  619. {
  620. if (!current[key].Equals(original[key]))
  621. result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key]));
  622. }
  623. }
  624. else
  625. {
  626. result.Add(string.Format("[{0}] not present in previous dictionary!", key));
  627. }
  628. return result;
  629. }
  630. public static bool GetValue<T,TType>(this T sender, Expression<Func<T,TType>> property, bool original, out TType result) where T : BaseObject
  631. {
  632. if (sender.HasOriginalValue<T, TType>(property))
  633. {
  634. if (original)
  635. result = sender.GetOriginalValue<T, TType>(property);
  636. else
  637. {
  638. var expr = property.Compile();
  639. result = expr(sender);
  640. }
  641. return true;
  642. }
  643. result = default(TType);
  644. return false;
  645. }
  646. public static void SetOriginalValue<T, TType>(this T sender, string propertyname, TType value) where T : BaseObject
  647. {
  648. sender.OriginalValueList[propertyname] = value;
  649. }
  650. public static bool HasOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property) where T : BaseObject
  651. {
  652. var propname = CoreUtils.GetFullPropertyName(property, ".");
  653. return !String.IsNullOrWhiteSpace(propname) && sender.OriginalValueList != null && sender.OriginalValueList.ContainsKey(propname);
  654. }
  655. public static bool TryGetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType defaultValue, [NotNullWhen(true)] out TType value) where T : BaseObject
  656. {
  657. var propname = CoreUtils.GetFullPropertyName(property, ".");
  658. if(propname.IsNullOrWhiteSpace() || sender.OriginalValueList is null)
  659. {
  660. value = defaultValue;
  661. return false;
  662. }
  663. else if(sender.OriginalValueList.TryGetValue(propname, out var val))
  664. {
  665. value = CoreUtils.ChangeType<TType>(val);
  666. return true;
  667. }
  668. else
  669. {
  670. value = defaultValue;
  671. return false;
  672. }
  673. }
  674. public static bool TryGetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, [NotNullWhen(true)] out TType value) where T : BaseObject
  675. {
  676. return sender.TryGetOriginalValue(property, default, out value);
  677. }
  678. public static TType GetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property) where T : BaseObject
  679. {
  680. sender.TryGetOriginalValue(property, out var value);
  681. return value;
  682. }
  683. public static TType GetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType defaultValue) where T : BaseObject
  684. {
  685. sender.TryGetOriginalValue(property, defaultValue, out var value);
  686. return value;
  687. }
  688. public static void SetOriginalValue<T, TType>(this T sender, Expression<Func<T, TType>> property, TType value) where T : BaseObject
  689. {
  690. var propname = CoreUtils.GetFullPropertyName(property, ".");
  691. sender.OriginalValueList[propname] = value;
  692. }
  693. }
  694. /// <summary>
  695. /// An <see cref="IProperty"/> is loggable if it has the <see cref="LoggablePropertyAttribute"/> defined on it.<br/>
  696. /// If it is part of an <see cref="IEntityLink"/>, then it is only loggable if the <see cref="IEntityLink"/> property on the parent class
  697. /// also has <see cref="LoggablePropertyAttribute"/>.
  698. /// </summary>
  699. public class LoggablePropertyAttribute : Attribute
  700. {
  701. public string Format { get; set; }
  702. }
  703. }