BaseObject.cs 22 KB

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