Column.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  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. using System.Reflection;
  8. using System.Runtime.CompilerServices;
  9. using System.Runtime.Serialization;
  10. using JetBrains.Annotations;
  11. using Newtonsoft.Json;
  12. using Newtonsoft.Json.Linq;
  13. namespace InABox.Core
  14. {
  15. public interface IColumn
  16. {
  17. string Property { get; }
  18. Type Type { get; }
  19. }
  20. public static class Column
  21. {
  22. public static IColumn Create(Type concrete, string property)
  23. {
  24. var type = typeof(Column<>).MakeGenericType(concrete);
  25. var result = Activator.CreateInstance(type, property) as IColumn;
  26. return result!;
  27. }
  28. }
  29. public class Column<T> : IColumn
  30. {
  31. private IProperty _property;
  32. public Type Type => _property.PropertyType;
  33. public string Property => _property.Name;
  34. public Expression Expression => _property.Expression();
  35. public bool IsEqualTo(string name) =>
  36. !name.IsNullOrWhiteSpace() && Property.Equals(name);
  37. public bool IsEqualTo(Column<T> column) =>
  38. string.Equals(Property, column.Property);
  39. public bool IsParentOf(string name) =>
  40. !name.IsNullOrWhiteSpace() && name.StartsWith(Property + ".");
  41. public Column(IProperty property)
  42. {
  43. _property = property;
  44. }
  45. public Column(Expression<Func<T, object?>> expression)
  46. {
  47. _property = DatabaseSchema.Property(expression)
  48. ?? throw new Exception($"Property {CoreUtils.GetFullPropertyName(expression, ".")} not found.");
  49. }
  50. public Column(string property)
  51. {
  52. _property = DatabaseSchema.Property(typeof(T), property)
  53. ?? throw new Exception($"Property {property} not found.");
  54. }
  55. public Column<TNew> Cast<TNew>()
  56. where TNew: T
  57. {
  58. return new Column<TNew>(Property);
  59. }
  60. public bool TryCast<TNew>([NotNullWhen(true)] out Column<TNew>? newColumn)
  61. {
  62. if(DatabaseSchema.Property(typeof(TNew), Property) is IProperty property)
  63. {
  64. newColumn = new Column<TNew>(property);
  65. return true;
  66. }
  67. else
  68. {
  69. newColumn = null;
  70. return false;
  71. }
  72. }
  73. public override string ToString() => Property;
  74. }
  75. public interface IColumns : ISerializeBinary
  76. {
  77. int Count { get; }
  78. IEnumerable<string> ColumnNames();
  79. bool Contains(string column);
  80. IColumns Add(string column);
  81. IColumns Add(IColumn column);
  82. IColumns Add<T>(Expression<Func<T, object?>> column);
  83. IColumns Add(IProperty property);
  84. IColumns Add(IEnumerable<string> columns);
  85. IColumns Add(params string[] columns);
  86. IEnumerator<IColumn> GetEnumerator();
  87. }
  88. public enum ColumnType
  89. {
  90. //ExcludeVisible,
  91. /// <summary>
  92. /// Do not include <see cref="Entity.ID"/> in the columns.
  93. /// </summary>
  94. ExcludeID,
  95. IncludeOptional,
  96. IncludeForeignKeys,
  97. /// <summary>
  98. /// Include all columns that are accessible through entity links present in the root class.
  99. /// </summary>
  100. IncludeLinked,
  101. IncludeAggregates,
  102. IncludeFormulae,
  103. /// <summary>
  104. /// Include any columns that are a <see cref="CustomProperty"/>.
  105. /// </summary>
  106. IncludeUserProperties,
  107. /// <summary>
  108. /// Include all columns that are accessible through entity links, even nested ones.
  109. /// </summary>
  110. IncludeNestedLinks,
  111. IncludeEditable,
  112. /// <summary>
  113. /// Add all columns that are data actually on the entity, and not on entity links or calculated fields.
  114. /// </summary>
  115. DataColumns,
  116. /// <summary>
  117. /// Add all columns found.
  118. /// </summary>
  119. All
  120. }
  121. [Flags]
  122. public enum ColumnTypeFlags
  123. {
  124. /// <summary>
  125. /// No columns at all.
  126. /// </summary>
  127. None = 0,
  128. /// <summary>
  129. /// All columns which are marked as <see cref="RequiredColumnAttribute"/>.
  130. /// </summary>
  131. Required = 1,
  132. /// <summary>
  133. /// All columns.
  134. /// </summary>
  135. All = 2,
  136. /// <summary>
  137. /// Only columns on the entity, and not on entity links or calculated fields.
  138. /// </summary>
  139. /// <remarks>
  140. /// This option <b>does</b> include foreign keys.
  141. /// </remarks>
  142. Local = 4,
  143. IncludeID = 8,
  144. IncludeForeignKeys = 16,
  145. /// <summary>
  146. /// Include all columns that are accessible through entity links present in the root class.
  147. /// </summary>
  148. IncludeLinked = 32,
  149. /// <summary>
  150. /// Include all columns that are accessible through entity links, even nested ones.
  151. /// </summary>
  152. IncludeNestedLinks = 64,
  153. IncludeAggregates = 128,
  154. IncludeFormulae = 256,
  155. /// <summary>
  156. /// Include any columns that are a <see cref="CustomProperty"/>.
  157. /// </summary>
  158. IncludeUserProperties = 512,
  159. IncludeEditable = 1024,
  160. /// <summary>
  161. /// Include all columns marked as <see cref="Visible.Optional"/>, <see cref="Visible.Default"/> (or that don't have a visibility).
  162. /// </summary>
  163. IncludeOptional = 2048,
  164. /// <summary>
  165. /// Include all columns marked as <see cref="Visible.Default"/> (or that don't have a visibility).
  166. /// </summary>
  167. IncludeVisible = 4096,
  168. DefaultVisible = IncludeVisible | IncludeID,
  169. EditorColumns = IncludeID | Required | IncludeOptional | IncludeForeignKeys | IncludeUserProperties | IncludeEditable,
  170. }
  171. public static class Columns
  172. {
  173. public static IColumns Create<T>(Type concrete, ColumnTypeFlags flags)
  174. {
  175. if (!typeof(T).IsAssignableFrom(concrete))
  176. throw new Exception($"Columns: {concrete.EntityName()} does not implement {typeof(T).EntityName()}");
  177. var type = typeof(Columns<>).MakeGenericType(concrete);
  178. var result = Activator.CreateInstance(type, flags);
  179. return (result as IColumns)!;
  180. }
  181. public static IColumns Create(Type concrete, ColumnTypeFlags flags)
  182. {
  183. var type = typeof(Columns<>).MakeGenericType(concrete);
  184. var result = Activator.CreateInstance(type, flags) as IColumns;
  185. return result!;
  186. }
  187. /// <summary>
  188. /// Create a new <see cref="IColumns"/> that is completely empty.
  189. /// </summary>
  190. public static IColumns None(Type T) => Create(T, ColumnTypeFlags.None);
  191. /// <summary>
  192. /// Create a new <see cref="IColumns"/> with all columns of <paramref name="T"/> that are marked as <see cref="RequiredColumnAttribute"/>.
  193. /// </summary>
  194. public static IColumns Required(Type T) => Create(T, ColumnTypeFlags.Required);
  195. /// <summary>
  196. /// Create a new <see cref="IColumns"/> with all columns local to the entity, and not on entity links or calculated fields.
  197. /// </summary>
  198. public static IColumns Local(Type T) => Create(T, ColumnTypeFlags.Local);
  199. /// <summary>
  200. /// Create a new <see cref="IColumns"/> with all columns that the entity has.
  201. /// </summary>
  202. public static IColumns All(Type T) => Create(T, ColumnTypeFlags.All);
  203. /// <summary>
  204. /// Create a new <see cref="IColumns"/> that is completely empty.
  205. /// </summary>
  206. public static Columns<T> None<T>()
  207. {
  208. return new Columns<T>(ColumnTypeFlags.None);
  209. }
  210. /// <summary>
  211. /// Create a new <see cref="IColumns"/> with all columns of <paramref name="T"/> that are marked as <see cref="RequiredColumnAttribute"/>.
  212. /// </summary>
  213. public static Columns<T> Required<T>()
  214. {
  215. return new Columns<T>(ColumnTypeFlags.Required);
  216. }
  217. /// <summary>
  218. /// Create a new <see cref="IColumns"/> with all columns local to the entity, and not on entity links or calculated fields.
  219. /// </summary>
  220. public static Columns<T> Local<T>()
  221. {
  222. return new Columns<T>(ColumnTypeFlags.Local);
  223. }
  224. /// <summary>
  225. /// Create a new <see cref="IColumns"/> with all columns that the entity has.
  226. /// </summary>
  227. public static Columns<T> All<T>()
  228. {
  229. return new Columns<T>(ColumnTypeFlags.All);
  230. }
  231. }
  232. public class Columns<T> : IColumns, ICollection<Column<T>>
  233. {
  234. #region Private Fields
  235. private readonly List<Column<T>> columns;
  236. #endregion
  237. #region Public Accessors
  238. public Column<T> this[int index] => columns[index];
  239. public int Count => columns.Count;
  240. bool ICollection<Column<T>>.IsReadOnly => false;
  241. #endregion
  242. private Columns()
  243. {
  244. columns = new List<Column<T>>();
  245. }
  246. public Columns(ColumnTypeFlags flags)
  247. {
  248. columns = new List<Column<T>>();
  249. AddColumns(flags);
  250. }
  251. public Columns<T> AddColumns(ColumnTypeFlags flags)
  252. {
  253. if (flags == ColumnTypeFlags.None)
  254. return this;
  255. var props = DatabaseSchema.Properties(typeof(T))
  256. .Where(x => x.Setter() != null)
  257. .OrderBy(x => x.PropertySequence()).ToList();
  258. if (flags.HasFlag(ColumnTypeFlags.All))
  259. {
  260. foreach (var prop in props)
  261. columns.Add(new Column<T>(prop.Name));
  262. return this;
  263. }
  264. if (typeof(T).IsSubclassOf(typeof(Entity)) && flags.HasFlag(ColumnTypeFlags.IncludeID))
  265. columns.Add(new Column<T>(nameof(Entity.ID)));
  266. foreach(var prop in props)
  267. {
  268. if (flags.HasFlag(ColumnTypeFlags.Required) && prop.Required)
  269. {
  270. columns.Add(new Column<T>(prop));
  271. }
  272. else
  273. {
  274. var isLocal = !prop.HasParentEntityLink()
  275. || (prop.Parent?.HasParentEntityLink() != true && prop.Name.EndsWith(".ID"));
  276. if (flags.HasFlag(ColumnTypeFlags.Local) && isLocal && !prop.IsCalculated)
  277. {
  278. columns.Add(new Column<T>(prop));
  279. }
  280. else if(prop is CustomProperty)
  281. {
  282. if (flags.HasFlag(ColumnTypeFlags.IncludeUserProperties))
  283. {
  284. columns.Add(new Column<T>(prop));
  285. }
  286. else
  287. {
  288. // Don't add
  289. }
  290. }
  291. else
  292. {
  293. var parentLink = prop.HasParentEntityLink();
  294. var failed = false;
  295. if(prop.HasParentEntityLink())
  296. {
  297. if(prop.Parent?.HasParentEntityLink() == true && !flags.HasFlag(ColumnTypeFlags.IncludeNestedLinks))
  298. {
  299. failed = true;
  300. }
  301. else if(!prop.Name.EndsWith(".ID") && !flags.HasFlag(ColumnTypeFlags.IncludeLinked))
  302. {
  303. failed = true;
  304. }
  305. else if(prop.Name.EndsWith(".ID") && !flags.HasFlag(ColumnTypeFlags.IncludeForeignKeys))
  306. {
  307. failed = true;
  308. }
  309. }
  310. if (!failed)
  311. {
  312. var hasNullEditor = prop.GetParent(x => x.HasEditor && x.Editor is NullEditor) != null;
  313. var visible = hasNullEditor ? Visible.Hidden : (prop.Editor?.Visible ?? Visible.Optional);
  314. var editable = hasNullEditor ? Editable.Hidden : (prop.Editor?.Editable ?? Editable.Enabled);
  315. failed = (!flags.HasFlag(ColumnTypeFlags.IncludeVisible) || visible != Visible.Default)
  316. && (!flags.HasFlag(ColumnTypeFlags.IncludeOptional) || (visible != Visible.Optional && visible != Visible.Default))
  317. && (!flags.HasFlag(ColumnTypeFlags.IncludeEditable) || !editable.ColumnVisible());
  318. }
  319. if (!failed)
  320. {
  321. failed = (!flags.HasFlag(ColumnTypeFlags.IncludeAggregates) && prop.HasAttribute<AggregateAttribute>())
  322. || (!flags.HasFlag(ColumnTypeFlags.IncludeFormulae) && prop.HasAttribute<FormulaAttribute>());
  323. }
  324. if (!failed)
  325. {
  326. columns.Add(new Column<T>(prop));
  327. }
  328. }
  329. }
  330. }
  331. return this;
  332. }
  333. #region IColumns
  334. IColumns IColumns.Add(string column) => Add(column);
  335. IColumns IColumns.Add<TEntity>(Expression<Func<TEntity, object?>> expression)
  336. {
  337. return Add(CoreUtils.GetFullPropertyName(expression, "."));
  338. }
  339. public IColumns Add(IColumn column)
  340. {
  341. if (column is Column<T> col)
  342. return Add(col);
  343. return this;
  344. }
  345. IColumns IColumns.Add(IProperty property) => Add(property);
  346. IColumns IColumns.Add(params string[] columnnames) => Add(columnnames);
  347. IColumns IColumns.Add(IEnumerable<string> columnnames) => Add(columnnames);
  348. #endregion
  349. #region Add
  350. private Columns<T> Add(string column)
  351. {
  352. if (!Contains(column))
  353. {
  354. var property = DatabaseSchema.Property(typeof(T), column);
  355. if(property is null)
  356. {
  357. Logger.Send(LogType.Error, "", $"Property {column} does not exist on {typeof(T).Name}");
  358. }
  359. else
  360. {
  361. columns.Add(new Column<T>(property));
  362. }
  363. }
  364. return this;
  365. }
  366. public Columns<T> Add(Column<T> column)
  367. {
  368. if(!Contains(column.Property))
  369. {
  370. columns.Add(column);
  371. }
  372. return this;
  373. }
  374. public Columns<T> Add(IProperty property)
  375. {
  376. if (!Contains(property.Name))
  377. {
  378. columns.Add(new Column<T>(property));
  379. }
  380. return this;
  381. }
  382. public Columns<T> Add(Expression<Func<T, object?>> expression)
  383. => Add(CoreUtils.GetFullPropertyName(expression, "."));
  384. public Columns<T> Add<TType>(Expression<Func<T, TType>> expression)
  385. => Add(CoreUtils.GetFullPropertyName(expression, "."));
  386. #region Range Adds
  387. public Columns<T> AddSubColumns<TSub>(Expression<Func<T, TSub>> super, Columns<TSub>? sub)
  388. {
  389. sub ??= CoreUtils.GetColumns(sub);
  390. var prefix = CoreUtils.GetFullPropertyName(super, ".") + ".";
  391. foreach(var column in sub.ColumnNames())
  392. {
  393. columns.Add(new Column<T>(prefix + column));
  394. }
  395. return this;
  396. }
  397. public Columns<T> Add(IEnumerable<Column<T>> columns)
  398. {
  399. foreach(var col in columns)
  400. {
  401. Add(col);
  402. }
  403. return this;
  404. }
  405. public Columns<T> Add(params string[] columnnames)
  406. {
  407. foreach (var name in columnnames)
  408. Add(name);
  409. return this;
  410. }
  411. public Columns<T> Add(IEnumerable<string> columnnames)
  412. {
  413. foreach (var name in columnnames)
  414. Add(name);
  415. return this;
  416. }
  417. public Columns<T> Add(params Expression<Func<T, object?>>[] expressions)
  418. {
  419. foreach (var expression in expressions)
  420. columns.Add(new Column<T>(expression));
  421. return this;
  422. }
  423. /// <summary>
  424. /// Add a range of columns, <b>without</b> checking for duplicates.
  425. /// </summary>
  426. /// <param name="columns"></param>
  427. /// <returns></returns>
  428. public Columns<T> AddRange(IEnumerable<Column<T>> columns)
  429. {
  430. this.columns.AddRange(columns);
  431. return this;
  432. }
  433. #endregion
  434. #endregion
  435. #region Remove
  436. public Columns<T> Remove(string column)
  437. {
  438. columns.RemoveAll(x => x.Property == column);
  439. return this;
  440. }
  441. #endregion
  442. #region Casting Columns Type
  443. public Columns<TNew> Cast<TNew>()
  444. where TNew : T
  445. {
  446. var cols = Columns.None<TNew>();
  447. foreach(var column in columns)
  448. {
  449. cols.Add(column.Cast<TNew>());
  450. }
  451. return cols;
  452. }
  453. /// <summary>
  454. /// Cast the columns to <typeparamref name="TNew"/>, keeping the columns that are found in both <typeparamref name="T"/> and <typeparamref name="TNew"/>.
  455. /// </summary>
  456. /// <typeparam name="TNew"></typeparam>
  457. /// <returns></returns>
  458. public Columns<TNew> CastIntersection<TNew>()
  459. {
  460. var cols = Columns.None<TNew>();
  461. foreach(var column in columns)
  462. {
  463. if (column.TryCast<TNew>(out var newColumn))
  464. {
  465. cols.Add(newColumn);
  466. }
  467. }
  468. return cols;
  469. }
  470. #endregion
  471. public override string ToString()
  472. {
  473. return string.Join("; ", columns.Select(x => x.Property));
  474. }
  475. public int IndexOf(Expression<Func<T, object>> expression)
  476. {
  477. var columnName = CoreUtils.GetFullPropertyName(expression, ".");
  478. for(int i = 0; i < columns.Count; ++i)
  479. {
  480. if (columns[i].Property == columnName)
  481. {
  482. return i;
  483. }
  484. }
  485. return -1;
  486. }
  487. public bool Contains(string column) => columns.Any(x => x.IsEqualTo(column));
  488. public IEnumerable<string> ColumnNames()
  489. {
  490. return columns.Select(c => c.Property);
  491. }
  492. #region Binary Serialization
  493. public void SerializeBinary(CoreBinaryWriter writer)
  494. {
  495. writer.Write(columns.Count);
  496. foreach(var column in columns)
  497. {
  498. writer.Write(column.Property);
  499. }
  500. }
  501. public void DeserializeBinary(CoreBinaryReader reader)
  502. {
  503. columns.Clear();
  504. var nColumns = reader.ReadInt32();
  505. for(int i = 0; i < nColumns; ++i)
  506. {
  507. var property = reader.ReadString();
  508. columns.Add(new Column<T>(property));
  509. }
  510. }
  511. #endregion
  512. #region ICollection
  513. public IEnumerator<Column<T>> GetEnumerator()
  514. {
  515. return columns.GetEnumerator();
  516. }
  517. IEnumerator<IColumn> IColumns.GetEnumerator() => GetEnumerator();
  518. IEnumerator IEnumerable.GetEnumerator()
  519. {
  520. return columns.GetEnumerator();
  521. }
  522. void ICollection<Column<T>>.Add(Column<T> item)
  523. {
  524. Add(item);
  525. }
  526. void ICollection<Column<T>>.Clear()
  527. {
  528. columns.Clear();
  529. }
  530. bool ICollection<Column<T>>.Contains(Column<T> item)
  531. {
  532. return Contains(item.Property);
  533. }
  534. void ICollection<Column<T>>.CopyTo(Column<T>[] array, int arrayIndex)
  535. {
  536. columns.CopyTo(array, arrayIndex);
  537. }
  538. bool ICollection<Column<T>>.Remove(Column<T> item)
  539. {
  540. return columns.RemoveAll(x => x.Property == item.Property) > 0;
  541. }
  542. #endregion
  543. }
  544. public static class ColumnsExtensions
  545. {
  546. public static Columns<T> ToColumns<T>(this IEnumerable<Column<T>> columns, ColumnTypeFlags flags)
  547. {
  548. return new Columns<T>(flags).AddRange(columns);
  549. }
  550. }
  551. public static class ColumnSerialization
  552. {
  553. /// <summary>
  554. /// Inverse of <see cref="Write{T}(CoreBinaryWriter, Columns{T}?)"/>.
  555. /// </summary>
  556. /// <param name="reader"></param>
  557. /// <returns></returns>
  558. public static Columns<T>? ReadColumns<T>(this CoreBinaryReader reader)
  559. {
  560. if (reader.ReadBoolean())
  561. {
  562. var columns = Columns.None<T>();
  563. columns.DeserializeBinary(reader);
  564. return columns;
  565. }
  566. return null;
  567. }
  568. /// <summary>
  569. /// Inverse of <see cref="ReadColumns{T}(CoreBinaryReader)"/>.
  570. /// </summary>
  571. /// <param name="filter"></param>
  572. /// <param name="writer"></param>
  573. public static void Write<T>(this CoreBinaryWriter writer, Columns<T>? columns)
  574. {
  575. if (columns is null)
  576. {
  577. writer.Write(false);
  578. }
  579. else
  580. {
  581. writer.Write(true);
  582. columns.SerializeBinary(writer);
  583. }
  584. }
  585. }
  586. public class ColumnJsonConverter : JsonConverter
  587. {
  588. public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
  589. {
  590. if(value is null)
  591. {
  592. writer.WriteNull();
  593. return;
  594. }
  595. var property = (CoreUtils.GetPropertyValue(value, "Expression") as Expression)
  596. ?? throw new Exception("'Column.Expression' may not be null");
  597. var prop = CoreUtils.ExpressionToString(value.GetType().GenericTypeArguments[0], property, true);
  598. var name = CoreUtils.GetPropertyValue(value, "Property") as string;
  599. writer.WriteStartObject();
  600. writer.WritePropertyName("$type");
  601. writer.WriteValue(value.GetType().FullName);
  602. writer.WritePropertyName("Expression");
  603. writer.WriteValue(prop);
  604. writer.WritePropertyName("Property");
  605. writer.WriteValue(name);
  606. writer.WriteEndObject();
  607. }
  608. public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
  609. {
  610. if (reader.TokenType == JsonToken.Null)
  611. return null;
  612. var data = new Dictionary<string, object>();
  613. while (reader.TokenType != JsonToken.EndObject && reader.Read())
  614. if (reader.Value != null)
  615. {
  616. var key = reader.Value.ToString();
  617. reader.Read();
  618. if (String.Equals(key, "$type"))
  619. objectType = Type.GetType(reader.Value.ToString()) ?? objectType;
  620. else
  621. data[key] = reader.Value;
  622. }
  623. var prop = data["Property"].ToString();
  624. var result = Activator.CreateInstance(objectType, prop);
  625. return result;
  626. }
  627. public override bool CanConvert(Type objectType)
  628. {
  629. if (objectType.IsConstructedGenericType)
  630. {
  631. var ot = objectType.GetGenericTypeDefinition();
  632. var tt = typeof(Column<>);
  633. if (ot == tt)
  634. return true;
  635. }
  636. return false;
  637. }
  638. }
  639. }