BaseObject.cs 26 KB

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