BaseObject.cs 21 KB

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