BaseObject.cs 21 KB

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