LinkedProperties.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. namespace InABox.Core
  8. {
  9. public interface ISubObject
  10. {
  11. BaseObject? GetLinkedParent();
  12. void SetLinkedParent(BaseObject obj);
  13. string? GetLinkedPath();
  14. void SetLinkedPath(string path);
  15. }
  16. public static class SubObjectExtensions
  17. {
  18. public static bool Synchronise(this ISubObject obj, object Entity)
  19. {
  20. var result = false;
  21. foreach (var prop in DatabaseSchema.Properties(obj.GetType()))
  22. {
  23. if (prop.Name == "ID" || !(prop is StandardProperty stdProp) || stdProp.Property.DeclaringType.Equals(typeof(BaseObject))) continue;
  24. var entityProp = DatabaseSchema.Property(Entity.GetType(), prop.Name);
  25. if(entityProp != null)
  26. {
  27. prop.Setter()(obj, entityProp.Getter()(Entity));
  28. }
  29. }
  30. foreach (var link in LinkedProperties.Find(obj, out var parent))
  31. {
  32. link.Update(obj, parent);
  33. result = true;
  34. }
  35. return result;
  36. }
  37. }
  38. public class SubObjectOriginalValues : IOriginalValues
  39. {
  40. private ISubObject Object { get; set; }
  41. private IOriginalValues? _rootDictionary;
  42. private IOriginalValues RootDictionary
  43. {
  44. get
  45. {
  46. _rootDictionary ??= (LinkedProperties.GetParent(Object)?.OriginalValueList ?? new OriginalValues());
  47. return _rootDictionary;
  48. }
  49. }
  50. private string? _rootPath;
  51. /// <summary>
  52. /// Path of this sub object according to the root, suffixed with a "."
  53. /// </summary>
  54. private string RootPath
  55. {
  56. get
  57. {
  58. _rootPath ??= $"{LinkedProperties.GetPath(Object)}.";
  59. return _rootPath;
  60. }
  61. }
  62. public SubObjectOriginalValues(ISubObject obj)
  63. {
  64. Object = obj;
  65. }
  66. private string ChangeKey(string key) => $"{RootPath}{key}";
  67. public bool ContainsKey(string key)
  68. {
  69. return RootDictionary.ContainsKey(ChangeKey(key));
  70. }
  71. public void Clear()
  72. {
  73. foreach(var (k, v) in RootDictionary)
  74. {
  75. if (k.StartsWith(RootPath))
  76. {
  77. RootDictionary.Remove(k);
  78. }
  79. }
  80. }
  81. public void Remove(string key)
  82. {
  83. RootDictionary.Remove(ChangeKey(key));
  84. }
  85. public bool TryAdd(string key, object? value)
  86. {
  87. return RootDictionary.TryAdd(ChangeKey(key), value);
  88. }
  89. public bool TryGetValue(string key, out object? value)
  90. {
  91. return RootDictionary.TryGetValue(ChangeKey(key), out value);
  92. }
  93. public IEnumerator<KeyValuePair<string, object?>> GetEnumerator()
  94. {
  95. return RootDictionary.Where(x => x.Key.StartsWith(RootPath)).Select(x => new KeyValuePair<string, object?>(x.Key[RootPath.Length..], x.Value)).GetEnumerator();
  96. }
  97. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  98. public object? this[string key]
  99. {
  100. get => RootDictionary[ChangeKey(key)];
  101. set => RootDictionary[ChangeKey(key)] = value;
  102. }
  103. }
  104. public class SubObjectLoadedColumns : ILoadedColumns
  105. {
  106. private ISubObject Object { get; set; }
  107. private ILoadedColumns? _rootSet;
  108. private ILoadedColumns RootSet
  109. {
  110. get
  111. {
  112. _rootSet ??= (LinkedProperties.GetParent(Object)?.LoadedColumns ?? new LoadedColumns());
  113. return _rootSet;
  114. }
  115. }
  116. private string? _rootPath;
  117. /// <summary>
  118. /// Path of this sub object according to the root, suffixed with a "."
  119. /// </summary>
  120. private string RootPath
  121. {
  122. get
  123. {
  124. _rootPath ??= $"{LinkedProperties.GetPath(Object)}.";
  125. return _rootPath;
  126. }
  127. }
  128. public SubObjectLoadedColumns(ISubObject obj)
  129. {
  130. Object = obj;
  131. }
  132. private string ChangeKey(string key) => $"{RootPath}{key}";
  133. public bool Add(string key)
  134. {
  135. return RootSet.Add(ChangeKey(key));
  136. }
  137. public bool Contains(string key)
  138. {
  139. return RootSet.Contains(ChangeKey(key));
  140. }
  141. public IEnumerator<string> GetEnumerator()
  142. {
  143. return RootSet.Where(x => x.StartsWith(RootPath)).Select(x => x[RootPath.Length..]).GetEnumerator();
  144. }
  145. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  146. }
  147. public static class LinkedProperties
  148. {
  149. private static readonly Dictionary<Type, List<ILinkedProperty>> _LinkedProperties = new Dictionary<Type, List<ILinkedProperty>>();
  150. public static void Register<TLinkedEntity, TEntityLink, TType>(Expression<Func<TLinkedEntity,TEntityLink>> path, Expression<Func<TEntityLink, TType>> source,
  151. Expression<Func<TLinkedEntity, TType>> target, Func<TLinkedEntity,TType, Result<TType, string>>? func = null)
  152. where TLinkedEntity : class
  153. where TEntityLink : class
  154. {
  155. var map = new LinkedProperty<TLinkedEntity, TEntityLink, TType>(path, source, target, func);
  156. if(!_LinkedProperties.TryGetValue(map.Type, out var props))
  157. {
  158. props = new List<ILinkedProperty>();
  159. _LinkedProperties[map.Type] = props;
  160. }
  161. if(!props.Any(x => x.Equals(map)))
  162. {
  163. props.Add(map);
  164. }
  165. }
  166. /*public static IEnumerable<ILinkedProperty> Find(object parent, object path)
  167. {
  168. var all = _LinkedProperties.Where(x => x.Type == parent.GetType());
  169. var filtered = all.Where(x => CoreUtils.GetPropertyValue(parent,x.Path)?.GetType() == path?.GetType());
  170. return filtered;
  171. }*/
  172. public static BaseObject? GetParent(ISubObject link)
  173. {
  174. while (true)
  175. {
  176. var parent = link.GetLinkedParent();
  177. if(parent is ISubObject parentLink)
  178. {
  179. link = parentLink;
  180. }
  181. else
  182. {
  183. return parent;
  184. }
  185. }
  186. }
  187. public static string? GetPath(ISubObject link)
  188. {
  189. var path = link.GetLinkedPath();
  190. while (true)
  191. {
  192. var parent = link.GetLinkedParent();
  193. if (parent is ISubObject parentLink)
  194. {
  195. link = parentLink;
  196. path = $"{link.GetLinkedPath()}.{path}";
  197. }
  198. else
  199. {
  200. return path;
  201. }
  202. }
  203. }
  204. public static IEnumerable<ILinkedProperty> FindAll(Type parent)
  205. {
  206. if (_LinkedProperties.TryGetValue(parent, out var props))
  207. {
  208. return props;
  209. }
  210. return Enumerable.Empty<ILinkedProperty>();
  211. }
  212. public static IEnumerable<ILinkedProperty> Find(ISubObject subObject, out BaseObject parent)
  213. {
  214. parent = GetParent(subObject);
  215. if (parent is null || !_LinkedProperties.TryGetValue(parent.GetType(), out var props))
  216. {
  217. return Enumerable.Empty<ILinkedProperty>();
  218. }
  219. var path = GetPath(subObject);
  220. return props.Where(x => x.Path == path);
  221. }
  222. public static bool Find(ISubObject subObject, string name, [NotNullWhen(true)] out ILinkedProperty? property, [NotNullWhen(true)] out BaseObject? parent)
  223. {
  224. property = null;
  225. parent = GetParent(subObject);
  226. if (parent is null)
  227. return false;
  228. if(!_LinkedProperties.TryGetValue(parent.GetType(), out var props))
  229. return false;
  230. var path = GetPath(subObject);
  231. property = props.FirstOrDefault(x => string.Equals(
  232. $"{x.Path}{(x.Path.IsNullOrWhiteSpace() ? "" : ".")}{x.Source}",
  233. $"{path}{(path.IsNullOrWhiteSpace() ? "" : ".")}{name}"));
  234. return property != null;
  235. }
  236. }
  237. }