BaseObject.cs 22 KB

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