using InABox.Clients; using InABox.Core; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; namespace InABox.DynamicGrid; public class DynamicGridMemoryEntityDataComponent : BaseDynamicGridDataComponent where T : Entity, IRemotable, IPersistent, new() { /// /// Keeps a cache of initially loaded objects, so that we can figure out which guys to delete when we save. /// private T[] MasterList = Array.Empty(); public List Items { get; private set; } /// /// 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; private set; } private static bool IsAutoEntity => typeof(T).HasAttribute(); public DynamicGridMemoryEntityDataComponent(DynamicGrid grid): base(grid) { Items = new List(); } #region MemoryEntityGrid public void LoadData(T[] items) { MasterList = items; Items = MasterList.ToList(); LoadedColumns = null; } public void LoadData(CoreTable data) { MasterList = data.ToArray(); Items = MasterList.ToList(); LoadedColumns = null; } public void LoadData(Filter? filter, Columns columns, SortOrder? sort) { var data = Client.Query(filter, columns, sort); MasterList = data.ToArray(); Items = MasterList.ToList(); LoadedColumns = columns.ColumnNames().ToHashSet(); } public void SaveItems() { if (IsAutoEntity) { return; } // First remove any deleted files foreach (var map in MasterList) if (!Items.Contains(map)) Client.Delete(map, typeof(T).Name + " Deleted by User"); Client.Save(Items.Where(x => x.IsChanged()), "Updated by User"); } #endregion public override CoreTable LoadImportKeys(string[] fields) { var result = new CoreTable(); result.LoadColumns(Columns.None().Add(fields)); result.LoadRows(MasterList); return result; } public override T LoadItem(CoreRow row) { return Items[Grid.GetMasterRow(row).Index]; } public override void SaveItem(T item) { if (!Items.Contains(item)) Items.Add(item); if (item is ISequenceable) Items = Items.AsQueryable().OrderBy(x => (x as ISequenceable)!.Sequence).ToList(); } public override void SaveItems(T[] items) { foreach(var item in items) { if (!Items.Contains(item)) Items.Add(item); } if (typeof(T).HasInterface()) Items = Items.AsQueryable().OrderBy(x => (x as ISequenceable)!.Sequence).ToList(); } public override void DeleteItems(params CoreRow[] rows) { var items = rows.Select(LoadItem).ToList(); foreach (var item in items) { Items.Remove(item); } } public override void Reload( Filters criteria, Columns columns, SortOrder? sort, CancellationToken token, Action action) { var results = new CoreTable(); results.LoadColumns(typeof(T)); EnsureColumns(columns); if (sort != null) { var exp = IQueryableExtensions.ToLambda(sort.Expression); var sorted = sort.Direction == SortDirection.Ascending ? Items.AsQueryable().OrderBy(exp) : Items.AsQueryable().OrderByDescending(exp); foreach (var then in sort.Thens) { var thexp = IQueryableExtensions.ToLambda(then.Expression); sorted = sort.Direction == SortDirection.Ascending ? sorted.ThenBy(exp) : sorted.ThenByDescending(exp); } Items = sorted.ToList(); } results.LoadRows(Items); action.Invoke(new(results)); } public void EnsureColumns(Columns columns) { RequireColumns(columns); LoadForeignProperties(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 void LoadForeignProperties(Columns columns) { // 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 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 void RequireColumns(Columns columns) { if (LoadedColumns is null) return; // Figure out which columns we still need. var newColumns = columns.Where(x => !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(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 = 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) { LoadedColumns.Add(column.Property); } } } }