浏览代码

Finished changes to columns

Kenric Nugteren 1 年之前
父节点
当前提交
a5bb398c74

+ 2 - 14
InABox.Core/Column.cs

@@ -38,6 +38,8 @@ namespace InABox.Core
 
         public string Property => _property.Name;
 
+        public Expression Expression => _property.Expression();
+
         public bool IsEqualTo(string name) => 
             !name.IsNullOrWhiteSpace() && Property.Equals(name);
         public bool IsEqualTo(Column<T> column) =>
@@ -501,20 +503,6 @@ namespace InABox.Core
         }
 
         #endregion
-        
-        [Obsolete("", true)]
-        public Columns(params Expression<Func<T, object?>>[] expressions)
-        {
-            foreach (var expression in expressions)
-                columns.Add(new Column<T>(expression));
-        }
-
-        [Obsolete("", true)]
-        public Columns(IEnumerable<string> properties)
-        {
-            foreach (var property in properties)
-                columns.Add(new Column<T>(property));
-        }
 
         #region Casting Columns Type
 

+ 1 - 1
InABox.Core/CoreUtils.cs

@@ -1449,7 +1449,7 @@ namespace InABox.Core
             {
                 if (!_columnscache.TryGetValue(T, out var result))
                 {
-                    result = Columns.Create(T);
+                    result = Columns.None(T);
                     var props = PropertyList(T,
                         x =>
                             x.GetCustomAttribute<DoNotSerialize>() == null &&

+ 2 - 2
InABox.Core/DataModel/AutoDataModel.cs

@@ -75,7 +75,7 @@ namespace InABox.Core
                              x.PropertyType.GetInheritedGenericTypeArguments().FirstOrDefault() == typeof(T));
                     var target = CoreUtils.GetPropertyExpression(map, prop.Name + ".ID", typeof(object));
 
-                    var cols = Core.Columns.Create(map);
+                    var cols = Core.Columns.None(map);
                     var columnList = CoreUtils.GetColumnNames(map, 
                         x => !x.DeclaringType.IsAssignableFrom(map) || x.Name.Split('.', StringSplitOptions.None).First() != prop.Name);
                     foreach (var col in columnList) cols.Add(col);
@@ -129,7 +129,7 @@ namespace InABox.Core
 
                     var lookupAlias = headName + "_" + TableName(map);
 
-                    var cols = Core.Columns.Create(map);
+                    var cols = Core.Columns.None(map);
                     var columnList = CoreUtils.GetColumnNames(map, x => !x.DeclaringType.IsAssignableFrom(map) || x.Name != prop.Name);
                     foreach (var col in columnList) cols.Add(col);
                     cols.Add(prop.Name + ".ID");

+ 1 - 1
InABox.Core/DataModel/DataModel.cs

@@ -213,7 +213,7 @@ namespace InABox.Core
                 IColumns? additional = null;
                 if(table.Type != null)
                 {
-                    additional = Columns.Create(table.Type);
+                    additional = Columns.None(table.Type);
                     foreach (var column in CoreUtils.GetColumnNames(table.Type, x => true))
                     {
                         if (!current.ContainsKey(column))

+ 2 - 2
InABox.Core/Deletion.cs

@@ -43,11 +43,11 @@ namespace InABox.Core
 
         public static IColumns DeletionColumns(Type T)
         {
-            var columns = Columns.Create(T);
+            var columns = Columns.None(T);
             foreach(var property in DatabaseSchema.Properties(T))
             {
                 if (IsDeletionColumn(property))
-                    columns.Add(property.Name);
+                    columns.Add(property);
             }
             return columns;
         }

+ 2 - 2
InABox.Core/DigitalForms/DataModel/DigitalFormDataModel.cs

@@ -136,7 +136,7 @@ namespace InABox.Core
                 client.Add(
                     new QueryDef<TEntity>(
                         new Filter<TEntity>(x => x.ID).IsEqualTo(Entity.ID),
-                        new Columns<TEntity>(x => x.ID),
+                        Columns.None<TEntity>().Add(x => x.ID),
                         null
                     ),
                     typeof(TEntity)
@@ -146,7 +146,7 @@ namespace InABox.Core
                 client.Add(
                     new QueryDef<TInstance>(
                         new Filter<TInstance>(x => x.ID).IsEqualTo(Instance.ID),
-                        new Columns<TInstance>
+                        Columns.None<TInstance>().Add
                         (
                             x => x.ID, 
                             x => x.Number,

+ 1 - 1
InABox.Core/Filter.cs

@@ -597,7 +597,7 @@ namespace InABox.Core
             Ors = new List<Filter<T>>();
         }
 
-        public Filter(Column<T> column): base(column)
+        public Filter(Column<T> column): base(column.Expression)
         {
             Ands = new List<Filter<T>>();
             Ors = new List<Filter<T>>();

+ 3 - 3
InABox.Core/ILookupDefinition.cs

@@ -364,7 +364,7 @@ namespace InABox.Core
         /// <returns></returns>
         public static IColumns RequiredColumns(Type T)
         {
-            var result = Columns.Create(T);
+            var result = Columns.None(T);
 
             var props = DatabaseSchema.Properties(T);
             foreach (var prop in DatabaseSchema.Properties(T))
@@ -386,7 +386,7 @@ namespace InABox.Core
                         // and we don't want that.
                         if(parentLink.GetAttribute<LookupDefinitionAttribute>() is LookupDefinitionAttribute attr)
                         {
-                            foreach (var column in (Activator.CreateInstance(attr.Generator) as ILookupDefinitionGenerator)!.DefineColumns().GetColumns())
+                            foreach (var column in (Activator.CreateInstance(attr.Generator) as ILookupDefinitionGenerator)!.DefineColumns())
                             {
                                 result.Add(parentLink.Name + "." + column.Property);
                             }
@@ -399,7 +399,7 @@ namespace InABox.Core
             // it means it's probably necessary for a calculation of some kind, and therefore we should also have it when we load the object.
             if(GetDefinition(T) is ILookupDefinition definition)
             {
-                foreach(var column in definition.RequiredColumns().GetColumns())
+                foreach(var column in definition.RequiredColumns())
                 {
                     result.Add(column);
                 }

+ 1 - 4
InABox.Core/Imports/BaseImporter.cs

@@ -427,10 +427,7 @@ namespace InABox.Core
                 else
                 {
                     var client = ClientFactory.CreateClient(Type);
-                    var columns = Columns.Create(Type);
-                    foreach (var field in Fields)
-                        columns.Add(field);
-                    columns.Add("ID");
+                    var columns = Columns.None(Type).Add(Fields).Add("ID");
                     Results = client.Query(null, columns);
                 }
             }

+ 5 - 0
InABox.Core/SerializableExpression.cs

@@ -14,6 +14,11 @@ namespace InABox.Core
             Deserialize(info, context);
         }
 
+        public SerializableExpression(Expression expression)
+        {
+            Expression = expression;
+        }
+
         public SerializableExpression(Expression<Func<T, object?>> expression)
         {
             Expression = CoreUtils.ExtractMemberExpression(expression);

+ 1 - 1
InABox.Database/DatabaseUpdateScript.cs

@@ -27,7 +27,7 @@ public abstract class DatabaseUpdateScript
     {
         Logger.Send(LogType.Information, "", $"Converting {typeof(T).EntityName().Split('.').Last()}...");
         List<T> updates = new List<T>();
-        var columns = Columns.None<T>().Add(x => x.ID));
+        var columns = Columns.None<T>().Add(x => x.ID);
         foreach (var map in maps)
         {
             columns.Add(map.Old);

+ 0 - 1
InABox.Database/Stores/IStore.cs

@@ -19,7 +19,6 @@ namespace InABox.Database
         // Called once at Database Startup to initialise caches etc
         void Init();
         
-        CoreTable Query(Filter<Entity>? filter = null, Columns<Entity>? columns = null, SortOrder<Entity>? sort = null);
         CoreTable Query(IFilter? filter = null, IColumns? columns = null, ISortOrder? sort = null);
         
         void Save(Entity entity, string auditnote);

+ 2 - 7
InABox.Database/Stores/Store.cs

@@ -258,11 +258,6 @@ namespace InABox.Database
             return DoQuery(filter, columns, sort);
         }
 
-        public CoreTable Query(Filter<Entity>? filter = null, Columns<Entity>? columns = null, SortOrder<Entity>? sort = null)
-        {
-            return DoQuery((Filter<T>?)filter, columns, (SortOrder<T>)sort);
-        }
-        
         public CoreTable Query(IFilter? filter = null, IColumns? columns = null, ISortOrder? sort = null)
         {
             return DoQuery(filter as Filter<T>, columns as Columns<T>, sort as SortOrder<T>);
@@ -322,7 +317,7 @@ namespace InABox.Database
                     var newvalue = 0;
                     var row = Provider.Query(
                         filter,
-                        new Columns<T>(new[] { prop.Name }),
+                        Columns.None<T>().Add(prop.Name),
                         new SortOrder<T>(prop.Name, SortDirection.Descending),
                         1
                     ).Rows.FirstOrDefault();
@@ -366,7 +361,7 @@ namespace InABox.Database
                     
                     var row = Provider.Query(
                         autoinc.AutoIncrementFilter(), 
-                        new Columns<T>(new[] { prop.Name }), 
+                        Columns.None<T>().Add(prop.Name), 
                         new SortOrder<T>(prop.Name,SortDirection.Descending),
                         1
                     ).Rows.FirstOrDefault();

+ 9 - 9
inabox.database.sqlite/SQLiteProvider.cs

@@ -769,7 +769,7 @@ namespace InABox.Database.SQLite
                     foreach (var table in union.Tables)
                     {
 
-                        var columns = Columns.Create(table.Entity);
+                        var columns = Columns.None(table.Entity);
                         var constants = CheckDefaultColumns(union);
                         
                         var interfacefields = new Dictionary<string, string>();
@@ -1506,7 +1506,7 @@ namespace InABox.Database.SQLite
                     {
                         var subEntityType = subQuery.GetQueryType();
 
-                        var subColumns = Columns.Create(subEntityType);
+                        var subColumns = Columns.None(subEntityType);
                         var subColumn = subQuery.GetColumn();
 
                         subColumns.Add(subColumn);
@@ -1863,7 +1863,7 @@ namespace InABox.Database.SQLite
                                     }
                                 }
 
-                                var subcols = Columns.Create(linkedtype);
+                                var subcols = Columns.None(linkedtype);
 
                                 foreach (var key in agg.Links.Keys)
                                     subcols.Add(key);
@@ -2027,7 +2027,7 @@ namespace InABox.Database.SQLite
                                 if (!siblings.Contains(parent))
                                     siblings.Add(parent);
 
-                                var subcols = Columns.Create(linkedType).Add(siblings);
+                                var subcols = Columns.None(linkedType).Add(siblings);
 
                                 var subPrefix = (char)(newprefix + 1);
 
@@ -2092,7 +2092,7 @@ namespace InABox.Database.SQLite
                                     if (!siblings.Contains("ID"))
                                         siblings.Insert(0, "ID");
 
-                                    var subcols = Columns.Create(linkedType).Add(siblings);
+                                    var subcols = Columns.None(linkedType).Add(siblings);
 
                                     var linkedtable = string.Format("({0})",
                                         PrepareSelectNonGeneric(linkedType, command, newprefix, null, subcols, null, null, null, int.MaxValue, false, useparams));
@@ -2442,8 +2442,8 @@ namespace InABox.Database.SQLite
 
             var cols = CoreUtils.GetColumns(T, columns);
 
-            var blobColumns = Columns.Create(T);
-            foreach(var column in cols.GetColumns())
+            var blobColumns = Columns.None(T);
+            foreach(var column in cols)
             {
                 var prop = DatabaseSchema.Property(T, column.Property);
                 if (prop is not null && prop.HasAttribute<ExternalStorageAttribute>())
@@ -2457,7 +2457,7 @@ namespace InABox.Database.SQLite
             }
 
             var result = new CoreTable(T.EntityName());
-            foreach (var col in cols.GetColumns())
+            foreach (var col in cols)
                 result.Columns.Add(new CoreColumn { ColumnName = col.Property, DataType = col.Type });
             //LogStop("MakeTable");
 
@@ -3194,7 +3194,7 @@ namespace InABox.Database.SQLite
                     var row = DoQueryNonGeneric(
                         entityType,
                         Filter.Create<Entity>(entityType, x => x.ID).IsEqualTo(setNull.EntityID),
-                        Columns.Create(entityType)
+                        Columns.None(entityType)
                             .Add<Entity>(x => x.ID)
                             .Add(setNull.Property),
                         null,

+ 1 - 1
inabox.wpf/DigitalForms/Designer/DynamicEditFormWindow.xaml.cs

@@ -281,7 +281,7 @@ public partial class DynamicFormEditWindow : Window, IDynamicFormWindow
     }
     public static IColumns FormColumns(Type TForm)
     {
-        return Columns.Create<IDigitalFormInstance>(TForm)
+        return Columns.Create<IDigitalFormInstance>(TForm, ColumnTypeFlags.None)
             .Add<IDigitalFormInstance>(x => x.ID)
             .Add<IDigitalFormInstance>(x=>x.Number)
             .Add<IDigitalFormInstance>(x=>x.Description)

+ 1 - 1
inabox.wpf/DynamicGrid/Columns/EditorColumns/DynamicGridCodePopupColumn.cs

@@ -22,7 +22,7 @@ public class DynamicGridCodePopupColumn<TEntity> : DynamicGridEditorColumn<TEnti
     private IColumns GetLinkColumns(Type type, String prefix)
     {
         var prefixwithstop = $"{prefix.ToUpper()}.";
-        var cols = Columns.Create(type);
+        var cols = Columns.None(type);
         var props = DatabaseSchema.Properties(typeof(TEntity))
             .Where(x => x.Name.ToUpper().StartsWith(prefixwithstop));
         foreach (var prop in props)

+ 2 - 2
inabox.wpf/DynamicGrid/Columns/EditorColumns/DynamicGridPopupColumn.cs

@@ -50,7 +50,7 @@ public class DynamicGridPopupColumn<TEntity> : DynamicGridEditorColumn<TEntity,
     private IColumns GetLinkColumns(Type type, String prefix)
     {
         var prefixwithstop = $"{prefix.ToUpper()}.";
-        var cols = Columns.Create(type);
+        var cols = Columns.None(type);
         var props = DatabaseSchema.Properties(typeof(TEntity))
             .Where(x => x.Name.ToUpper().StartsWith(prefixwithstop));
         foreach (var prop in props)
@@ -97,7 +97,7 @@ public class DynamicGridPopupColumn<TEntity> : DynamicGridEditorColumn<TEntity,
         var prefix = String.Join(".", Definition.ColumnName.Split('.').Reverse().Skip(1).Reverse());
         var displaycols = new List<String>();
         var lookupcolumns = LookupFactory.DefineLookupColumns(typeof(TEntity), editor.Type, prefix);
-        foreach (var lookupcolumn in lookupcolumns.GetColumns())
+        foreach (var lookupcolumn in lookupcolumns)
         {
             var displaycol = String.IsNullOrWhiteSpace(prefix)
                 ? lookupcolumn.Property

+ 560 - 561
inabox.wpf/DynamicGrid/DynamicDataGrid.cs

@@ -14,717 +14,716 @@ using InABox.Core;
 using InABox.WPF;
 using Expression = System.Linq.Expressions.Expression;
 
-namespace InABox.DynamicGrid
-{
-    public interface IDynamicDataGrid : IDynamicGrid
-    {
-        /// <summary>
-        /// The tag the the DynamicGridColumns are stored against. If set to <see langword="null"/>,
-        /// the name of <typeparamref name="TEntity"/> is used as a default.
-        /// </summary>
-        string? ColumnsTag { get; set; }
+namespace InABox.DynamicGrid;
 
-        IColumns LoadEditorColumns();
-    }
+public interface IDynamicDataGrid : IDynamicGrid
+{
+    /// <summary>
+    /// The tag the the DynamicGridColumns are stored against. If set to <see langword="null"/>,
+    /// the name of <typeparamref name="TEntity"/> is used as a default.
+    /// </summary>
+    string? ColumnsTag { get; set; }
 
-    public class DynamicDataGrid<TEntity> : DynamicGrid<TEntity>, IDynamicDataGrid where TEntity : Entity, IRemotable, IPersistent, new()
-    {
+    IColumns LoadEditorColumns();
+}
 
-        public delegate void OnReloadEventHandler(object sender, Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sortby);
+public class DynamicDataGrid<TEntity> : DynamicGrid<TEntity>, IDynamicDataGrid where TEntity : Entity, IRemotable, IPersistent, new()
+{
 
-        private readonly int ChunkSize = 500;
-        private Button MergeBtn = null!; //Late-initialised
+    public delegate void OnReloadEventHandler(object sender, Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sortby);
 
-        public DynamicGridFilterButtonComponent<TEntity> FilterComponent;
-        protected DynamicGridCustomColumnsComponent<TEntity> ColumnsComponent;
+    private readonly int ChunkSize = 500;
+    private Button MergeBtn = null!; //Late-initialised
 
-        private Column<TEntity>[] FilterColumns;
+    public DynamicGridFilterButtonComponent<TEntity> FilterComponent;
+    protected DynamicGridCustomColumnsComponent<TEntity> ColumnsComponent;
 
-        public DynamicDataGrid() : base()
-        {
-            var fields = DatabaseSchema.Properties(typeof(TEntity));
+    private Column<TEntity>[] FilterColumns;
 
-            foreach (var field in fields)
-                if (!MasterColumns.Any(x => x.ColumnName == field.Name))
-                    MasterColumns.Add(new DynamicGridColumn { ColumnName = field.Name });
+    public DynamicDataGrid() : base()
+    {
+        var fields = DatabaseSchema.Properties(typeof(TEntity));
 
-            var cols = LookupFactory.DefineColumns<TEntity>();
+        foreach (var field in fields)
+            if (!MasterColumns.Any(x => x.ColumnName == field.Name))
+                MasterColumns.Add(new DynamicGridColumn { ColumnName = field.Name });
 
-            // Minimum Columns for Lookup values
-            foreach (var col in cols)
-                HiddenColumns.Add(CoreUtils.CreateLambdaExpression<TEntity>(col.Property));
+        var cols = LookupFactory.DefineColumns<TEntity>();
 
-            // Minimum Columns for Successful Saving
-            // This should be cross-checked with the relevant Store<>
-            // so that clients will (usually) provide sufficient columns for saving
-            foreach (var col in LookupFactory.RequiredColumns<TEntity>())
-                HiddenColumns.Add(CoreUtils.CreateLambdaExpression<TEntity>(col.Property));
+        // Minimum Columns for Lookup values
+        foreach (var col in cols)
+            HiddenColumns.Add(CoreUtils.CreateLambdaExpression<TEntity>(col.Property));
 
-            //HiddenColumns.Add(x => x.ID);
+        // Minimum Columns for Successful Saving
+        // This should be cross-checked with the relevant Store<>
+        // so that clients will (usually) provide sufficient columns for saving
+        foreach (var col in LookupFactory.RequiredColumns<TEntity>())
+            HiddenColumns.Add(CoreUtils.CreateLambdaExpression<TEntity>(col.Property));
 
-            if (typeof(TEntity).GetInterfaces().Contains(typeof(IIssues)))
-            {
-                HiddenColumns.Add(x => (x as IIssues)!.Issues);
-                var coltype = typeof(DynamicIssuesColumn<>).MakeGenericType(typeof(TEntity));
-                ActionColumns.Add((Activator.CreateInstance(coltype, this) as DynamicActionColumn)!);
-            }
+        //HiddenColumns.Add(x => x.ID);
 
-            SetupFilterColumns();
+        if (typeof(TEntity).GetInterfaces().Contains(typeof(IIssues)))
+        {
+            HiddenColumns.Add(x => (x as IIssues)!.Issues);
+            var coltype = typeof(DynamicIssuesColumn<>).MakeGenericType(typeof(TEntity));
+            ActionColumns.Add((Activator.CreateInstance(coltype, this) as DynamicActionColumn)!);
         }
 
-        protected override void Init()
-        {
-            FilterComponent = new(this,
-                new GlobalConfiguration<CoreFilterDefinitions>(GetTag()),
-                new UserConfiguration<CoreFilterDefinitions>(GetTag()));
-            FilterComponent.OnFilterRefresh += () => Refresh(false, true);
+        SetupFilterColumns();
+    }
 
-            ColumnsComponent = new DynamicGridCustomColumnsComponent<TEntity>(this, GetTag());
+    protected override void Init()
+    {
+        FilterComponent = new(this,
+            new GlobalConfiguration<CoreFilterDefinitions>(GetTag()),
+            new UserConfiguration<CoreFilterDefinitions>(GetTag()));
+        FilterComponent.OnFilterRefresh += () => Refresh(false, true);
 
-            MergeBtn = AddButton("Merge", Wpf.Resources.merge.AsBitmapImage(Color.White), DoMerge);
-        }
-        protected override void DoReconfigure(FluentList<DynamicGridOption> options)
-        {
-            options.BeginUpdate();
-            if (Security.CanEdit<TEntity>())
-                options.Add(DynamicGridOption.AddRows).Add(DynamicGridOption.EditRows);
-            if (Security.CanDelete<TEntity>())
-                options.Add(DynamicGridOption.DeleteRows);
-            if (Security.CanImport<TEntity>() && typeof(TEntity).HasInterface<IImportable>())
-                options.Add(DynamicGridOption.ImportData);
-            if (Security.CanExport<TEntity>() && typeof(TEntity).HasInterface<IExportable>())
-                options.Add(DynamicGridOption.ExportData);
-            if (Security.CanMerge<TEntity>())
-                options.Add(DynamicGridOption.MultiSelect);
-            options.EndUpdate();
-        }
+        ColumnsComponent = new DynamicGridCustomColumnsComponent<TEntity>(this, GetTag());
+
+        MergeBtn = AddButton("Merge", Wpf.Resources.merge.AsBitmapImage(Color.White), DoMerge);
+    }
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        options.BeginUpdate();
+        if (Security.CanEdit<TEntity>())
+            options.Add(DynamicGridOption.AddRows).Add(DynamicGridOption.EditRows);
+        if (Security.CanDelete<TEntity>())
+            options.Add(DynamicGridOption.DeleteRows);
+        if (Security.CanImport<TEntity>() && typeof(TEntity).HasInterface<IImportable>())
+            options.Add(DynamicGridOption.ImportData);
+        if (Security.CanExport<TEntity>() && typeof(TEntity).HasInterface<IExportable>())
+            options.Add(DynamicGridOption.ExportData);
+        if (Security.CanMerge<TEntity>())
+            options.Add(DynamicGridOption.MultiSelect);
+        options.EndUpdate();
+    }
 
-        [MemberNotNull(nameof(FilterColumns))]
-        private void SetupFilterColumns()
+    [MemberNotNull(nameof(FilterColumns))]
+    private void SetupFilterColumns()
+    {
+        if (typeof(TEntity).GetCustomAttribute<AutoEntity>() is AutoEntity auto)
         {
-            if (typeof(TEntity).GetCustomAttribute<AutoEntity>() is AutoEntity auto)
+            if (auto.Generator is not null)
             {
-                if (auto.Generator is not null)
-                {
-                    var columns = auto.Generator.IDColumns;
-                    FilterColumns = columns.Select(x => new Column<TEntity>(x.Property)).ToArray();
-                }
-                else
-                {
-                    FilterColumns = Array.Empty<Column<TEntity>>();
-                }
+                var columns = auto.Generator.IDColumns;
+                FilterColumns = columns.Select(x => new Column<TEntity>(x.Property)).ToArray();
             }
             else
             {
-                FilterColumns = new[] { new Column<TEntity>(x => x.ID) };
-            }
-
-            foreach (var column in FilterColumns)
-            {
-                AddHiddenColumn(column.Property);
+                FilterColumns = Array.Empty<Column<TEntity>>();
             }
         }
-
-        protected override void BeforeLoad(IDynamicEditorForm form, TEntity[] items)
+        else
         {
-            form.ReadOnly = form.ReadOnly || !Security.CanEdit<TEntity>();
-            base.BeforeLoad(form, items);
+            FilterColumns = new[] { new Column<TEntity>(x => x.ID) };
         }
 
-        private string? _columnsTag;
-        public string? ColumnsTag
+        foreach (var column in FilterColumns)
         {
-            get => _columnsTag;
-            set
-            {
-                _columnsTag = value;
-                ColumnsComponent.Tag = GetTag();
-            }
+            AddHiddenColumn(column.Property);
         }
+    }
 
-        protected override void OptionsChanged()
-        {
-            base.OptionsChanged();
-            if (MergeBtn != null)
-                MergeBtn.Visibility = Visibility.Collapsed;
-
-            FilterComponent.ShowFilterList = HasOption(DynamicGridOption.FilterRows) && !HasOption(DynamicGridOption.HideDatabaseFilters);
-        }
+    protected override void BeforeLoad(IDynamicEditorForm form, TEntity[] items)
+    {
+        form.ReadOnly = form.ReadOnly || !Security.CanEdit<TEntity>();
+        base.BeforeLoad(form, items);
+    }
 
-        protected override void SelectItems(CoreRow[]? rows)
+    private string? _columnsTag;
+    public string? ColumnsTag
+    {
+        get => _columnsTag;
+        set
         {
-            base.SelectItems(rows);
-            MergeBtn.Visibility = HasOption(DynamicGridOption.MultiSelect) && typeof(TEntity).IsAssignableTo(typeof(IMergeable)) && Security.CanMerge<TEntity>() && rows != null && rows.Length > 1
-                ? Visibility.Visible
-                : Visibility.Collapsed;
+            _columnsTag = value;
+            ColumnsComponent.Tag = GetTag();
         }
-        
-        public event OnReloadEventHandler? OnReload;
+    }
 
-        protected override void Reload(Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sort,
-            Action<CoreTable?, Exception?> action)
-        {
-            criteria.Add(FilterComponent.GetFilter());
+    protected override void OptionsChanged()
+    {
+        base.OptionsChanged();
+        if (MergeBtn != null)
+            MergeBtn.Visibility = Visibility.Collapsed;
 
-            OnReload?.Invoke(this, criteria, columns, ref sort);
-            new Client<TEntity>().Query(criteria.Combine(), columns, sort, action);
-        }
+        FilterComponent.ShowFilterList = HasOption(DynamicGridOption.FilterRows) && !HasOption(DynamicGridOption.HideDatabaseFilters);
+    }
 
-        private CoreRow[]? SelectedBeforeRefresh;
-        protected override void OnAfterRefresh()
-        {
-            base.OnAfterRefresh();
+    protected override void SelectItems(CoreRow[]? rows)
+    {
+        base.SelectItems(rows);
+        MergeBtn.Visibility = HasOption(DynamicGridOption.MultiSelect) && typeof(TEntity).IsAssignableTo(typeof(IMergeable)) && Security.CanMerge<TEntity>() && rows != null && rows.Length > 1
+            ? Visibility.Visible
+            : Visibility.Collapsed;
+    }
+    
+    public event OnReloadEventHandler? OnReload;
 
-            if (SelectedBeforeRefresh is not null)
-            {
-                var selectedValues = SelectedBeforeRefresh
-                    .Select(x => FilterColumns.Select(c => x[c.Property]).ToList())
-                    .ToList();
+    protected override void Reload(Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sort,
+        Action<CoreTable?, Exception?> action)
+    {
+        criteria.Add(FilterComponent.GetFilter());
 
-                SelectedBeforeRefresh = null;
+        OnReload?.Invoke(this, criteria, columns, ref sort);
+        new Client<TEntity>().Query(criteria.Combine(), columns, sort, action);
+    }
 
-                var selectedRows = Data.Rows.Where(r =>
-                {
-                    return selectedValues.Any(v =>
-                    {
-                        for (int i = 0; i < v.Count; ++i)
-                        {
-                            if (v[i]?.Equals(r[FilterColumns[i].Property]) != true)
-                                return false;
-                        }
-                        return true;
-                    });
-                }).ToArray();
-
-                SelectedRows = selectedRows;
-                SelectItems(selectedRows);
-            }
-        }
+    private CoreRow[]? SelectedBeforeRefresh;
+    protected override void OnAfterRefresh()
+    {
+        base.OnAfterRefresh();
 
-        public override void Refresh(bool reloadcolumns, bool reloaddata)
+        if (SelectedBeforeRefresh is not null)
         {
-            SelectedBeforeRefresh = SelectedRows;
-            base.Refresh(reloadcolumns, reloaddata);
-        }
+            var selectedValues = SelectedBeforeRefresh
+                .Select(x => FilterColumns.Select(c => x[c.Property]).ToList())
+                .ToList();
 
-        IColumns IDynamicDataGrid.LoadEditorColumns() => LoadEditorColumns();
+            SelectedBeforeRefresh = null;
 
-        public Columns<TEntity> LoadEditorColumns()
-        {
-            return DynamicGridUtils.LoadEditorColumns(DataColumns());
-        }
-
-        private Filter<TEntity>? GetRowFilter(CoreRow row)
-        {
-            var newFilters = new Filters<TEntity>();
-            foreach (var column in FilterColumns)
+            var selectedRows = Data.Rows.Where(r =>
             {
-                newFilters.Add(new Filter<TEntity>(column.Property).IsEqualTo(row[column.Property]));
-            }
-            return newFilters.Combine();
-        }
-
-        public override TEntity[] LoadItems(CoreRow[] rows)
-        {
-            Filter<TEntity>? filter = null;
-            var results = new List<TEntity>();
-            for (var i = 0; i < rows.Length; i += ChunkSize)
-            {
-                var chunk = rows.Skip(i).Take(ChunkSize);
-                foreach (var row in chunk)
+                return selectedValues.Any(v =>
                 {
-                    var newFilter = GetRowFilter(row);
-
-                    if (newFilter is not null)
+                    for (int i = 0; i < v.Count; ++i)
                     {
-                        //var id = row.Get<TEntity, Guid>(x => x.ID);
-                        if (filter is null)
-                            filter = newFilter;//new Filter<TEntity>(x => x.ID).IsEqualTo(id);
-                        else
-                            filter = filter.Or(newFilter);//.Or(x => x.ID).IsEqualTo(id);
+                        if (v[i]?.Equals(r[FilterColumns[i].Property]) != true)
+                            return false;
                     }
-                }
-
-                var columns = LoadEditorColumns(); // new Columns<TEntity>().Default(ColumnType.IncludeOptional, ColumnType.IncludeForeignKeys, ColumnType.IncludeUserProperties);
-                var data = Client.Query(filter, columns);
-                results.AddRange(data.ToObjects<TEntity>());
-            }
+                    return true;
+                });
+            }).ToArray();
 
-            return results.ToArray();
+            SelectedRows = selectedRows;
+            SelectItems(selectedRows);
         }
+    }
 
-        public override TEntity LoadItem(CoreRow row)
-        {
-            var id = row.Get<TEntity, Guid>(x => x.ID);
-            var filter = GetRowFilter(row);//new Filter<TEntity>(x => x.ID).IsEqualTo(id);
-            return Client.Query(filter, LoadEditorColumns()).ToObjects<TEntity>().FirstOrDefault()
-                ?? throw new Exception($"No {typeof(TEntity).Name} with ID {id}");
-        }
+    public override void Refresh(bool reloadcolumns, bool reloaddata)
+    {
+        SelectedBeforeRefresh = SelectedRows;
+        base.Refresh(reloadcolumns, reloaddata);
+    }
 
-        public override void SaveItem(TEntity item)
-        {
-            Client.Save(item, "Edited by User");
-        }
+    IColumns IDynamicDataGrid.LoadEditorColumns() => LoadEditorColumns();
+
+    public Columns<TEntity> LoadEditorColumns()
+    {
+        return DynamicGridUtils.LoadEditorColumns(DataColumns());
+    }
 
-        public override void SaveItems(TEntity[] items)
+    private Filter<TEntity>? GetRowFilter(CoreRow row)
+    {
+        var newFilters = new Filters<TEntity>();
+        foreach (var column in FilterColumns)
         {
-            Client.Save(items, "Edited by User");
+            newFilters.Add(new Filter<TEntity>(column.Property).IsEqualTo(row[column.Property]));
         }
+        return newFilters.Combine();
+    }
 
-        public override void DeleteItems(params CoreRow[] rows)
+    public override TEntity[] LoadItems(CoreRow[] rows)
+    {
+        Filter<TEntity>? filter = null;
+        var results = new List<TEntity>();
+        for (var i = 0; i < rows.Length; i += ChunkSize)
         {
-            var deletes = new List<TEntity>();
-            foreach (var row in rows)
+            var chunk = rows.Skip(i).Take(ChunkSize);
+            foreach (var row in chunk)
             {
-                var delete = new TEntity();
-                foreach (var column in FilterColumns)
+                var newFilter = GetRowFilter(row);
+
+                if (newFilter is not null)
                 {
-                    CoreUtils.SetPropertyValue(delete, column.Property, row[column.Property]);
+                    //var id = row.Get<TEntity, Guid>(x => x.ID);
+                    if (filter is null)
+                        filter = newFilter;//new Filter<TEntity>(x => x.ID).IsEqualTo(id);
+                    else
+                        filter = filter.Or(newFilter);//.Or(x => x.ID).IsEqualTo(id);
                 }
-
-                //var delete = /* row.ToObject<TEntity>(); */ 
-                deletes.Add(delete);
             }
 
-            Client.Delete(deletes, "Deleted on User Request");
+            var columns = LoadEditorColumns(); // new Columns<TEntity>().Default(ColumnType.IncludeOptional, ColumnType.IncludeForeignKeys, ColumnType.IncludeUserProperties);
+            var data = Client.Query(filter, columns);
+            results.AddRange(data.ToObjects<TEntity>());
         }
 
-        private object GetPropertyValue(Expression member, object obj)
-        {
-            var objectMember = Expression.Convert(member, typeof(object));
+        return results.ToArray();
+    }
 
-            var getterLambda = Expression.Lambda<Func<object>>(objectMember);
+    public override TEntity LoadItem(CoreRow row)
+    {
+        var id = row.Get<TEntity, Guid>(x => x.ID);
+        var filter = GetRowFilter(row);//new Filter<TEntity>(x => x.ID).IsEqualTo(id);
+        return Client.Query(filter, LoadEditorColumns()).ToObjects<TEntity>().FirstOrDefault()
+            ?? throw new Exception($"No {typeof(TEntity).Name} with ID {id}");
+    }
 
-            var getter = getterLambda.Compile();
+    public override void SaveItem(TEntity item)
+    {
+        Client.Save(item, "Edited by User");
+    }
 
-            return getter();
-        }
+    public override void SaveItems(TEntity[] items)
+    {
+        Client.Save(items, "Edited by User");
+    }
 
-        protected override void ObjectToRow(TEntity obj, CoreRow row)
+    public override void DeleteItems(params CoreRow[] rows)
+    {
+        var deletes = new List<TEntity>();
+        foreach (var row in rows)
         {
-            foreach (var column in Data.Columns)
+            var delete = new TEntity();
+            foreach (var column in FilterColumns)
             {
-                var prop = DatabaseSchema.Property(obj.GetType(), column.ColumnName);
-                if (prop is StandardProperty)
-                    row.Set(column.ColumnName, CoreUtils.GetPropertyValue(obj, prop.Name));
-                else if (prop is CustomProperty)
-                    row.Set(column.ColumnName, obj.UserProperties[column.ColumnName]);
+                CoreUtils.SetPropertyValue(delete, column.Property, row[column.Property]);
             }
 
-            //base.ObjectToRow(obj, row);
+            //var delete = /* row.ToObject<TEntity>(); */ 
+            deletes.Add(delete);
         }
 
-        //private void Auditgrid_OnReload(object sender, Dictionary<string, object> criteria, ref String sort)
-        //{
-        //	criteria["DocumentType"] = typeof(TEntity).EntityName();
-        //	criteria["DocumentID"] = (sender as FrameworkElement).Tag;
+        Client.Delete(deletes, "Deleted on User Request");
+    }
 
-        //	sort = "TimeStamp";
-        //}
+    private object GetPropertyValue(Expression member, object obj)
+    {
+        var objectMember = Expression.Convert(member, typeof(object));
 
-        public override void LoadEditorButtons(TEntity item, DynamicEditorButtons buttons)
-        {
-            base.LoadEditorButtons(item, buttons);
-            if (ClientFactory.IsSupported<AuditTrail>())
-                buttons.Add("Audit Trail", Wpf.Resources.view.AsBitmapImage(), item, AuditTrailClick);
-        }
+        var getterLambda = Expression.Lambda<Func<object>>(objectMember);
 
-        private void AuditTrailClick(object sender, object item)
-        {
-            var entity = (TEntity)item;
-            var window = new AuditWindow(entity.ID);
-            window.ShowDialog();
-        }
+        var getter = getterLambda.Compile();
 
-        protected override DynamicGridColumns LoadColumns()
-        {
-            return ColumnsComponent.LoadColumns();
-        }
+        return getter();
+    }
 
-        protected override void SaveColumns(DynamicGridColumns columns)
+    protected override void ObjectToRow(TEntity obj, CoreRow row)
+    {
+        foreach (var column in Data.Columns)
         {
-            ColumnsComponent.SaveColumns(columns);
+            var prop = DatabaseSchema.Property(obj.GetType(), column.ColumnName);
+            if (prop is StandardProperty)
+                row.Set(column.ColumnName, CoreUtils.GetPropertyValue(obj, prop.Name));
+            else if (prop is CustomProperty)
+                row.Set(column.ColumnName, obj.UserProperties[column.ColumnName]);
         }
 
-        protected override void LoadColumnsMenu(ContextMenu menu)
-        {
-            base.LoadColumnsMenu(menu);
-            ColumnsComponent.LoadColumnsMenu(menu);
-        }
+        //base.ObjectToRow(obj, row);
+    }
 
-        private string GetTag()
-        {
-            var tag = typeof(TEntity).Name;
-            if (!string.IsNullOrWhiteSpace(ColumnsTag))
-                tag = string.Format("{0}.{1}", tag, ColumnsTag);
-            return tag;
-        }
+    //private void Auditgrid_OnReload(object sender, Dictionary<string, object> criteria, ref String sort)
+    //{
+    //	criteria["DocumentType"] = typeof(TEntity).EntityName();
+    //	criteria["DocumentID"] = (sender as FrameworkElement).Tag;
 
-        protected override DynamicGridSettings LoadSettings()
-        {
-            var tag = GetTag();
+    //	sort = "TimeStamp";
+    //}
 
-            var user = Task.Run(() => new UserConfiguration<DynamicGridSettings>(tag).Load());
-            user.Wait();
+    public override void LoadEditorButtons(TEntity item, DynamicEditorButtons buttons)
+    {
+        base.LoadEditorButtons(item, buttons);
+        if (ClientFactory.IsSupported<AuditTrail>())
+            buttons.Add("Audit Trail", Wpf.Resources.view.AsBitmapImage(), item, AuditTrailClick);
+    }
 
-            //var global = Task.Run(() => new GlobalConfiguration<DynamicGridSettings>(tag).Load());
-            //global.Wait();
-            //Task.WaitAll(user, global);
-            //var columns = user.Result.Any() ? user.Result : global.Result;
+    private void AuditTrailClick(object sender, object item)
+    {
+        var entity = (TEntity)item;
+        var window = new AuditWindow(entity.ID);
+        window.ShowDialog();
+    }
 
-            return user.Result;
-        }
-        protected override void SaveSettings(DynamicGridSettings settings)
-        {
-            var tag = GetTag();
-            new UserConfiguration<DynamicGridSettings>(tag).Save(settings);
-        }
+    protected override DynamicGridColumns LoadColumns()
+    {
+        return ColumnsComponent.LoadColumns();
+    }
 
-        protected override BaseEditor? GetEditor(object item, DynamicGridColumn column)
-        {
-            var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == column.ColumnName);
-            if (prop != null)
-                return prop.Editor;
-            return base.GetEditor(item, column);
-        }
+    protected override void SaveColumns(DynamicGridColumns columns)
+    {
+        ColumnsComponent.SaveColumns(columns);
+    }
 
-        protected override object? GetEditorValue(object item, string name)
-        {
-            if (item is TEntity entity)
-            {
-                var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == name);
-                if (prop is CustomProperty)
-                {
-                    if (entity.UserProperties.ContainsKey(name))
-                        return entity.UserProperties[name];
-                    return null;
-                }
-            }
+    protected override void LoadColumnsMenu(ContextMenu menu)
+    {
+        base.LoadColumnsMenu(menu);
+        ColumnsComponent.LoadColumnsMenu(menu);
+    }
 
-            return base.GetEditorValue(item, name);
-        }
+    private string GetTag()
+    {
+        var tag = typeof(TEntity).Name;
+        if (!string.IsNullOrWhiteSpace(ColumnsTag))
+            tag = string.Format("{0}.{1}", tag, ColumnsTag);
+        return tag;
+    }
+
+    protected override DynamicGridSettings LoadSettings()
+    {
+        var tag = GetTag();
+
+        var user = Task.Run(() => new UserConfiguration<DynamicGridSettings>(tag).Load());
+        user.Wait();
+
+        //var global = Task.Run(() => new GlobalConfiguration<DynamicGridSettings>(tag).Load());
+        //global.Wait();
+        //Task.WaitAll(user, global);
+        //var columns = user.Result.Any() ? user.Result : global.Result;
+
+        return user.Result;
+    }
+    protected override void SaveSettings(DynamicGridSettings settings)
+    {
+        var tag = GetTag();
+        new UserConfiguration<DynamicGridSettings>(tag).Save(settings);
+    }
+
+    protected override BaseEditor? GetEditor(object item, DynamicGridColumn column)
+    {
+        var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == column.ColumnName);
+        if (prop != null)
+            return prop.Editor;
+        return base.GetEditor(item, column);
+    }
 
-        protected override void SetEditorValue(object item, string name, object value)
+    protected override object? GetEditorValue(object item, string name)
+    {
+        if (item is TEntity entity)
         {
             var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == name);
-            if (prop is CustomProperty && item is TEntity entity)
+            if (prop is CustomProperty)
             {
-                entity.UserProperties[name] = value;
-            }
-            else
-            {
-                base.SetEditorValue(item, name, value);
+                if (entity.UserProperties.ContainsKey(name))
+                    return entity.UserProperties[name];
+                return null;
             }
         }
 
-        protected bool Duplicate(
-            CoreRow row,
-            Expression<Func<TEntity, object?>> codefield,
-            Type[] childtypes)
+        return base.GetEditorValue(item, name);
+    }
+
+    protected override void SetEditorValue(object item, string name, object value)
+    {
+        var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == name);
+        if (prop is CustomProperty && item is TEntity entity)
         {
-            var id = row.Get<TEntity, Guid>(x => x.ID);
+            entity.UserProperties[name] = value;
+        }
+        else
+        {
+            base.SetEditorValue(item, name, value);
+        }
+    }
 
-            var code = row.Get(codefield) as string;
+    protected bool Duplicate(
+        CoreRow row,
+        Expression<Func<TEntity, object?>> codefield,
+        Type[] childtypes)
+    {
+        var id = row.Get<TEntity, Guid>(x => x.ID);
+
+        var code = row.Get(codefield) as string;
 
-            var tasks = new List<Task>();
+        var tasks = new List<Task>();
+
+        var itemtask = Task.Run(() =>
+        {
+            var filter = new Filter<TEntity>(x => x.ID).IsEqualTo(id);
+            var result = new Client<TEntity>().Load(filter).FirstOrDefault()
+                ?? throw new Exception("Entity does not exist!");
+            return result;
+        });
+        tasks.Add(itemtask);
+        //itemtask.Wait();
 
-            var itemtask = Task.Run(() =>
+        Task<List<string?>>? codetask = null;
+        if (!string.IsNullOrWhiteSpace(code))
+        {
+            codetask = Task.Run(() =>
             {
-                var filter = new Filter<TEntity>(x => x.ID).IsEqualTo(id);
-                var result = new Client<TEntity>().Load(filter).FirstOrDefault()
-                    ?? throw new Exception("Entity does not exist!");
+                var columns = Columns.None<TEntity>().Add(codefield);
+                //columns.Add<String>(codefield);
+                var filter = new Filter<TEntity>(codefield).BeginsWith(code);
+                var table = new Client<TEntity>().Query(filter, columns);
+                var result = table.Rows.Select(x => x.Get(codefield) as string).ToList();
                 return result;
             });
-            tasks.Add(itemtask);
-            //itemtask.Wait();
+            tasks.Add(codetask);
+        }
+        //codetask.Wait();
 
-            Task<List<string?>>? codetask = null;
-            if (!string.IsNullOrWhiteSpace(code))
+        var children = new Dictionary<Type, CoreTable>();
+        foreach (var childtype in childtypes)
+        {
+            var childtask = Task.Run(() =>
             {
-                codetask = Task.Run(() =>
-                {
-                    var columns = Columns.None<TEntity>().Add(codefield);
-                    //columns.Add<String>(codefield);
-                    var filter = new Filter<TEntity>(codefield).BeginsWith(code);
-                    var table = new Client<TEntity>().Query(filter, columns);
-                    var result = table.Rows.Select(x => x.Get(codefield) as string).ToList();
-                    return result;
-                });
-                tasks.Add(codetask);
-            }
-            //codetask.Wait();
+                var prop = childtype.GetProperties().FirstOrDefault(x =>
+                    x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)) &&
+                    x.PropertyType.GetInheritedGenericTypeArguments().FirstOrDefault() == typeof(TEntity));
 
-            var children = new Dictionary<Type, CoreTable>();
-            foreach (var childtype in childtypes)
-            {
-                var childtask = Task.Run(() =>
+                if (prop is not null)
                 {
-                    var prop = childtype.GetProperties().FirstOrDefault(x =>
-                        x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)) &&
-                        x.PropertyType.GetInheritedGenericTypeArguments().FirstOrDefault() == typeof(TEntity));
-
-                    if (prop is not null)
+                    var filter = Core.Filter.Create(childtype);
+                    filter.Expression = CoreUtils.GetMemberExpression(childtype, prop.Name + ".ID");
+                    filter.Operator = Operator.IsEqualTo;
+                    filter.Value = id;
+                    var sort = LookupFactory.DefineSort(childtype);
+                    var client = ClientFactory.CreateClient(childtype);
+                    var table = client.Query(filter, null, sort);
+                    foreach (var r in table.Rows)
                     {
-                        var filter = Core.Filter.Create(childtype);
-                        filter.Expression = CoreUtils.GetMemberExpression(childtype, prop.Name + ".ID");
-                        filter.Operator = Operator.IsEqualTo;
-                        filter.Value = id;
-                        var sort = LookupFactory.DefineSort(childtype);
-                        var client = ClientFactory.CreateClient(childtype);
-                        var table = client.Query(filter, null, sort);
-                        foreach (var r in table.Rows)
-                        {
-                            r["ID"] = Guid.Empty;
-                            r[prop.Name + ".ID"] = Guid.Empty;
-                        }
-
-                        children[childtype] = table;
+                        r["ID"] = Guid.Empty;
+                        r[prop.Name + ".ID"] = Guid.Empty;
                     }
-                    else
-                    {
-                        Logger.Send(LogType.Error, "", $"DynamicDataGrid<{typeof(TEntity)}>.Duplicate(): No parent property found for child type {childtype}");
-                    }
-                });
-                tasks.Add(childtask);
-                //childtask.Wait();
-            }
 
-            //var manytomanys = CoreUtils.TypeList(
-            //    AppDomain.CurrentDomain.GetAssemblies(),
-            //    x => x.GetInterfaces().Any(intf => intf.IsGenericType && intf.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && intf.GenericTypeArguments.Contains(typeof(T)))
-            //);
-
-            //Task<CoreTable> childtask = Task.Run(() =>
-            //{
-            //    var result = new CoreTable();
-            //    result.LoadColumns(typeof(TChild));
-            //    var children = new Client<TChild>().Load(new Filter<TChild>(x => linkfield).IsEqualTo(id));
-            //    foreach (var child in children)
-            //    {
-            //        child.ID = Guid.Empty;
-            //        String linkprop = CoreUtils.GetFullPropertyName<TChild, Guid>(linkfield, ".");
-            //        CoreUtils.SetPropertyValue(child, linkprop, Guid.Empty);
-            //        var newrow = result.NewRow();
-            //        result.LoadRow(newrow, child);
-            //        result.Rows.Add(newrow);
-            //    }
-            //    return result;
-            //});
-            //tasks.Add(childtask);
-
-            Task.WaitAll(tasks.ToArray());
-
-            var item = itemtask.Result;
-            item.ID = Guid.Empty;
-
-            if (codetask != null)
-            {
-                var codes = codetask.Result;
-                var i = 1;
+                    children[childtype] = table;
+                }
+                else
+                {
+                    Logger.Send(LogType.Error, "", $"DynamicDataGrid<{typeof(TEntity)}>.Duplicate(): No parent property found for child type {childtype}");
+                }
+            });
+            tasks.Add(childtask);
+            //childtask.Wait();
+        }
 
-                while (codes.Contains(string.Format("{0} ({1})", code, i)))
-                    i++;
-                var codeprop = CoreUtils.GetFullPropertyName(codefield, ".");
-                CoreUtils.SetPropertyValue(item, codeprop, string.Format("{0} ({1})", code, i));
-            }
+        //var manytomanys = CoreUtils.TypeList(
+        //    AppDomain.CurrentDomain.GetAssemblies(),
+        //    x => x.GetInterfaces().Any(intf => intf.IsGenericType && intf.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && intf.GenericTypeArguments.Contains(typeof(T)))
+        //);
 
-            var grid = new DynamicDataGrid<TEntity>();
-            return grid.EditItems(new[] { item }, t => children.ContainsKey(t) ? children[t] : null, true);
-        }
+        //Task<CoreTable> childtask = Task.Run(() =>
+        //{
+        //    var result = new CoreTable();
+        //    result.LoadColumns(typeof(TChild));
+        //    var children = new Client<TChild>().Load(new Filter<TChild>(x => linkfield).IsEqualTo(id));
+        //    foreach (var child in children)
+        //    {
+        //        child.ID = Guid.Empty;
+        //        String linkprop = CoreUtils.GetFullPropertyName<TChild, Guid>(linkfield, ".");
+        //        CoreUtils.SetPropertyValue(child, linkprop, Guid.Empty);
+        //        var newrow = result.NewRow();
+        //        result.LoadRow(newrow, child);
+        //        result.Rows.Add(newrow);
+        //    }
+        //    return result;
+        //});
+        //tasks.Add(childtask);
+
+        Task.WaitAll(tasks.ToArray());
+
+        var item = itemtask.Result;
+        item.ID = Guid.Empty;
+
+        if (codetask != null)
+        {
+            var codes = codetask.Result;
+            var i = 1;
+
+            while (codes.Contains(string.Format("{0} ({1})", code, i)))
+                i++;
+            var codeprop = CoreUtils.GetFullPropertyName(codefield, ".");
+            CoreUtils.SetPropertyValue(item, codeprop, string.Format("{0} ({1})", code, i));
+        }
+
+        var grid = new DynamicDataGrid<TEntity>();
+        return grid.EditItems(new[] { item }, t => children.ContainsKey(t) ? children[t] : null, true);
+    }
 
-        private bool DoMerge(Button arg1, CoreRow[] arg2)
-        {
-            if (arg2 == null || arg2.Length <= 1)
-                return false;
-            var targetid = arg2.Last().Get<TEntity, Guid>(x => x.ID);
-            var target = arg2.Last().ToObject<TEntity>().ToString();
-            var otherids = arg2.Select(r => r.Get<TEntity, Guid>(x => x.ID)).Where(x => x != targetid).ToArray();
-            string[] others = arg2.Where(r => otherids.Contains(r.Get<Guid>("ID"))).Select(x => x.ToObject<TEntity>().ToString()!).ToArray();
-            var rows = arg2.Length;
-            if (MessageBox.Show(
-                    string.Format(
-                        "This will merge the following items:\n\n- {0}\n\n into:\n\n- {1}\n\nAfter this, the items will be permanently removed.\nAre you sure you wish to do this?",
-                        string.Join("\n- ", others),
-                        target
-                    ),
-                    "Merge Items Warning",
-                    MessageBoxButton.YesNo,
-                    MessageBoxImage.Stop) != MessageBoxResult.Yes
-               )
-                return false;
-
-            using (new WaitCursor())
+    private bool DoMerge(Button arg1, CoreRow[] arg2)
+    {
+        if (arg2 == null || arg2.Length <= 1)
+            return false;
+        var targetid = arg2.Last().Get<TEntity, Guid>(x => x.ID);
+        var target = arg2.Last().ToObject<TEntity>().ToString();
+        var otherids = arg2.Select(r => r.Get<TEntity, Guid>(x => x.ID)).Where(x => x != targetid).ToArray();
+        string[] others = arg2.Where(r => otherids.Contains(r.Get<Guid>("ID"))).Select(x => x.ToObject<TEntity>().ToString()!).ToArray();
+        var rows = arg2.Length;
+        if (MessageBox.Show(
+                string.Format(
+                    "This will merge the following items:\n\n- {0}\n\n into:\n\n- {1}\n\nAfter this, the items will be permanently removed.\nAre you sure you wish to do this?",
+                    string.Join("\n- ", others),
+                    target
+                ),
+                "Merge Items Warning",
+                MessageBoxButton.YesNo,
+                MessageBoxImage.Stop) != MessageBoxResult.Yes
+           )
+            return false;
+
+        using (new WaitCursor())
+        {
+            var types = CoreUtils.TypeList(
+                AppDomain.CurrentDomain.GetAssemblies(),
+                x =>
+                    x.IsClass
+                    && !x.IsAbstract
+                    && !x.IsGenericType
+                    && x.IsSubclassOf(typeof(Entity))
+                    && !x.Equals(typeof(AuditTrail))
+                    && !x.Equals(typeof(TEntity))
+                    && x.GetCustomAttribute<AutoEntity>() == null
+                    && x.HasInterface<IRemotable>()
+                    && x.HasInterface<IPersistent>()
+            ).ToArray();
+
+            foreach (var type in types)
             {
-                var types = CoreUtils.TypeList(
-                    AppDomain.CurrentDomain.GetAssemblies(),
+                var props = CoreUtils.PropertyList(
+                    type,
                     x =>
-                        x.IsClass
-                        && !x.IsAbstract
-                        && !x.IsGenericType
-                        && x.IsSubclassOf(typeof(Entity))
-                        && !x.Equals(typeof(AuditTrail))
-                        && !x.Equals(typeof(TEntity))
-                        && x.GetCustomAttribute<AutoEntity>() == null
-                        && x.HasInterface<IRemotable>()
-                        && x.HasInterface<IPersistent>()
-                ).ToArray();
-
-                foreach (var type in types)
+                        x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink))
+                        && x.PropertyType.GetInheritedGenericTypeArguments().Contains(typeof(TEntity))
+                );
+                foreach (var prop in props)
                 {
-                    var props = CoreUtils.PropertyList(
-                        type,
-                        x =>
-                            x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink))
-                            && x.PropertyType.GetInheritedGenericTypeArguments().Contains(typeof(TEntity))
-                    );
-                    foreach (var prop in props)
+                    var propname = string.Format(prop.Name + ".ID");
+                    var filter = Core.Filter.Create(type);
+                    filter.Expression = CoreUtils.CreateMemberExpression(type, propname);
+                    filter.Operator = Operator.InList;
+                    filter.Value = otherids;
+                    var columns = Columns.None(type)
+                        .Add("ID")
+                        .Add(propname);
+                    var updates = ClientFactory.CreateClient(type).Query(filter, columns).Rows.Select(r => r.ToObject(type)).ToArray();
+                    if (updates.Any())
                     {
-                        var propname = string.Format(prop.Name + ".ID");
-                        var filter = Core.Filter.Create(type);
-                        filter.Expression = CoreUtils.CreateMemberExpression(type, propname);
-                        filter.Operator = Operator.InList;
-                        filter.Value = otherids;
-                        var columns = Columns.Create(type)
-                            .Add("ID")
-                            .Add(propname);
-                        var updates = ClientFactory.CreateClient(type).Query(filter, columns).Rows.Select(r => r.ToObject(type)).ToArray();
-                        if (updates.Any())
-                        {
-                            foreach (var update in updates)
-                                CoreUtils.SetPropertyValue(update, propname, targetid);
-                            ClientFactory.CreateClient(type).Save(updates,
-                                string.Format("Merged {0} Records", typeof(TEntity).EntityName().Split('.').Last()));
-                        }
+                        foreach (var update in updates)
+                            CoreUtils.SetPropertyValue(update, propname, targetid);
+                        ClientFactory.CreateClient(type).Save(updates,
+                            string.Format("Merged {0} Records", typeof(TEntity).EntityName().Split('.').Last()));
                     }
                 }
+            }
 
-                var histories = new Client<AuditTrail>()
-                    .Query(
-                        new Filter<AuditTrail>(x => x.EntityID).InList(otherids),
-                        Columns.None<AuditTrail>()
-                            .Add(x => x.ID).Add(x => x.EntityID))
-                    .ToArray<AuditTrail>();
-                foreach (var history in histories)
-                    history.EntityID = targetid;
-                if (histories.Length != 0)
-                    new Client<AuditTrail>().Save(histories, "");
-
-                var deletes = new List<object>();
-                foreach (var otherid in otherids)
-                {
-                    var delete = new TEntity();
-                    CoreUtils.SetPropertyValue(delete, "ID", otherid);
-                    deletes.Add(delete);
-                }
-
-                ClientFactory.CreateClient(typeof(TEntity))
-                    .Delete(deletes, string.Format("Merged {0} Records", typeof(TEntity).EntityName().Split('.').Last()));
-                return true;
+            var histories = new Client<AuditTrail>()
+                .Query(
+                    new Filter<AuditTrail>(x => x.EntityID).InList(otherids),
+                    Columns.None<AuditTrail>()
+                        .Add(x => x.ID).Add(x => x.EntityID))
+                .ToArray<AuditTrail>();
+            foreach (var history in histories)
+                history.EntityID = targetid;
+            if (histories.Length != 0)
+                new Client<AuditTrail>().Save(histories, "");
+
+            var deletes = new List<object>();
+            foreach (var otherid in otherids)
+            {
+                var delete = new TEntity();
+                CoreUtils.SetPropertyValue(delete, "ID", otherid);
+                deletes.Add(delete);
             }
+
+            ClientFactory.CreateClient(typeof(TEntity))
+                .Delete(deletes, string.Format("Merged {0} Records", typeof(TEntity).EntityName().Split('.').Last()));
+            return true;
         }
+    }
 
-        protected override IEnumerable<TEntity> LoadDuplicatorItems(CoreRow[] rows)
+    protected override IEnumerable<TEntity> LoadDuplicatorItems(CoreRow[] rows)
+    {
+        return rows.Select(x => x.ToObject<TEntity>());
+    }
+
+    protected override bool BeforePaste(IEnumerable<TEntity> items, ClipAction action)
+    {
+        if (action == ClipAction.Copy)
         {
-            return rows.Select(x => x.ToObject<TEntity>());
+            foreach (var item in items)
+                item.ID = Guid.Empty;
+            return true;
         }
 
-        protected override bool BeforePaste(IEnumerable<TEntity> items, ClipAction action)
-        {
-            if (action == ClipAction.Copy)
-            {
-                foreach (var item in items)
-                    item.ID = Guid.Empty;
-                return true;
-            }
+        return base.BeforePaste(items, action);
+    }
 
-            return base.BeforePaste(items, action);
-        }
+    protected override void CustomiseExportFilters(Filters<TEntity> filters, CoreRow[] visiblerows)
+    {
+        base.CustomiseExportFilters(filters, visiblerows);
 
-        protected override void CustomiseExportFilters(Filters<TEntity> filters, CoreRow[] visiblerows)
+        /*if (IsEntity)
+        {
+            var ids = visiblerows.Select(r => r.Get<TEntity, Guid>(c => c.ID)).ToArray();
+            filters.Add(new Filter<TEntity>(x => x.ID).InList(ids));
+        }
+        else
         {
-            base.CustomiseExportFilters(filters, visiblerows);
+            Filter<TEntity>? filter = null;
 
-            /*if (IsEntity)
-            {
-                var ids = visiblerows.Select(r => r.Get<TEntity, Guid>(c => c.ID)).ToArray();
-                filters.Add(new Filter<TEntity>(x => x.ID).InList(ids));
-            }
-            else
+            foreach (var row in visiblerows)
             {
-                Filter<TEntity>? filter = null;
-
-                foreach (var row in visiblerows)
+                var rowFilter = GetRowFilter(row);
+                if(rowFilter is not null)
                 {
-                    var rowFilter = GetRowFilter(row);
-                    if(rowFilter is not null)
+                    if (filter is null)
+                    {
+                        filter = rowFilter;
+                    }
+                    else
                     {
-                        if (filter is null)
-                        {
-                            filter = rowFilter;
-                        }
-                        else
-                        {
-                            filter.Or(rowFilter);
-                        }
+                        filter.Or(rowFilter);
                     }
                 }
-                filters.Add(filter);
-            }*/
-        }
+            }
+            filters.Add(filter);
+        }*/
+    }
 
-        protected override IEnumerable<Tuple<Type?, CoreTable>> LoadExportTables(Filters<TEntity> filter, IEnumerable<Tuple<Type, IColumns>> tableColumns)
+    protected override IEnumerable<Tuple<Type?, CoreTable>> LoadExportTables(Filters<TEntity> filter, IEnumerable<Tuple<Type, IColumns>> tableColumns)
+    {
+        var queries = new Dictionary<string, IQueryDef>();
+        var columns = tableColumns.ToList();
+        foreach (var table in columns)
         {
-            var queries = new Dictionary<string, IQueryDef>();
-            var columns = tableColumns.ToList();
-            foreach (var table in columns)
-            {
-                var tableType = table.Item1;
+            var tableType = table.Item1;
 
-                PropertyInfo? property = null;
+            PropertyInfo? property = null;
 
-                var m2m = CoreUtils.GetManyToMany(tableType, typeof(TEntity));
+            var m2m = CoreUtils.GetManyToMany(tableType, typeof(TEntity));
 
-                IFilter? queryFilter = null;
-                if (m2m != null)
-                {
-                    property = CoreUtils.GetManyToManyThisProperty(tableType, typeof(TEntity));
-                }
-                else
+            IFilter? queryFilter = null;
+            if (m2m != null)
+            {
+                property = CoreUtils.GetManyToManyThisProperty(tableType, typeof(TEntity));
+            }
+            else
+            {
+                var o2m = CoreUtils.GetOneToMany(tableType, typeof(TEntity));
+                if (o2m != null)
                 {
-                    var o2m = CoreUtils.GetOneToMany(tableType, typeof(TEntity));
-                    if (o2m != null)
-                    {
-                        property = CoreUtils.GetOneToManyProperty(tableType, typeof(TEntity));
-                    }
+                    property = CoreUtils.GetOneToManyProperty(tableType, typeof(TEntity));
                 }
+            }
 
-                if (property != null)
-                {
-                    var subQuery = new SubQuery<TEntity>();
-                    subQuery.Filter = filter.Combine();
-                    subQuery.Column = new Column<TEntity>(x => x.ID);
+            if (property != null)
+            {
+                var subQuery = new SubQuery<TEntity>();
+                subQuery.Filter = filter.Combine();
+                subQuery.Column = new Column<TEntity>(x => x.ID);
 
-                    queryFilter = (Activator.CreateInstance(typeof(Filter<>).MakeGenericType(tableType)) as IFilter)!;
-                    queryFilter.Expression = CoreUtils.GetMemberExpression(tableType, property.Name + ".ID");
-                    queryFilter.InQuery(subQuery);
+                queryFilter = (Activator.CreateInstance(typeof(Filter<>).MakeGenericType(tableType)) as IFilter)!;
+                queryFilter.Expression = CoreUtils.GetMemberExpression(tableType, property.Name + ".ID");
+                queryFilter.InQuery(subQuery);
 
-                    queries[tableType.Name] = new QueryDef(tableType)
-                    {
-                        Filter = queryFilter,
-                        Columns = table.Item2
-                    };
-                }
+                queries[tableType.Name] = new QueryDef(tableType)
+                {
+                    Filter = queryFilter,
+                    Columns = table.Item2
+                };
             }
+        }
 
-            var results = Client.QueryMultiple(queries);
+        var results = Client.QueryMultiple(queries);
 
-            return columns.Select(x => new Tuple<Type?, CoreTable>(x.Item1, results[x.Item1.Name]));
-        }
+        return columns.Select(x => new Tuple<Type?, CoreTable>(x.Item1, results[x.Item1.Name]));
+    }
 
-        protected override CoreTable LoadImportKeys(String[] fields)
-        {
-            return new Client<TEntity>().Query(null, new Columns<TEntity>(fields));
-        }
-        
+    protected override CoreTable LoadImportKeys(String[] fields)
+    {
+        return Client.Query(null, Columns.None<TEntity>().Add(fields));
     }
+    
 }

+ 1 - 1
inabox.wpf/DynamicGrid/DynamicEntityFormGrid.cs

@@ -127,7 +127,7 @@ namespace InABox.DynamicGrid
             var form = new Client<TForm>()
                 .Query(
                     new Filter<TForm>(x => x.ID).IsEqualTo(item.ID),
-                    new Columns<TForm>(x => x.ID)
+                    Columns.None<TForm>().Add(x => x.ID)
                         .Add(x => x.FormData)
                         .Add(x => x.BlobData)
                         .Add(x => x.FormCompleted)

+ 1 - 1
inabox.wpf/DynamicGrid/DynamicGrid.cs

@@ -1945,7 +1945,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
     protected virtual CoreTable LoadImportKeys(String[] fields)
     {
         var result = new CoreTable();
-        result.LoadColumns(new Columns<T>(fields));
+        result.LoadColumns(Columns.None<T>().Add(fields));
         return result;
     }
 

+ 2 - 4
inabox.wpf/DynamicGrid/Editors/CodePopupEditor/CodePopupEditorControl.cs

@@ -182,11 +182,9 @@ public class CodePopupEditorControl : DynamicEditorControl<Guid, CodePopupEditor
         if (_type != null)
         {
             var columns = LookupFactory.DefineLookupColumns(Host.GetEditorType(), _type, ColumnName);
-            var cols = OtherColumns.Keys.ToList();
-            foreach (var column in columns)
-                cols.Add(column);
+            var cols = OtherColumns.Keys.Concat(columns.ColumnNames()).ToArray();
 
-            var list = new PopupList(_type, _value, cols.ToArray(), new Dictionary<string, string> { { CodeColumn, code } });
+            var list = new PopupList(_type, _value, cols, new Dictionary<string, string> { { CodeColumn, code } });
 
             list.OnDefineFilter += t => LookupFactory.DefineLookupFilter(Host.GetEditorType(), t, ColumnName, Host.GetItems());
 

+ 2 - 2
inabox.wpf/DynamicGrid/IDynamicMemoryEntityGrid.cs

@@ -109,7 +109,7 @@ public static class DynamicMemoryEntityGridExtensions
                 var ids = data.Item3.Select(prop.Getter()).Cast<Guid>().ToArray();
                 queryDefs.Add(new KeyedQueryDef(prop.Name, data.Item1,
                     Filter.Create<Entity>(data.Item1, x => x.ID).InList(ids),
-                    Columns.Create(data.Item1)
+                    Columns.None(data.Item1)
                         .Add(data.Item2.Select(x => x.Item1))
                         .Add<Entity>(x => x.ID)));
             }
@@ -147,7 +147,7 @@ public static class DynamicMemoryEntityGridExtensions
         if (grid.LoadedColumns is null) return;
 
         // Figure out which columns we still need.
-        var newColumns = columns.Where(x => !grid.LoadedColumns.Contains(x.Property)).ToColumns();
+        var newColumns = columns.Where(x => !grid.LoadedColumns.Contains(x.Property)).ToColumns(ColumnTypeFlags.None);
         if (newColumns.Count > 0 && typeof(T).GetCustomAttribute<AutoEntity>() is null)
         {
             var data = Client.Query(

+ 1 - 6
inabox.wpf/Reports/ReportUtils.cs

@@ -159,12 +159,7 @@ namespace InABox.Wpf.Reports
                             col.CustomBindableControl = "MultiImageObject";
                         }*/
                     }
-                    var columns = Columns.Create(modelTable.Type);
-                    foreach(var column in columnNames)
-                    {
-                        columns.Add(column);
-                    }
-                    modelTable.Columns = columns;
+                    modelTable.Columns = Columns.None(modelTable.Type).Add(columnNames);
                 }
             }