using InABox.Clients; using InABox.Core; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace InABox.DynamicGrid; /// /// Defines a common interface for dealing with grids like /// or , which display s, but do not load them necessarily from the database, /// instead keeping them in memory. ///
/// This interface then allows other functions, like /// , to work based on and manage our column handling better. ///
/// public interface IDynamicMemoryEntityGrid where T : Entity, IRemotable, IPersistent, new() { /// /// A set of columns representing which columns have been loaded from the database. /// /// /// This is used to refresh the data when the columns change.
/// /// It is if no data has been loaded from the database (that is, the data was gotten from /// a page data handler instead.) ///
public HashSet? LoadedColumns { get; } public IEnumerable Items { get; } } public static class DynamicMemoryEntityGridExtensions { public static void EnsureColumns(this IDynamicMemoryEntityGrid grid, Columns columns) where T : Entity, IRemotable, IPersistent, new() { RequireColumns(grid, columns); LoadForeignProperties(grid, columns); } /// /// Load the properties of any s on this where the is not . /// This allows us to populate columns of transient objects, as long as they are linked by the ID. What this actually then does is query each /// linked table with the required columns. /// /// public static void LoadForeignProperties(this IDynamicMemoryEntityGrid grid, Columns columns) where T : Entity, IRemotable, IPersistent, new() { // Lists of properties that we need, arranged by the entity link property which is their parent. // LinkIDProperty : (Type, Properties: [(columnName, property)], Objects) var newData = new Dictionary>, HashSet>>(); foreach (var column in columns) { var property = DatabaseSchema.Property(typeof(T), column.Property); if (property?.GetOuterParent(x => x.IsEntityLink) is IProperty linkProperty) { var remaining = column.Property[(linkProperty.Name.Length + 1)..]; if (remaining.Equals(nameof(IEntityLink.ID))) { // This guy isn't foreign, so we don't pull him. continue; } var idProperty = DatabaseSchema.Property(typeof(T), linkProperty.Name + "." + nameof(IEntityLink.ID))!; var linkType = linkProperty.PropertyType.GetInterfaceDefinition(typeof(IEntityLink<>))!.GenericTypeArguments[0]; if (!newData.TryGetValue(idProperty, out var data)) { data = new Tuple>, HashSet>( linkType, new List>(), new HashSet()); newData.Add(idProperty, data); } var any = false; foreach (var item in grid.Items) { if (!item.LoadedColumns.Contains(column.Property)) { var linkID = (Guid)idProperty.Getter()(item); if (linkID != Guid.Empty) { any = true; data.Item3.Add(item); } } } if (any) { data.Item2.Add(new(remaining, property)); } } } var queryDefs = new List(); foreach (var (prop, data) in newData) { if (data.Item2.Count != 0) { var ids = data.Item3.Select(prop.Getter()).Cast().ToArray(); queryDefs.Add(new KeyedQueryDef(prop.Name, data.Item1, Filter.Create(data.Item1, x => x.ID).InList(ids), Columns.None(data.Item1) .Add(data.Item2.Select(x => x.Item1)) .Add(x => x.ID))); } } var results = Client.QueryMultiple(queryDefs); foreach(var (prop, data) in newData) { var table = results.GetOrDefault(prop.Name); if(table is null) { continue; } foreach (var entity in data.Item3) { var linkID = (Guid)prop.Getter()(entity); var row = table.Rows.FirstOrDefault(x => x.Get(x => x.ID) == linkID); if (row is not null) { foreach (var (name, property) in data.Item2) { if (!entity.LoadedColumns.Contains(property.Name)) { property.Setter()(entity, row[name]); entity.LoadedColumns.Add(property.Name); } } } } } } public static void RequireColumns(this IDynamicMemoryEntityGrid grid, Columns columns) where T : Entity, IRemotable, IPersistent, new() { if (grid.LoadedColumns is null) return; // Figure out which columns we still need. var newColumns = columns.Where(x => !grid.LoadedColumns.Contains(x.Property)).ToColumns(ColumnTypeFlags.None); if (newColumns.Count > 0 && typeof(T).GetCustomAttribute() is null) { var data = Client.Query( new Filter(x => x.ID).InList(grid.Items.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 item = grid.Items.FirstOrDefault(x => x.ID == row.Get(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) { grid.LoadedColumns.Add(column.Property); } } } }