BaseObject.cs 20 KB

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