Column.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Linq.Expressions;
  5. using System.Reflection;
  6. using System.Runtime.Serialization;
  7. using Newtonsoft.Json;
  8. namespace InABox.Core
  9. {
  10. public interface IColumn
  11. {
  12. string Property { get; }
  13. }
  14. public class Column<T> : SerializableExpression<T>, IColumn
  15. {
  16. public bool IsEqualTo(String name) =>
  17. !String.IsNullOrWhiteSpace(name) && String.Equals(Property, name);
  18. public bool IsParentOf(String name) =>
  19. !String.IsNullOrWhiteSpace(name) && name.StartsWith(Property + ".");
  20. public Column()
  21. {
  22. }
  23. public Column(Expression<Func<T, object?>> expression) : base(expression)
  24. {
  25. //String[] parts = expression.ToString().Split(new String[] { "=>" }, StringSplitOptions.RemoveEmptyEntries);
  26. //string property = String.Join(".", parts.Last().Split('.').Skip(1));
  27. //property = property.Replace("Convert(", "").Replace("(","").Replace(")", "");
  28. Property = CoreUtils.GetFullPropertyName(expression, ".");
  29. }
  30. public Column(string property)
  31. {
  32. Property = property;
  33. var iprop = DatabaseSchema.Property(typeof(T), property);
  34. if (iprop != null)
  35. Expression = iprop.Expression();
  36. else
  37. Expression = CoreUtils.CreateMemberExpression(typeof(T), property);
  38. }
  39. public string Property { get; private set; }
  40. public override void Deserialize(SerializationInfo info, StreamingContext context)
  41. {
  42. }
  43. public override void Serialize(SerializationInfo info, StreamingContext context)
  44. {
  45. }
  46. public static explicit operator Column<T>(Column<Entity> v)
  47. {
  48. var result = new Column<T>();
  49. var exp = CoreUtils.ExpressionToString(typeof(T), v.Expression, true);
  50. result.Expression = CoreUtils.StringToExpression(exp);
  51. result.Property = v.Property;
  52. return result;
  53. }
  54. public override string ToString()
  55. {
  56. var name = Expression.ToString().Replace("x => ", "").Replace("x.", "");
  57. if (Expression.NodeType == System.Linq.Expressions.ExpressionType.Index)
  58. {
  59. var chars = name.SkipWhile(x => !x.Equals('[')).TakeWhile(x => !x.Equals(']'));
  60. name = string.Join("", chars).Replace("[", "").Replace("]", "").Replace("\"", "");
  61. }
  62. return name;
  63. //return Property.ToString();
  64. }
  65. public Type ExpressionType()
  66. {
  67. if (Expression == null)
  68. throw new Exception(string.Format("Expression [{0}] may not be null!", Property));
  69. if (Expression is IndexExpression)
  70. return DatabaseSchema.Property(typeof(T), Property).PropertyType;
  71. return Expression.Type;
  72. }
  73. }
  74. public interface IColumns
  75. {
  76. bool Any();
  77. IEnumerable<string> ColumnNames();
  78. Dictionary<String, Type> AsDictionary();
  79. IColumns Add(string column);
  80. IColumns Add(IColumn column);
  81. IColumns Add<T>(Expression<Func<T, object?>> column);
  82. IColumns DefaultColumns(params ColumnType[] types);
  83. }
  84. public enum ColumnType
  85. {
  86. ExcludeVisible,
  87. ExcludeID,
  88. IncludeOptional,
  89. IncludeForeignKeys,
  90. IncludeLinked,
  91. IncludeAggregates,
  92. IncludeFormulae,
  93. IncludeUserProperties,
  94. IncludeNestedLinks,
  95. IncludeEditable,
  96. All
  97. }
  98. public static class Columns
  99. {
  100. public static IColumns Create<T>(Type concrete)
  101. {
  102. if (!typeof(T).IsAssignableFrom(concrete))
  103. throw new Exception($"Columns: {concrete.EntityName()} does not implement {typeof(T).EntityName()}");
  104. var type = typeof(Columns<>).MakeGenericType(concrete);
  105. var result = Activator.CreateInstance(type);
  106. return (result as IColumns)!;
  107. }
  108. public static IColumns Create(Type concrete)
  109. {
  110. var type = typeof(Columns<>).MakeGenericType(concrete);
  111. var result = Activator.CreateInstance(type) as IColumns;
  112. return result!;
  113. }
  114. }
  115. public class Columns<T> : IColumns
  116. {
  117. private readonly List<Column<T>> columns;
  118. public Columns()
  119. {
  120. columns = new List<Column<T>>();
  121. }
  122. public Columns(params Expression<Func<T, object?>>[] expressions) : this()
  123. {
  124. foreach (var expression in expressions)
  125. columns.Add(new Column<T>(expression));
  126. }
  127. public Columns(IEnumerable<string> properties) : this()
  128. {
  129. foreach (var property in properties)
  130. columns.Add(new Column<T>(property));
  131. }
  132. public Column<T>[] Items
  133. {
  134. get { return columns != null ? columns.ToArray() : new Column<T>[] { }; }
  135. set
  136. {
  137. columns.Clear();
  138. columns.AddRange(value);
  139. }
  140. }
  141. public bool Any() => columns.Any();
  142. public IColumns Add(string column)
  143. {
  144. if(CoreUtils.TryGetProperty(typeof(T), column, out var propertyInfo))
  145. {
  146. if (!propertyInfo.PropertyType.GetInterfaces().Contains(typeof(IEnclosedEntity)) &&
  147. !propertyInfo.PropertyType.GetInterfaces().Any(x => x == typeof(IEntityLink)))
  148. {
  149. var exists = columns.Any(x => x.Expression.ToString().Replace("x.", "").Equals(column));
  150. if (!exists)
  151. columns.Add(new Column<T>(column));
  152. }
  153. }
  154. else
  155. {
  156. var prop = DatabaseSchema.Property(typeof(T), column);
  157. if (prop != null)
  158. {
  159. var exists = columns.Any(x => x.Expression.Equals(prop.Expression()));
  160. if (!exists)
  161. columns.Add(new Column<T>(column));
  162. }
  163. }
  164. return this;
  165. }
  166. public IColumns Add<TEntity>(Expression<Func<TEntity, object?>> expression)
  167. {
  168. return Add(CoreUtils.GetFullPropertyName(expression, "."));
  169. }
  170. public Columns<T> Add(Column<T> column)
  171. {
  172. if(!columns.Any(x => x.Property.Equals(column.Property)))
  173. {
  174. columns.Add(column);
  175. }
  176. return this;
  177. }
  178. public IColumns Add(IColumn column)
  179. {
  180. if (column is Column<T> col)
  181. return Add(col);
  182. return this;
  183. }
  184. public IEnumerable<string> ColumnNames()
  185. {
  186. return Items.Select(c => c.Property);
  187. //List<String> result = new List<string>();
  188. //foreach (var col in Items)
  189. // result.Add(col.Property);
  190. //return result;
  191. }
  192. public Dictionary<String, Type> AsDictionary()
  193. {
  194. Dictionary< String, Type> result = new Dictionary< String, Type>();
  195. foreach (var column in Items)
  196. result[column.Property] = column.ExpressionType();
  197. return result;
  198. }
  199. public IColumns DefaultColumns(params ColumnType[] types)
  200. {
  201. return Default(types);
  202. }
  203. public Columns<T> Add(params string[] columnnames)
  204. {
  205. foreach (var name in columnnames)
  206. Add(name);
  207. return this;
  208. }
  209. public Columns<T> Add(IEnumerable<string> columnnames)
  210. {
  211. foreach (var name in columnnames)
  212. Add(name);
  213. return this;
  214. }
  215. public Columns<T> Add(Expression<Func<T, object?>> expression)
  216. {
  217. try
  218. {
  219. var property = CoreUtils.GetFullPropertyName(expression, ".");
  220. var exists = columns.Any(x => x.Expression.ToString().Replace("x.", "").Equals(property));
  221. if (!exists)
  222. columns.Add(new Column<T>(expression));
  223. }
  224. catch (Exception e)
  225. {
  226. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  227. }
  228. return this;
  229. }
  230. public Columns<T> Add<TType>(Expression<Func<T, TType>> expression)
  231. {
  232. try
  233. {
  234. var property = CoreUtils.GetFullPropertyName(expression, ".");
  235. var exists = columns.Any(x => x.Expression.ToString().Replace("x.", "").Equals(property));
  236. if (!exists)
  237. {
  238. columns.Add(new Column<T>(property));
  239. }
  240. }
  241. catch (Exception e)
  242. {
  243. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  244. }
  245. return this;
  246. }
  247. public Columns<T> Remove(string column)
  248. {
  249. var col = new Column<T>(column);
  250. columns.RemoveAll(x => x.ToString() == col.ToString());
  251. return this;
  252. }
  253. public static explicit operator Columns<T>(Columns<Entity> vs)
  254. {
  255. var result = new Columns<T>();
  256. var items = vs.Items.Cast<Column<T>>().ToArray();
  257. result.Items = items;
  258. //List<Column<T>> cols = new List<Column<T>>();
  259. //foreach (var v in vs.Items)
  260. // cols.Add((Column<T>)v);
  261. //result.Items = cols.ToArray();
  262. return result;
  263. }
  264. public Columns<T> Default(params ColumnType[] types)
  265. {
  266. columns.Clear();
  267. var props = DatabaseSchema.Properties(typeof(T)).Where(x=>x.Setter() != null).OrderBy(x => CoreUtils.GetPropertySequence(typeof(T), x.Name)).ToList();
  268. if (types.Contains(ColumnType.All))
  269. {
  270. foreach (var prop in props)
  271. columns.Add(new Column<T>(prop.Name));
  272. return this;
  273. }
  274. if (typeof(T).IsSubclassOf(typeof(Entity)) && !types.Contains(ColumnType.ExcludeID))
  275. columns.Add(new Column<T>("ID"));
  276. for (int iCol=0; iCol<props.Count; iCol++)
  277. {
  278. var prop = props[iCol];
  279. var bOK = true;
  280. var bIsForeignKey = false;
  281. var bNullEditor = prop.Editor is NullEditor;
  282. if (prop is CustomProperty)
  283. {
  284. if (!types.Any(x => x.Equals(ColumnType.IncludeUserProperties)))
  285. bOK = false;
  286. else
  287. columns.Add(new Column<T>(prop.Name));
  288. }
  289. if (bOK)
  290. if (prop.Name.Contains(".") && !(prop is CustomProperty))
  291. {
  292. var ancestors = prop.Name.Split('.');
  293. var anclevel = 2;
  294. for (var i = 1; i < ancestors.Length; i++)
  295. {
  296. var ancestor = string.Join(".", ancestors.Take(i));
  297. var ancprop = CoreUtils.GetProperty(typeof(T), ancestor);
  298. bNullEditor = bNullEditor || ancprop.GetCustomAttribute<NullEditor>() != null;
  299. if (ancprop.PropertyType.GetInterfaces().Contains(typeof(IEnclosedEntity)))
  300. anclevel++;
  301. else if (ancprop.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)))
  302. {
  303. if (types.Contains(ColumnType.IncludeLinked) || types.Contains(ColumnType.IncludeForeignKeys))
  304. {
  305. if (types.Contains(ColumnType.IncludeNestedLinks) || ancestors.Length <= anclevel)
  306. {
  307. if (prop.Name.EndsWith(".ID") && types.Contains(ColumnType.IncludeForeignKeys))
  308. {
  309. bIsForeignKey = true;
  310. break;
  311. }
  312. if (!types.Contains(ColumnType.IncludeLinked))
  313. {
  314. bOK = false;
  315. break;
  316. }
  317. }
  318. else
  319. {
  320. bOK = false;
  321. break;
  322. }
  323. }
  324. else
  325. {
  326. bOK = false;
  327. break;
  328. }
  329. }
  330. }
  331. }
  332. if (bOK)
  333. {
  334. var visible = prop.Editor != null
  335. ? bNullEditor
  336. ? Visible.Hidden
  337. : prop.Editor.Visible
  338. : Visible.Optional;
  339. var editable = prop.Editor != null
  340. ? bNullEditor
  341. ? Editable.Hidden
  342. : prop.Editor.Editable
  343. : Editable.Enabled;
  344. bOK = (types.Any(x => x.Equals(ColumnType.IncludeForeignKeys)) && bIsForeignKey) ||
  345. (!types.Any(x => x.Equals(ColumnType.ExcludeVisible)) && visible.Equals(Visible.Default)) ||
  346. (types.Any(x => x.Equals(ColumnType.IncludeOptional)) && visible.Equals(Visible.Optional)) ||
  347. (types.Any(x => x.Equals(ColumnType.IncludeEditable)) && !editable.Equals(Editable.Hidden));
  348. }
  349. var property = bOK ? DatabaseSchema.Property(typeof(T), prop.Name) : null;
  350. if (property is StandardProperty)
  351. {
  352. if (bOK && !types.Any(x => x.Equals(ColumnType.IncludeAggregates)))
  353. bOK = CoreUtils.GetProperty(typeof(T), prop.Name).GetCustomAttribute<AggregateAttribute>() == null;
  354. if (bOK && !types.Any(x => x.Equals(ColumnType.IncludeFormulae)))
  355. bOK = CoreUtils.GetProperty(typeof(T), prop.Name).GetCustomAttribute<FormulaAttribute>() == null;
  356. }
  357. if (bOK && !columns.Any(x => string.Equals(x.Property?.ToUpper(), prop.Name?.ToUpper())))
  358. {
  359. if (bOK && prop.Editor is LookupEditor le)
  360. {
  361. if (le.OtherColumns != null)
  362. {
  363. var prefix = String.Join(".",prop.Name.Split('.').Reverse().Skip(1).Reverse());
  364. foreach (var col in le.OtherColumns)
  365. {
  366. String newcol = prefix + "." + col.Key;
  367. if (!columns.Any(x => String.Equals(newcol, x.Property)))
  368. columns.Add(new Column<T>(newcol));
  369. }
  370. }
  371. }
  372. if (!columns.Any(x => String.Equals(prop.Name, x.Property)))
  373. columns.Add(new Column<T>(prop.Name));
  374. }
  375. }
  376. return this;
  377. }
  378. }
  379. public class ColumnJsonConverter : JsonConverter
  380. {
  381. public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
  382. {
  383. if(value is null)
  384. {
  385. writer.WriteNull();
  386. return;
  387. }
  388. var property = (CoreUtils.GetPropertyValue(value, "Expression") as Expression)
  389. ?? throw new Exception("'Column.Expression' may not be null");
  390. var prop = CoreUtils.ExpressionToString(value.GetType().GenericTypeArguments[0], property, true);
  391. var name = CoreUtils.GetPropertyValue(value, "Property") as string;
  392. writer.WriteStartObject();
  393. writer.WritePropertyName("Expression");
  394. writer.WriteValue(prop);
  395. writer.WritePropertyName("Property");
  396. writer.WriteValue(name);
  397. writer.WriteEndObject();
  398. }
  399. public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
  400. {
  401. if (reader.TokenType == JsonToken.Null)
  402. return null;
  403. var data = new Dictionary<string, object>();
  404. while (reader.TokenType != JsonToken.EndObject && reader.Read())
  405. if (reader.Value != null)
  406. {
  407. var key = reader.Value.ToString();
  408. reader.Read();
  409. data[key] = reader.Value;
  410. }
  411. var prop = data["Property"].ToString();
  412. var result = Activator.CreateInstance(objectType, prop);
  413. return result;
  414. }
  415. public override bool CanConvert(Type objectType)
  416. {
  417. if (objectType.IsConstructedGenericType)
  418. {
  419. var ot = objectType.GetGenericTypeDefinition();
  420. var tt = typeof(Column<>);
  421. if (ot == tt)
  422. return true;
  423. }
  424. return false;
  425. }
  426. }
  427. }