Browse Source

Removed null columns of ManyToMany grid

Kenric Nugteren 1 year ago
parent
commit
bfc48abd7b

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

@@ -271,14 +271,14 @@ namespace InABox.DynamicGrid
         private void GetDocuments(Action<Dictionary<string,byte[]>> action)
         {
             var ids = SelectedRows.Select(r => r.Get<IEntityDocument, Guid>(c => c.DocumentLink.ID)).ToArray();
-            var files = new Client<Document>().Query(
+            var files = Client.Query(
                 new Filter<Document>(x => x.ID).InList(ids),
                 new Columns<Document>(x => x.FileName).Add(x => x.Data)
             ).ToDictionary<Document, String, byte[]>(x => x.FileName, x => x.Data);
             action?.Invoke(files);
         }
 
-        private String SanitiseFileName(string filename)
+        private static string SanitiseFileName(string filename)
         {
             var basefilename = Path.GetFileNameWithoutExtension(filename);
             var extension = Path.GetExtension(filename);

+ 8 - 7
inabox.wpf/DynamicGrid/DynamicManyToManyDataGrid.cs

@@ -46,28 +46,29 @@ namespace InABox.DynamicGrid
         {
             var expr = CoreUtils.CreateLambdaExpression<TManyToMany>(prop.Name + ".ID");
             criteria.Add(new Filter<TManyToMany>(expr).IsEqualTo(ID));
-            new Client<TManyToMany>().Query(criteria.Combine(), columns, sort, action);
+            Client.Query(criteria.Combine(), columns, sort, action);
         }
 
         protected override TManyToMany LoadItem(CoreRow row)
         {
             var id = row.Get<TManyToMany, Guid>(x => x.ID);
-            return new Client<TManyToMany>()
-                .Load(
-                    new Filter<TManyToMany>(x => x.ID).IsEqualTo(id))
-                .FirstOrDefault() ?? throw new Exception($"{typeof(TManyToMany)} with ID {id} does not exist!");
+            return Client
+                .Query(
+                    new Filter<TManyToMany>(x => x.ID).IsEqualTo(id),
+                    DynamicGridUtils.LoadEditorColumns(DataColumns()))
+                .ToObjects<TManyToMany>().FirstOrDefault() ?? throw new Exception($"{typeof(TManyToMany)} with ID {id} does not exist!");
         }
 
         protected override void DeleteItems(params CoreRow[] rows)
         {
             var items = LoadItems(rows);
             foreach (var item in items)
-                new Client<TManyToMany>().Delete(item, "");
+                Client.Delete(item, "");
         }
 
         public override void SaveItem(TManyToMany item)
         {
-            new Client<TManyToMany>().Save(item, "");
+            Client.Save(item, "");
         }
     }
 }

+ 354 - 305
inabox.wpf/DynamicGrid/DynamicManyToManyGrid.cs

@@ -10,410 +10,459 @@ using System.Windows.Controls;
 using InABox.Clients;
 using InABox.Configuration;
 using InABox.Core;
+using InABox.Wpf;
 using InABox.WPF;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+public interface IDynamicManyToManyGrid<TManyToMany, TThis> : IDynamicEditorPage
 {
-    public interface IDynamicManyToManyGrid<TManyToMany, TThis> : IDynamicEditorPage
-    {
-    }
+}
 
-    public class DynamicManyToManyGrid<TManyToMany, TThis> : DynamicGrid<TManyToMany>, IDynamicEditorPage, IDynamicManyToManyGrid<TManyToMany, TThis>
-        where TThis : Entity, new()
-        where TManyToMany : Entity, IPersistent, IRemotable, new()
-    {
-        //private Guid ID = Guid.Empty;
-        protected TThis Item;
-        private TManyToMany[] MasterList = { };
-        protected PropertyInfo otherproperty;
-        protected IEntityLink GetOtherLink(TManyToMany item) => (otherproperty.GetValue(item) as IEntityLink)!;
+public class DynamicManyToManyGrid<TManyToMany, TThis> : DynamicGrid<TManyToMany>, IDynamicEditorPage, IDynamicManyToManyGrid<TManyToMany, TThis>
+    where TThis : Entity, new()
+    where TManyToMany : Entity, IPersistent, IRemotable, new()
+{
+    //private Guid ID = Guid.Empty;
+    protected TThis Item;
+
+    /// <summary>
+    /// Keeps a cache of initially loaded objects, so that we can figure out which guys to delete when we save.
+    /// </summary>
+    private TManyToMany[] MasterList = Array.Empty<TManyToMany>();
 
-        protected PropertyInfo thisproperty;
-        protected IEntityLink GetThisLink(TManyToMany item) => (thisproperty.GetValue(item) as IEntityLink)!;
-        
-        protected List<TManyToMany> WorkingList = new();
+    protected PropertyInfo otherproperty;
+    protected IEntityLink GetOtherLink(TManyToMany item) => (otherproperty.GetValue(item) as IEntityLink)!;
 
-        public PageType PageType => PageType.Other;
+    protected PropertyInfo thisproperty;
+    protected IEntityLink GetThisLink(TManyToMany item) => (thisproperty.GetValue(item) as IEntityLink)!;
+    
+    protected List<TManyToMany> WorkingList = new();
 
-        private bool _readOnly;
-        public bool ReadOnly
+    public PageType PageType => PageType.Other;
+
+    private bool _readOnly;
+    public bool ReadOnly
+    {
+        get => _readOnly;
+        set
         {
-            get => _readOnly;
-            set
+            if(_readOnly != value)
             {
-                if(_readOnly != value)
-                {
-                    _readOnly = value;
-                    Reconfigure();
-                }
+                _readOnly = value;
+                Reconfigure();
             }
         }
+    }
 
-        private static bool IsAutoEntity => typeof(TManyToMany).HasAttribute<AutoEntity>();
+    private static bool IsAutoEntity => typeof(TManyToMany).HasAttribute<AutoEntity>();
 
-        protected DynamicGridCustomColumnsComponent<TManyToMany> ColumnsComponent;
+    protected DynamicGridCustomColumnsComponent<TManyToMany> ColumnsComponent;
 
-        public DynamicManyToManyGrid()
-        {
-            MultiSelect = true;
-            thisproperty = CoreUtils.GetManyToManyThisProperty(typeof(TManyToMany), typeof(TThis));
-            otherproperty = CoreUtils.GetManyToManyOtherProperty(typeof(TManyToMany), typeof(TThis));
+    /// <summary>
+    /// A set of columns representing which columns have been loaded from the database.
+    /// </summary>
+    /// <remarks>
+    /// This is used to refresh the data when the columns change.<br/>
+    /// 
+    /// It is <see langword="null"/> if no data has been loaded from the database (that is, the data was gotten from
+    /// a page data handler instead.)
+    /// </remarks>
+    private HashSet<string>? LoadedColumns;
 
-            HiddenColumns.Add(x => x.ID);
-            HiddenColumns.Add(CoreUtils.CreateLambdaExpression<TManyToMany>(otherproperty.Name + ".ID"));
+    public DynamicManyToManyGrid()
+    {
+        MultiSelect = true;
+        thisproperty = CoreUtils.GetManyToManyThisProperty(typeof(TManyToMany), typeof(TThis));
+        otherproperty = CoreUtils.GetManyToManyOtherProperty(typeof(TManyToMany), typeof(TThis));
 
-            ColumnsComponent = new DynamicGridCustomColumnsComponent<TManyToMany>(this, GetTag());
-        }
+        HiddenColumns.Add(x => x.ID);
+        HiddenColumns.Add(CoreUtils.CreateLambdaExpression<TManyToMany>(otherproperty.Name + ".ID"));
 
-        protected override void Init()
-        {
-        }
+        ColumnsComponent = new DynamicGridCustomColumnsComponent<TManyToMany>(this, GetTag());
+    }
 
-        protected override void DoReconfigure(FluentList<DynamicGridOption> options)
-        {
-            options.BeginUpdate();
-
-            options.Add(DynamicGridOption.RecordCount)
-                .Add(DynamicGridOption.SelectColumns)
-                .Add(DynamicGridOption.MultiSelect);
-
-            if (Security.CanEdit<TManyToMany>() && !ReadOnly)
-                options.Add(DynamicGridOption.AddRows).Add(DynamicGridOption.EditRows);
-            if (Security.CanDelete<TManyToMany>() && !ReadOnly)
-                options.Add(DynamicGridOption.DeleteRows);
-            if (Security.CanImport<TManyToMany>() && !ReadOnly)
-                options.Add(DynamicGridOption.ImportData);
-            if (Security.CanExport<TManyToMany>())
-                options.Add(DynamicGridOption.ExportData);
-            if (Security.CanMerge<TManyToMany>())
-                options.Add(DynamicGridOption.MultiSelect);
-
-            options.EndUpdate();
-        }
+    protected override void Init()
+    {
+    }
+
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        options.BeginUpdate();
+
+        options.Add(DynamicGridOption.RecordCount)
+            .Add(DynamicGridOption.SelectColumns)
+            .Add(DynamicGridOption.MultiSelect);
+
+        if (Security.CanEdit<TManyToMany>() && !ReadOnly)
+            options.Add(DynamicGridOption.AddRows).Add(DynamicGridOption.EditRows);
+        if (Security.CanDelete<TManyToMany>() && !ReadOnly)
+            options.Add(DynamicGridOption.DeleteRows);
+        if (Security.CanImport<TManyToMany>() && !ReadOnly)
+            options.Add(DynamicGridOption.ImportData);
+        if (Security.CanExport<TManyToMany>())
+            options.Add(DynamicGridOption.ExportData);
+        if (Security.CanMerge<TManyToMany>())
+            options.Add(DynamicGridOption.MultiSelect);
+
+        options.EndUpdate();
+    }
 
-        public bool MultiSelect { get; set; }
+    public bool MultiSelect { get; set; }
 
-        public DynamicEditorGrid EditorGrid { get; set; }
+    public DynamicEditorGrid EditorGrid { get; set; }
 
-        public string Caption()
-        {
-            //var m2m = typeof(TManyToMany).GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Contains(typeof(TThis)));
-            //Type other = m2m.GenericTypeArguments.FirstOrDefault(x => x != typeof(TThis));
-            //var serv = System.Data.Entity PluralizationService.CreateService(new System.Globalization.CultureInfo("en-us"));
-            //var plural = serv.Pluralize(source);
-            //return MvcHtmlString.Create(plural);
-
-            var result = new Inflector.Inflector(new CultureInfo("en")).Pluralize(OtherType().Name);
-            return result;
-        }
+    public string Caption()
+    {
+        //var m2m = typeof(TManyToMany).GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Contains(typeof(TThis)));
+        //Type other = m2m.GenericTypeArguments.FirstOrDefault(x => x != typeof(TThis));
+        //var serv = System.Data.Entity PluralizationService.CreateService(new System.Globalization.CultureInfo("en-us"));
+        //var plural = serv.Pluralize(source);
+        //return MvcHtmlString.Create(plural);
+
+        var result = new Inflector.Inflector(new CultureInfo("en")).Pluralize(OtherType().Name);
+        return result;
+    }
 
-        public virtual int Order()
-        {
-            return int.MinValue;
-        }
+    public virtual int Order()
+    {
+        return int.MinValue;
+    }
 
-        public bool Ready { get; set; }
+    public bool Ready { get; set; }
 
-        public void Load(object item, Func<Type, CoreTable?>? PageDataHandler)
-        {
-            Item = (TThis)item;
+    public void Load(object item, Func<Type, CoreTable?>? PageDataHandler)
+    {
+        Item = (TThis)item;
 
-            var data = PageDataHandler?.Invoke(typeof(TManyToMany));
-            if (data != null)
+        var data = PageDataHandler?.Invoke(typeof(TManyToMany));
+        if (data != null)
+        {
+            RefreshData(data);
+        }
+        else
+        {
+            if (Item.ID == Guid.Empty)
             {
+                data = new CoreTable();
+                data.LoadColumns(typeof(TManyToMany));
                 RefreshData(data);
             }
             else
             {
-                if (Item.ID == Guid.Empty)
-                {
-                    data = new CoreTable();
-                    data.LoadColumns(typeof(TManyToMany));
-                    RefreshData(data);
-                }
-                else
+                var exp = CoreUtils.GetPropertyExpression<TManyToMany>(thisproperty.Name + ".ID");
+                var filter = new Filter<TManyToMany>(exp).IsEqualTo(Item.ID).And(exp).IsNotEqualTo(Guid.Empty);
+                var sort = LookupFactory.DefineSort<TManyToMany>();
+
+                var columns = DynamicGridUtils.LoadEditorColumns(DataColumns());
+
+                Client.Query(filter, columns, sort, (o, e) =>
                 {
-                    var exp = CoreUtils.GetPropertyExpression<TManyToMany>(thisproperty.Name + ".ID");
-                    var filter = new Filter<TManyToMany>(exp).IsEqualTo(Item.ID).And(exp).IsNotEqualTo(Guid.Empty);
-                    var sort = LookupFactory.DefineSort<TManyToMany>();
-                    new Client<TManyToMany>().Query(filter, null, sort, (o, e) => {
-                        if(o != null)
-                        {
-                            Dispatcher.Invoke(() => RefreshData(o));
-                        }
-                        else if(e != null)
+                    if (o != null)
+                    {
+                        LoadedColumns = columns.ColumnNames().ToHashSet();
+
+                        Dispatcher.Invoke(() => RefreshData(o));
+                    }
+                    else if(e != null)
+                    {
+                        Dispatcher.Invoke(() =>
                         {
-                            Logger.Send(LogType.Information, ClientFactory.UserID, $"Unknown Error: {CoreUtils.FormatException(e)}");
-                            MessageBox.Show("An error occurred while loading data.");
-                        }
-                    });
-                }
+                            MessageWindow.ShowError("An error occurred while loading data.", e);
+                        });
+                    }
+                });
             }
         }
+    }
 
-        public void BeforeSave(object item)
+    public void BeforeSave(object item)
+    {
+        // Don't need to do anything here
+    }
+
+    public void AfterSave(object item)
+    {
+        if (IsAutoEntity)
         {
-            // Don't need to do anything here
+            return;
         }
+        // First remove any deleted files
+        foreach (var map in MasterList)
+            if (!WorkingList.Contains(map))
+                Client.Delete(map, typeof(TManyToMany).Name + " Deleted by User");
 
-        public void AfterSave(object item)
+        foreach (var map in WorkingList)
         {
-            if (IsAutoEntity)
-            {
-                return;
-            }
-            // First remove any deleted files
-            foreach (var map in MasterList)
-                if (!WorkingList.Contains(map))
-                    new Client<TManyToMany>().Delete(map, typeof(TManyToMany).Name + " Deleted by User");
+            var prop = GetThisLink(map);
+            if (prop.ID != Item.ID)
+                prop.ID = Item.ID;
+        }
 
-            foreach (var map in WorkingList)
-            {
-                var prop = GetThisLink(map);
-                if (prop.ID != Item.ID)
-                    prop.ID = Item.ID;
-            }
+        if (WorkingList.Any(x => x.IsChanged()))
+            Client.Save(WorkingList.Where(x => x.IsChanged()), "Updated by User");
+    }
 
-            if (WorkingList.Any(x => x.IsChanged()))
-                new Client<TManyToMany>().Save(WorkingList.Where(x => x.IsChanged()), "Updated by User");
-        }
+    public Size MinimumSize()
+    {
+        return new Size(400, 400);
+    }
 
-        public Size MinimumSize()
-        {
-            return new Size(400, 400);
-        }
+    private static Type OtherType() =>
+        CoreUtils.GetManyToManyOtherType(typeof(TManyToMany), typeof(TThis));
 
-        private static Type OtherType() =>
-            CoreUtils.GetManyToManyOtherType(typeof(TManyToMany), typeof(TThis));
+    private static string GetTag()
+    {
+        return typeof(TManyToMany).Name + "." + typeof(TThis).Name;
+    }
 
-        private static string GetTag()
-        {
-            return typeof(TManyToMany).Name + "." + typeof(TThis).Name;
-        }
+    public override DynamicGridColumns GenerateColumns()
+    {
+        var cols = new DynamicGridColumns();
+        cols.AddRange(base.GenerateColumns().Where(x => !x.ColumnName.StartsWith(thisproperty.Name + ".")));
+        return cols;
+    }
 
-        public override DynamicGridColumns GenerateColumns()
-        {
-            var cols = new DynamicGridColumns();
-            cols.AddRange(base.GenerateColumns().Where(x => !x.ColumnName.StartsWith(thisproperty.Name + ".")));
-            return cols;
-        }
+    protected override DynamicGridColumns LoadColumns()
+    {
+        return ColumnsComponent.LoadColumns();
+    }
 
-        protected override DynamicGridColumns LoadColumns()
-        {
-            return ColumnsComponent.LoadColumns();
-        }
+    protected override void SaveColumns(DynamicGridColumns columns)
+    {
+        ColumnsComponent.SaveColumns(columns);
+    }
+    protected override void LoadColumnsMenu(ContextMenu menu)
+    {
+        base.LoadColumnsMenu(menu);
+        ColumnsComponent.LoadColumnsMenu(menu);
+    }
 
-        protected override void SaveColumns(DynamicGridColumns columns)
-        {
-            ColumnsComponent.SaveColumns(columns);
-        }
-        protected override void LoadColumnsMenu(ContextMenu menu)
-        {
-            base.LoadColumnsMenu(menu);
-            ColumnsComponent.LoadColumnsMenu(menu);
-        }
+    protected override DynamicGridSettings LoadSettings()
+    {
+        var tag = GetTag();
 
-        protected override DynamicGridSettings LoadSettings()
-        {
-            var tag = GetTag();
+        var user = Task.Run(() => new UserConfiguration<DynamicGridSettings>(tag).Load());
+        user.Wait();
 
-            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;
 
-            //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);
+    }
 
-            return user.Result;
-        }
-        protected override void SaveSettings(DynamicGridSettings settings)
+    protected virtual Guid[] CurrentGuids()
+    {
+        var result = new List<Guid>();
+        foreach (var item in WorkingList)
         {
-            var tag = GetTag();
-            new UserConfiguration<DynamicGridSettings>(tag).Save(settings);
+            //var prop = GetOtherLink(item);
+            var prop = GetThisLink(item);
+            result.Add(prop.ID);
         }
 
-        protected virtual Guid[] CurrentGuids()
-        {
-            var result = new List<Guid>();
-            foreach (var item in WorkingList)
-            {
-                //var prop = GetOtherLink(item);
-                var prop = GetThisLink(item);
-                result.Add(prop.ID);
-            }
-
-            return result.ToArray();
-        }
+        return result.ToArray();
+    }
 
 
-        protected virtual object GetFilter()
-        {
-            var result = LookupFactory.DefineFilter(OtherType(), typeof(TThis), new[] { (TThis)Item });
+    protected virtual IFilter? GetFilter()
+    {
+        var result = LookupFactory.DefineFilter(OtherType(), typeof(TThis), new[] { (TThis)Item });
 
-            var filtertype = typeof(Filter<>).MakeGenericType(OtherType());
+        var filtertype = typeof(Filter<>).MakeGenericType(OtherType());
 
-            var filtermethod = filtertype.GetMethods(BindingFlags.Public | BindingFlags.Static).Where(x =>
-                x.Name.Equals("List") && x.GetParameters().Last().ParameterType.IsAssignableFrom(typeof(IEnumerable<Guid>))).First();
-            var filterexpression = CoreUtils.GetPropertyExpression(OtherType(), "ID");
-            var filtervalues = CurrentGuids();
-            var filter = filtermethod.Invoke(null, new object[] { filterexpression, ListOperator.Excludes, filtervalues }) as IFilter;
+        var filtermethod = filtertype.GetMethods(BindingFlags.Public | BindingFlags.Static).Where(x =>
+            x.Name.Equals("List") && x.GetParameters().Last().ParameterType.IsAssignableFrom(typeof(IEnumerable<Guid>))).First();
+        var filterexpression = CoreUtils.GetPropertyExpression(OtherType(), "ID");
+        var filtervalues = CurrentGuids();
+        var filter = filtermethod.Invoke(null, new object[] { filterexpression, ListOperator.Excludes, filtervalues }) as IFilter;
 
-            if (filter != null)
+        if (filter != null)
+        {
+            if (result != null)
             {
-                if (result != null)
-                {
-                    filter.And(result);
-                }
-
-                return filter;
+                filter.And(result);
             }
 
-            if (result != null) return result;
-
-            return null;
+            return filter;
         }
 
-        protected override void DoAdd(bool OpenEditorOnDirectEdit = false)
+        if (result != null) return result;
+
+        return null;
+    }
+
+    protected override void DoAdd(bool OpenEditorOnDirectEdit = false)
+    {
+        if (MultiSelect)
         {
-            if (MultiSelect)
-            {
-                var filter = GetFilter();
+            var filter = GetFilter();
 
-                var dlgtype = typeof(MultiSelectDialog<>).MakeGenericType(OtherType());
-                var dlg = (Activator.CreateInstance(dlgtype, filter, null, true) as IMultiSelectDialog)!;
-                if (dlg.ShowDialog())
+            var dlgtype = typeof(MultiSelectDialog<>).MakeGenericType(OtherType());
+            var dlg = (Activator.CreateInstance(dlgtype, filter, null, true) as IMultiSelectDialog)!;
+            if (dlg.ShowDialog())
+            {
+                var guids = CurrentGuids();
+                foreach (var entity in dlg.Items(null))
                 {
-                    var guids = CurrentGuids();
-                    var items = dlgtype.GetMethod("Items").Invoke(dlg, new object[] { null }) as IEnumerable;
-                    foreach (var item in items)
+                    if (!guids.Contains(entity.ID))
                     {
-                        var entity = item as Entity;
-                        if (!guids.Contains(entity.ID))
-                        {
-                            var newitem = CreateItem();
-                            var prop = GetOtherLink(newitem);
-                            prop.ID = entity.ID;
-                            prop.Synchronise(entity);
-                            SaveItem(newitem);
-                        }
+                        var newitem = CreateItem();
+                        var prop = GetOtherLink(newitem);
+                        prop.ID = entity.ID;
+                        prop.Synchronise(entity);
+                        SaveItem(newitem);
                     }
-
-                    Refresh(false, true);
                 }
-            }
-            else
-            {
-                base.DoAdd();
-            }
-        }
 
-        protected override TManyToMany CreateItem()
-        {
-            var result = new TManyToMany();
-            if (Item != null)
-            {
-                var prop = GetThisLink(result);
-                prop.ID = Item.ID;
-                prop.Synchronise(Item);
+                Refresh(false, true);
             }
-
-            return result;
         }
-
-        protected override TManyToMany LoadItem(CoreRow row)
+        else
         {
-            return WorkingList[_recordmap[row].Index];
+            base.DoAdd();
         }
+    }
 
-        public override void SaveItem(TManyToMany item)
+    protected override TManyToMany CreateItem()
+    {
+        var result = new TManyToMany();
+        if (Item != null)
         {
-            if (!WorkingList.Contains(item))
-                WorkingList.Add(item);
+            var prop = GetThisLink(result);
+            prop.ID = Item.ID;
+            prop.Synchronise(Item);
         }
 
-        protected override void DeleteItems(params CoreRow[] rows)
-        {
-            foreach (var row in rows)
-            {
-                var id = row.Get<TManyToMany, Guid>(c => c.ID);
-                var item = WorkingList.FirstOrDefault(x => x.ID.Equals(id));
-                if (item != null)
-                    WorkingList.Remove(item);
-            }
-        }
+        return result;
+    }
 
-        private void RefreshData(CoreTable data)
+    protected override TManyToMany LoadItem(CoreRow row)
+    {
+        return WorkingList[_recordmap[row].Index];
+    }
+
+    public override void SaveItem(TManyToMany item)
+    {
+        if (!WorkingList.Contains(item))
+            WorkingList.Add(item);
+    }
+
+    protected override void DeleteItems(params CoreRow[] rows)
+    {
+        foreach (var row in rows)
         {
-            MasterList = data.Rows.Select(x => x.ToObject<TManyToMany>()).ToArray();
-            WorkingList = MasterList.ToList();
-            Refresh(true, true);
-            Ready = true;
+            var id = row.Get<TManyToMany, Guid>(c => c.ID);
+            var item = WorkingList.FirstOrDefault(x => x.ID.Equals(id));
+            if (item != null)
+                WorkingList.Remove(item);
         }
+    }
 
-        protected override void Reload(Filters<TManyToMany> criteria, Columns<TManyToMany> columns, ref SortOrder<TManyToMany>? sort,
-            Action<CoreTable?, Exception?> action)
-        {
-            var results = new CoreTable();
-            results.LoadColumns(typeof(TManyToMany));
+    private void RefreshData(CoreTable data)
+    {
+        MasterList = data.ToArray<TManyToMany>();
+        WorkingList = MasterList.ToList();
+        Refresh(true, true);
+        Ready = true;
+    }
+
+    protected override void Reload(Filters<TManyToMany> criteria, Columns<TManyToMany> columns, ref SortOrder<TManyToMany>? sort,
+        Action<CoreTable?, Exception?> action)
+    {
+        var results = new CoreTable();
+        results.LoadColumns(typeof(TManyToMany));
 
-            if (sort != null)
+        if (LoadedColumns is not null)
+        {
+            // Figure out which columns we still need.
+            var newColumns = columns.Where(x => !LoadedColumns.Contains(x.Property)).ToColumns();
+            if (newColumns.Any() && typeof(TManyToMany).GetCustomAttribute<AutoEntity>() is null)
             {
-                var exp = IQueryableExtensions.ToLambda<TManyToMany>(sort.Expression);
-                var sorted = sort.Direction == SortDirection.Ascending
-                    ? WorkingList.AsQueryable().OrderBy(exp)
-                    : WorkingList.AsQueryable().OrderByDescending(exp);
-                foreach (var then in sort.Thens)
+                var data = Client.Query(
+                    new Filter<TManyToMany>(x => x.ID).InList(WorkingList.Select(x => x.ID).Where(x => x != Guid.Empty).ToArray()),
+                    // We also need to add ID, so we know which item to fill.
+                    newColumns.Add(x => x.ID));
+                foreach (var row in data.Rows)
                 {
-                    var thexp = IQueryableExtensions.ToLambda<TManyToMany>(then.Expression);
-                    sorted = sort.Direction == SortDirection.Ascending ? sorted.ThenBy(exp) : sorted.ThenByDescending(exp);
+                    var item = WorkingList.FirstOrDefault(x => x.ID == row.Get<TManyToMany, Guid>(y => y.ID));
+                    if (item is not null)
+                    {
+                        row.FillObject(item, overrideExisting: false);
+                    }
+                }
+                // Remember that we have now loaded this data.
+                foreach (var column in newColumns)
+                {
+                    LoadedColumns.Add(column.Property);
                 }
-
-                WorkingList = sorted.ToList();
             }
-            results.LoadRows(WorkingList);
-
-            //results.LoadRows(WorkingList);
-            action.Invoke(results, null);
         }
 
-        protected override BaseEditor? GetEditor(object item, DynamicGridColumn column)
+        if (sort != null)
         {
-            var type = CoreUtils.GetProperty(typeof(TManyToMany), column.ColumnName).DeclaringType;
-            if (type.GetInterfaces().Contains(typeof(IEntityLink)) && type.ContainsInheritedGenericType(typeof(TThis)))
-                return new NullEditor();
-            return base.GetEditor(item, column);
-        }
+            var exp = IQueryableExtensions.ToLambda<TManyToMany>(sort.Expression);
+            var sorted = sort.Direction == SortDirection.Ascending
+                ? WorkingList.AsQueryable().OrderBy(exp)
+                : WorkingList.AsQueryable().OrderByDescending(exp);
+            foreach (var then in sort.Thens)
+            {
+                var thexp = IQueryableExtensions.ToLambda<TManyToMany>(then.Expression);
+                sorted = sort.Direction == SortDirection.Ascending ? sorted.ThenBy(exp) : sorted.ThenByDescending(exp);
+            }
 
-        public override void LoadEditorButtons(TManyToMany item, DynamicEditorButtons buttons)
-        {
-            base.LoadEditorButtons(item, buttons);
-            if (ClientFactory.IsSupported<AuditTrail>())
-                buttons.Add("Audit Trail",Wpf.Resources.view.AsBitmapImage(), item, AuditTrailClick);
+            WorkingList = sorted.ToList();
         }
+        results.LoadRows(WorkingList);
 
-        private void AuditTrailClick(object sender, object item)
-        {
-            var entity = (TManyToMany)item;
-            var window = new AuditWindow(entity.ID);
-            window.ShowDialog();
-        }
+        //results.LoadRows(WorkingList);
+        action.Invoke(results, null);
+    }
 
-        public override DynamicEditorPages LoadEditorPages(TManyToMany item)
-        {
-            return item.ID != Guid.Empty ? base.LoadEditorPages(item) : new DynamicEditorPages();
-        }
+    protected override BaseEditor? GetEditor(object item, DynamicGridColumn column)
+    {
+        var type = CoreUtils.GetProperty(typeof(TManyToMany), column.ColumnName).DeclaringType;
+        if (type.GetInterfaces().Contains(typeof(IEntityLink)) && type.ContainsInheritedGenericType(typeof(TThis)))
+            return new NullEditor();
+        return base.GetEditor(item, column);
+    }
+
+    public override void LoadEditorButtons(TManyToMany item, DynamicEditorButtons buttons)
+    {
+        base.LoadEditorButtons(item, buttons);
+        if (ClientFactory.IsSupported<AuditTrail>())
+            buttons.Add("Audit Trail", Wpf.Resources.view.AsBitmapImage(), item, AuditTrailClick);
+    }
 
-        protected override bool BeforePaste(IEnumerable<TManyToMany> items, ClipAction action)
+    private void AuditTrailClick(object sender, object? item)
+    {
+        if (item is not TManyToMany entity) return;
+
+        var window = new AuditWindow(entity.ID);
+        window.ShowDialog();
+    }
+
+    public override DynamicEditorPages LoadEditorPages(TManyToMany item)
+    {
+        return item.ID != Guid.Empty ? base.LoadEditorPages(item) : new DynamicEditorPages();
+    }
+
+    protected override bool BeforePaste(IEnumerable<TManyToMany> items, ClipAction action)
+    {
+        if (action == ClipAction.Copy)
         {
-            if (action == ClipAction.Copy)
+            foreach (var item in items)
             {
-                foreach (var item in items)
-                {
-                    item.ID = Guid.Empty;
-                }
+                item.ID = Guid.Empty;
             }
-            return base.BeforePaste(items, action);
         }
+        return base.BeforePaste(items, action);
     }
 }

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

@@ -218,7 +218,7 @@ namespace InABox.DynamicGrid
             {
                 return;
             }
-            new Client<TMany>().Delete(item, typeof(TMany).Name + " Deleted by User");
+            Client.Delete(item, typeof(TMany).Name + " Deleted by User");
         }
 
 

+ 4 - 0
inabox.wpf/DynamicGrid/MultiSelectDialog.cs

@@ -19,6 +19,8 @@ namespace InABox.DynamicGrid
         bool ShowDialog(String? column = null, String? filter = null, FilterType filtertype = FilterType.Contains);
         Guid[] IDs();
         CoreTable Data();
+
+        Entity[] Items(IColumns? columns = null);
     }
 
     public class MultiSelectDialog<T> : IMultiSelectDialog where T : Entity, IRemotable, IPersistent, new()
@@ -214,6 +216,8 @@ namespace InABox.DynamicGrid
             return Array.Empty<T>();
         }
 
+        Entity[] IMultiSelectDialog.Items(IColumns? columns) => Items(columns as Columns<T>);
+
         private void Grid_DoubleClick(object sender, HandledEventArgs args)
         {
             args.Handled = true;