Sfoglia il codice sorgente

Added some utility functions for doing lookups, column handling and editing entities

Kenric Nugteren 1 anno fa
parent
commit
64bd985e43

+ 31 - 0
InABox.Core/Client/Client.cs

@@ -90,6 +90,37 @@ namespace InABox.Clients
                 row?.FillObject(entity);
             }
         }
+        public static void EnsureColumns<TEntity>(IList<TEntity> entities, Columns<TEntity> columns)
+            where TEntity : Entity, IRemotable, IPersistent, new()
+        {
+            var newColumns = new Columns<TEntity>();
+            foreach(var entity in entities)
+            {
+                foreach(var column in columns)
+                {
+                    if (!entity.HasColumn(column.Property))
+                    {
+                        newColumns.Add(column);
+                    }
+                }
+            }
+            if (newColumns.Any())
+            {
+                newColumns.Add(x => x.ID);
+                var table = Query(new Filter<TEntity>(x => x.ID).InList(entities.Select(x => x.ID).ToArray()), newColumns);
+                foreach(var row in table.Rows)
+                {
+                    var id = row.Get<TEntity, Guid>(x => x.ID);
+                    var entity = entities.FirstOrDefault(x => x.ID == id);
+                    if(entity is null)
+                    {
+                        // Shouldn't happen, but just in case.
+                        continue;
+                    }
+                    row?.FillObject(entity);
+                }
+            }
+        }
 
         public static CoreTable Query<TEntity>(Filter<TEntity>? filter = null, Columns<TEntity>? columns = null, SortOrder<TEntity>? orderby = null)
             where TEntity : Entity, IRemotable, IPersistent, new()

+ 39 - 0
InABox.Core/Column.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Linq.Expressions;
 using System.Reflection;
@@ -55,6 +56,12 @@ namespace InABox.Core
         {
         }
 
+        public Column(IProperty property)
+        {
+            Property = property.Name;
+            Expression = property.Expression();
+        }
+
         public Column(Expression<Func<T, object?>> expression) : base(expression)
         {
             //String[] parts = expression.ToString().Split(new String[] { "=>" }, StringSplitOptions.RemoveEmptyEntries);
@@ -81,6 +88,20 @@ namespace InABox.Core
             return new Column<TNew>(Property);
         }
 
+        public bool TryCast<TNew>([NotNullWhen(true)] out Column<TNew>? newColumn)
+        {
+            if(DatabaseSchema.Property(typeof(TNew), Property) is IProperty property)
+            {
+                newColumn = new Column<TNew>(property);
+                return true;
+            }
+            else
+            {
+                newColumn = null;
+                return false;
+            }
+        }
+
         public string Property { get; private set; }
 
         public override void Deserialize(SerializationInfo info, StreamingContext context)
@@ -234,6 +255,24 @@ namespace InABox.Core
             return cols;
         }
 
+        /// <summary>
+        /// Cast the columns to <typeparamref name="TNew"/>, keeping the columns that are found in both <typeparamref name="T"/> and <typeparamref name="TNew"/>.
+        /// </summary>
+        /// <typeparam name="TNew"></typeparam>
+        /// <returns></returns>
+        public Columns<TNew> CastIntersection<TNew>()
+        {
+            var cols = new Columns<TNew>();
+            foreach(var column in columns)
+            {
+                if (column.TryCast<TNew>(out var newColumn))
+                {
+                    cols.Add(newColumn);
+                }
+            }
+            return cols;
+        }
+
         public override string ToString()
         {
             return String.Join("; ", columns.Select(x => x.Property));

+ 10 - 0
InABox.Core/EntityLink.cs

@@ -64,6 +64,16 @@ namespace InABox.Core
         [Obsolete]
         public Guid Deleted { get; set; }
 
+        /// <summary>
+        /// Basically do the same as <see cref="Synchronise(object)"/>, but also copy the <see cref="ID"/>.
+        /// </summary>
+        /// <param name="other">The link to copy data from.</param>
+        public void CopyFrom(IEntityLink<T> other)
+        {
+            ID = other.ID;
+            Synchronise(other);
+        }
+
         public virtual bool Synchronise(object Entity)
         {
             var result = false;

+ 62 - 2
InABox.Core/ILookupDefinition.cs

@@ -1,4 +1,5 @@
-using System;
+using InABox.Clients;
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
@@ -296,7 +297,9 @@ namespace InABox.Core
         /// Get a list of columns that need to be loaded in a lookup for property <paramref name="column"/> on <paramref name="TEntity"/>.
         /// </summary>
         /// <remarks>
-        /// <paramref name="column"/> can either be the name of an entity link, or the ID property of that entity link.
+        /// <paramref name="column"/> can either be the name of an entity link, or the ID property of that entity link.<br/>
+        /// Gives an aggregate of required columns on the entity link in question, the linked properties of <paramref name="TEntity"/>, and any defined on
+        /// a <see cref="LookupDefinitionAttribute"/> or <see cref="ILookupDefinition"/>.
         /// </remarks>
         /// <param name="TEntity"></param>
         /// <param name="TLookup"></param>
@@ -354,6 +357,9 @@ namespace InABox.Core
         /// <summary>
         /// Get a list of columns that need to be loaded for an entity of type <paramref name="T"/>.
         /// </summary>
+        /// <remarks>
+        /// Gives an aggregate of required columns, and columns found on <see cref="LookupDefinitionAttribute"/> and <see cref="ILookupDefinition"/>.
+        /// </remarks>
         /// <param name="T"></param>
         /// <returns></returns>
         public static IColumns RequiredColumns(Type T)
@@ -475,6 +481,60 @@ namespace InABox.Core
             where TLookup : BaseObject
             => OnCreateItem(typeof(TEntity), CoreUtils.GetFullPropertyName(column, "."), items, item);
 
+        public static void DoLookup<TEntity, TLookup, TLookupLink>(TEntity entity, Expression<Func<TEntity, TLookupLink>> column, Guid lookupID)
+            where TEntity : class
+            where TLookup : Entity, IRemotable, IPersistent, new()
+            where TLookupLink : IEntityLink<TLookup>
+        {
+            if(lookupID == Guid.Empty)
+            {
+                return;
+            }
+            var columns = DefineLookupColumns<TEntity, TLookup, TLookupLink>(column);
+            var row = Client.Query(new Filter<TLookup>(x => x.ID).IsEqualTo(lookupID), columns).Rows.FirstOrDefault();
+            if(row is null)
+            {
+                Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in DoLookup: {typeof(TLookup).Name} with ID {lookupID} not found.");
+                return;
+            }
+
+            foreach(var subColumn in columns)
+            {
+                CoreUtils.SetPropertyValue(entity, CoreUtils.GetFullPropertyName(column, ".") + "." + subColumn.Property, row[subColumn.Property]);
+            }
+        }
+
+        public static void DoLookups<TEntity, TLookup, TLookupLink>(IEnumerable<Tuple<TEntity, Guid>> lookups, Expression<Func<TEntity, TLookupLink>> column)
+            where TEntity : class
+            where TLookup : Entity, IRemotable, IPersistent, new()
+            where TLookupLink : IEntityLink<TLookup>
+        {
+            lookups = lookups.AsIList();
+            var lookupIDs = lookups.Select(x => x.Item2).Where(x => x != Guid.Empty).ToArray();
+
+            var columns = DefineLookupColumns<TEntity, TLookup, TLookupLink>(column).Add(x => x.ID);
+            var data = Client.Query(new Filter<TLookup>(x => x.ID).InList(lookupIDs), columns).Rows.ToDictionary(x => x.Get<TLookup, Guid>(x => x.ID));
+
+            foreach(var (entity, lookupID) in lookups)
+            {
+                if(lookupID == Guid.Empty)
+                {
+                    continue;
+                }
+
+                if(!data.TryGetValue(lookupID, out var row))
+                {
+                    Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in DoLookup: {typeof(TLookup).Name} with ID {lookupID} not found.");
+                    continue;
+                }
+
+                foreach(var subColumn in columns)
+                {
+                    CoreUtils.SetPropertyValue(entity, CoreUtils.GetFullPropertyName(column, ".") + "." + subColumn.Property, row[subColumn.Property]);
+                }
+            }
+        }
+
         #endregion
     }
 

+ 37 - 2
inabox.wpf/DynamicGrid/DynamicGridUtils.cs

@@ -536,13 +536,14 @@ public static class DynamicGridUtils
     /// <param name="pageDataHandler"></param>
     /// <param name="preloadPages"></param>
     /// <returns></returns>
-    public static bool Edit<T>(T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
+    public static bool EditObjects<T>(T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
         where T : BaseObject, new()
     {
         var grid = new DynamicItemsListGrid<T>();
         customiseGrid?.Invoke(grid);
         return grid.EditItems(items, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
     }
+
     /// <summary>
     /// Edit (using <see cref="DynamicItemsListGrid{T}"/>) a <see cref="BaseObject"/>s. Use for objects not saved in the database.
     /// </summary>
@@ -551,7 +552,7 @@ public static class DynamicGridUtils
     /// <param name="pageDataHandler"></param>
     /// <param name="preloadPages"></param>
     /// <returns></returns>
-    public static bool Edit<T>(T item, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
+    public static bool EditObject<T>(T item, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
         where T : BaseObject, new()
     {
         var grid = new DynamicItemsListGrid<T>();
@@ -559,6 +560,40 @@ public static class DynamicGridUtils
         return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
     }
 
+    /// <summary>
+    /// Edit (using a grid sourced with <see cref="CreateDynamicGrid{T}(Type)"/>) a <typeparamref name="T"/>.
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    /// <param name="item"></param>
+    /// <param name="pageDataHandler"></param>
+    /// <param name="preloadPages"></param>
+    /// <param name="customiseGrid"></param>
+    /// <returns></returns>
+    public static bool EditEntity<T>(T item, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
+        where T : Entity, new()
+    {
+        var grid = CreateDynamicGrid<T>(typeof(DynamicGrid<>));
+        customiseGrid?.Invoke(grid);
+        return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
+    }
+
+    /// <summary>
+    /// Edit (using a grid sourced with <see cref="CreateDynamicGrid{T}(Type)"/>) a list of <typeparamref name="T"/>.
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    /// <param name="item"></param>
+    /// <param name="pageDataHandler"></param>
+    /// <param name="preloadPages"></param>
+    /// <param name="customiseGrid"></param>
+    /// <returns></returns>
+    public static bool EditEntities<T>(T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
+        where T : Entity, new()
+    {
+        var grid = CreateDynamicGrid<T>(typeof(DynamicGrid<>));
+        customiseGrid?.Invoke(grid);
+        return grid.EditItems(items, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
+    }
+
     #endregion
 
     #region Drag + Drop

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

@@ -15,7 +15,7 @@ namespace InABox.DynamicGrid;
 /// instead keeping them in memory.
 /// <br/>
 /// This interface then allows other functions, like
-/// <see cref="DynamicMemoryEntryGridExtensions.EnsureColumns{T}(InABox.DynamicGrid.IDynamicMemoryEntityGrid{T}, Columns{T})"/>, to work based on <see cref="IDynamicMemoryEntityGrid{T}.LoadedColumns"/> and manage our column handling better.
+/// <see cref="DynamicMemoryEntityGridExtensions.EnsureColumns{T}(InABox.DynamicGrid.IDynamicMemoryEntityGrid{T}, Columns{T})"/>, to work based on <see cref="IDynamicMemoryEntityGrid{T}.LoadedColumns"/> and manage our column handling better.
 /// </summary>
 /// <typeparam name="T"></typeparam>
 public interface IDynamicMemoryEntityGrid<T>
@@ -35,7 +35,7 @@ public interface IDynamicMemoryEntityGrid<T>
     public IEnumerable<T> Items { get; }
 }
 
-public static class DynamicMemoryEntryGridExtensions
+public static class DynamicMemoryEntityGridExtensions
 {
     public static void EnsureColumns<T>(this IDynamicMemoryEntityGrid<T> grid, Columns<T> columns)
         where T : Entity, IRemotable, IPersistent, new()