using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Linq; using System.Linq.Expressions; using System.Reflection; using InABox.Clients; namespace InABox.Core { public delegate void DataModelUpdateEvent(string section, DataModel model); [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] public sealed class DataModelTableNameAttribute : Attribute { public string TableName { get; set; } public DataModelTableNameAttribute(string tableName) { TableName = tableName; } } public interface IDataModelSource { string SectionName { get; } DataModel DataModel(Selection selection); event DataModelUpdateEvent? OnUpdateDataModel; } public interface IDataModelRelationship { string ChildColumn { get; } string ChildTable { get; } string ParentColumn { get; } string ParentTable { get; } bool IsLookup { get; } DataRelation AsDataRelation(); string ParentColumnAsPropertyName(); string ChildColumnAsPropertyName(); } public interface IDataModelQueryDef : IQueryDef { string TableName { get; } } public class DataModelQueryDef : IDataModelQueryDef { public DataModelQueryDef(Filter filter, Columns columns, SortOrder sortorder, CoreRange? range = null, string? alias = null) { Type = typeof(T); Filter = filter; Columns = columns; SortOrder = sortorder; TableName = DataModel.TableName(Type, alias); Range = range; } public Type Type { get; } public IFilter Filter { get; } public IColumns Columns { get; } public ISortOrder SortOrder { get; } public CoreRange? Range { get; set; } public string TableName { get; } } public delegate void OnBeforeLoad(CancelEventArgs args); public delegate void OnAfterLoad(CancelEventArgs args); public interface IDataModel { IEnumerable DefaultTables { get; } IEnumerable> ModelTables { get; } void AddTable(string alias, CoreTable table, bool isdefault = false); void AddTable(Type type, CoreTable table, bool isdefault = false, string? alias = null); /// /// Adds a table to the datamodel. /// /// The type of the object represented by the table. /// A filter for the table. If set to , loads all objects of . /// The columns to load for this table. If set to , loads all the columns of . /// /// Is this table default loaded? If set to , this table is added to . /// The name of this table. Defaults to if not set or . /// /// if this table should be loaded - in some cases a table's data should loaded manually.
/// Set to if this is the case. /// void AddTable(Filter? filter, Columns? columns, bool isdefault = false, string? alias = null, bool shouldLoad = true); void LinkTable(Type parenttype, string parentcolumn, Type childtype, string childcolumn, string? parentalias = null, string? childalias = null, bool isLookup = false); void LinkTable(Type parenttype, string parentcolumn, string childalias, string childcolumn, string? parentalias = null, bool isLookup = false); void LinkTable(Expression> parent, Expression> child, string? parentalias = null, string? childalias = null, bool isLookup = false); void AddChildTable(Expression> parentcolumn, Expression> childcolumn, Filter? filter = null, Columns? columns = null, bool isdefault = false, string? parentalias = null, string? childalias = null); void AddLookupTable(Expression> sourcecolumn, Expression> lookupcolumn, Filter? filter = null, Columns? columns = null, bool isdefault = false, string? sourcealias = null, string? lookupalias = null); /// /// Remove a table from the data model. /// /// The type of the table to be removed. /// The table name, defaulting to the name of . /// if the table was removed, if it was not removed because it didn't exist. bool RemoveTable(string? alias = null); /// /// Gets the filter for a given table, which is used during The type of the table to get the filter of. /// The name of the table, defaulting to /// The filter. Filter? GetFilter(string? alias = null); /// /// Gets the columns for a given table, which are used during The type of the table to get the columns of. /// The name of the table, defaulting to /// The columns. Columns? GetColumns(string? alias = null); /// /// Sets the filter for a given table, which is used during The type of the table to set the filter of. /// The new filter. /// The name of the table, defaulting to void SetFilter(Filter? filter, string? alias = null); /// /// Sets the columns for a given table, which are used during The type of the table. /// The new columns. /// The name of the table, defaulting to . void SetColumns(Columns? columns, string? alias = null); void SetIsDefault(bool isDefault, string? alias = null); void SetShouldLoad(bool shouldLoad, string? alias = null); CoreTable GetTable(string? alias = null); void SetTableData(Type type, CoreTable tableData, string? alias = null); bool HasTable(Type type, string? alias = null); bool HasTable(string? alias = null); void LoadModel(IEnumerable? requiredTables, Dictionary? requiredQueries = null); void LoadModel(IEnumerable? requiredTables, params IDataModelQueryDef[] requiredQueries); /// /// Load the model, loading all tables that are set to be default. (See ). /// void LoadModel(); TType[] ExtractValues(Expression> column, bool distinct = true, string? alias = null); } public interface IDataModel : IDataModel where T : Entity, IRemotable, IPersistent, new() { } public abstract class DataModel : IDataModel { private readonly List _relationships = new List(); private readonly Dictionary _tables = new Dictionary(); public DataModel() { AddTable(null, null, true); AddChildTable(x => x.Logo.ID, x => x.ID, null, null, true, null, "CompanyLogo"); AddTable(new Filter(x => x.ID).IsEqualTo(ClientFactory.UserGuid), null, true); } public IEnumerable> ModelTables => _tables; public abstract string Name { get; } public IEnumerable Relations => _relationships.Select(x => x.AsDataRelation()).ToArray(); public IEnumerable Tables => _tables.Select(x => x.Value.Table.ToDataTable(x.Key)); public IEnumerable DefaultTables => _tables.Where(x => x.Value.IsDefault).Select(x => x.Value.Table.ToDataTable(x.Key)); public IEnumerable DefaultTableNames => _tables.Where(x => x.Value.IsDefault).Select(x => x.Key); public IEnumerable TableNames => _tables.Select(x => x.Key); public TType[] ExtractValues(Expression> column, bool distinct = true, string? alias = null) { return GetTable(alias).ExtractValues(column, distinct).ToArray(); } public DataSet AsDataSet() { var result = new DataSet(); foreach(var (key, table) in _tables) { var current = table.Table.Columns.ToDictionary(x => x.ColumnName, x => x); IColumns? additional = null; if(table.Type != null) { additional = Columns.None(table.Type); foreach (var column in CoreUtils.GetColumnNames(table.Type, x => true)) { if (!current.ContainsKey(column)) { additional.Add(column); } } } var dataTable = table.Table.ToDataTable(key, additional); result.Tables.Add(dataTable); } //result.Tables.AddRange(_tables.Select(x => x.Value.Table.ToDataTable(x.Key)).ToArray()); foreach (var relation in _relationships) { var childTable = result.Tables[relation.ChildTable]; var parentTable = result.Tables[relation.ParentTable]; if (childTable is null) { continue; } if (parentTable is null) { result.Tables.Remove(childTable); continue; } var parentColumn = parentTable.Columns[relation.ParentColumn.Replace(".", "_")]; var childColumn = childTable.Columns[relation.ChildColumn.Replace(".", "_")]; if (parentColumn is null || childColumn is null) { result.Tables.Remove(childTable); continue; } if (relation.IsLookup) { result.Relations.Add( string.Format("{0}_{1}", relation.ChildTable, relation.ParentTable), childColumn, parentColumn, false ); } else { result.Relations.Add( string.Format("{0}_{1}", relation.ParentTable, relation.ChildTable), parentColumn, childColumn, false ); } } return result; } public event OnBeforeLoad? OnBeforeLoad; public event OnAfterLoad? OnAfterLoad; protected virtual void BeforeLoad(IEnumerable requiredtables) { } protected virtual void CheckRequiredTables(List requiredtables) { } //protected abstract void Load(IEnumerable requiredtables); protected virtual void AfterLoad(IEnumerable requiredTables) { } protected void Load(Type type, CoreTable data, IEnumerable requiredtables, string? alias = null) { CheckTable(type, alias); var name = TableName(type, alias); var table = _tables[name].Table; if(!ReferenceEquals(table, data)) { table.Rows.Clear(); if (IsRequired(type, requiredtables, alias)) data.CopyTo(table); } } protected void Load(CoreTable data, IEnumerable requiredtables, string? alias = null) { Load(typeof(TType), data, requiredtables, alias); } protected void Load(IEnumerable items, IEnumerable requiredtables, string? alias = null) where TType : notnull { CheckTable(alias); var name = TableName(alias); var table = _tables[name].Table; table.Rows.Clear(); if (IsRequired(requiredtables)) foreach (var item in items) { table.LoadRow(item); } } private void CheckTable(string? alias = null) { CheckTable(typeof(TType), alias); } protected string TableName(string? alias = null) { return TableName(typeof(TType), alias); } public class DataModelTable { private bool shouldLoad; public DataModelTable(Type? type, CoreTable table, bool isDefault, IFilter? filter, IColumns? columns, bool shouldLoad = true) { Type = type; Table = table; IsDefault = isDefault; Filter = filter; Columns = columns; ShouldLoad = shouldLoad; } public Type? Type { get; } public CoreTable Table { get; set; } public bool IsDefault { get; set; } public IFilter? Filter { get; set; } public IColumns? Columns { get; set; } public bool ShouldLoad { get => shouldLoad && Type != null; set { shouldLoad = value; } } } #region New Load Methods private Filter GetSubquery(IDataModelRelationship relation, Dictionary requiredQueries) where TParent : Entity, IRemotable, IPersistent, new() where TChild : Entity, IRemotable, IPersistent, new() { var parentFilter = GetTableFilter(relation.ParentTable, requiredQueries); var subQuery = new SubQuery(parentFilter, new Column(relation.ParentColumnAsPropertyName())); var filter = new Filter(); filter.Expression = CoreUtils.CreateMemberExpression(typeof(TChild), relation.ChildColumnAsPropertyName()); filter.Operator = Operator.InQuery; filter.Value = subQuery; return filter; } private Filter? GetTableFilter(string tableName, Dictionary requiredQueries) where TType : Entity, IRemotable, IPersistent, new() { var newFilter = _tables[tableName].Filter as Filter; IQueryDef? query = null; requiredQueries?.TryGetValue(tableName, out query); if (query?.Filter is Filter filter) { if (newFilter != null) newFilter.And(filter); else newFilter = filter; } var relation = _relationships.Where(x => x.ChildTable == tableName).FirstOrDefault(); if (relation != null) { var table = _tables[relation.ParentTable]; if(table.Type != null) { var subFilter = (typeof(DataModel).GetMethod(nameof(GetSubquery), BindingFlags.NonPublic | BindingFlags.Instance) .MakeGenericMethod(_tables[relation.ParentTable].Type, typeof(TType)) .Invoke(this, new object?[] { relation, requiredQueries }) as Filter)!; if (newFilter != null) newFilter.And(subFilter); else newFilter = subFilter; } } return newFilter; } private IQueryDef LoadModelTable(string tableName, Dictionary requiredQueries) where TType : Entity, IRemotable, IPersistent, new() { var newFilter = GetTableFilter(tableName, requiredQueries); var newColumns = _tables[tableName].Columns as Columns; var newSort = LookupFactory.DefineSort(); IQueryDef? query = null; requiredQueries?.TryGetValue(tableName, out query); if (query == null) return new QueryDef(newFilter, newColumns, newSort); if (query.Columns != null) newColumns = query.Columns as Columns; return new QueryDef( newFilter, newColumns, query.SortOrder as SortOrder ); } public virtual void LoadModel(IEnumerable? requiredTables, Dictionary? requiredQueries = null) { var requiredTablesList = requiredTables != null ? requiredTables.ToList() : new List(); CheckRequiredTables(requiredTablesList); var args = new CancelEventArgs(); OnBeforeLoad?.Invoke(args); if (!args.Cancel) BeforeLoad(requiredTablesList); var queries = new Dictionary(); var genericMethod = typeof(DataModel).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) .Where(x => x.Name == nameof(LoadModelTable) && x.IsGenericMethod) .FirstOrDefault()!; foreach (var table in _tables) if (table.Value.ShouldLoad) if (requiredTables == null || requiredTablesList.Contains(table.Key)) queries[table.Key] = (genericMethod.MakeGenericMethod(table.Value.Type).Invoke(this, new object?[] { table.Key, requiredQueries }) as IQueryDef)!; var results = Client.QueryMultiple(queries); foreach (var result in results) if (_tables.TryGetValue(result.Key, out var table)) table.Table = result.Value; else Logger.Send(LogType.Error, "", string.Format("QueryMultiple returned table with key {0}, which is not in the data model!", result.Key)); args = new CancelEventArgs(); OnAfterLoad?.Invoke(args); if (!args.Cancel) AfterLoad(requiredTablesList); } public void LoadModel(IEnumerable? requiredTables, params IDataModelQueryDef[] requiredQueries) { LoadModel(requiredTables, requiredQueries.ToDictionary(x => x.TableName, x => x as IQueryDef)); } public void LoadModel() { LoadModel(DefaultTableNames); } #endregion #region Non-Generic Stuff public bool IsChildTable(string tableName) { return _relationships.Any(x => x.ChildTable == tableName); } public static string TableName(Type? type, string? alias = null) { return string.IsNullOrWhiteSpace(alias) ? (type ?? throw new Exception("No type or alias given!")).EntityName().Split('.').Last() : alias; } private void CheckTable(Type? type, string? alias = null) { var name = TableName(type, alias); if (!_tables.ContainsKey(name)) throw new Exception(string.Format("No Table for {0}", name)); } public void AddTable(Type? type, CoreTable table, bool isdefault = false, string? alias = null) { var name = TableName(type, alias); if (!_tables.ContainsKey(name)) _tables[name] = new DataModelTable(type, table, isdefault, null, null, false); else throw new Exception(string.Format("[{0}] already exists in this data model!", name)); } public void SetTableData(Type type, CoreTable tableData, string? alias = null) { var name = TableName(type, alias); if (_tables.TryGetValue(name, out var table)) { table.Table = tableData; table.ShouldLoad = false; } else { throw new Exception(string.Format("[{0}] does not exist in this data model!", name)); } } public void LinkTable(Type parenttype, string parentcolumn, string childalias, string childcolumn, string? parentalias = null, bool isLookup = false) => LinkTable(parenttype, parentcolumn, null, childcolumn, parentalias, childalias, isLookup); public void LinkTable(Type parenttype, string parentcolumn, Type? childtype, string childcolumn, string? parentalias = null, string? childalias = null, bool isLookup = false) { CheckTable(parenttype, parentalias); CheckTable(childtype, childalias); var relationship = new DataModelRelationship( TableName(parenttype, parentalias), parentcolumn, TableName(childtype, childalias), childcolumn, isLookup ); if (!_relationships.Any(x => x.ParentTable.Equals(relationship.ParentTable) && x.ChildTable.Equals(relationship.ChildTable))) _relationships.Add(relationship); } public bool HasTable(Type type, string? alias = null) { var name = TableName(type, alias); return _tables.ContainsKey(name); } public bool HasTable(string? alias = null) => HasTable(typeof(T), alias); #endregion #region Adding & Linking Tables public void AddTable(string alias, CoreTable table, bool isdefault = false) => AddTable(null, table, isdefault, alias); public void AddTable(Filter? filter, Columns? columns, bool isdefault = false, string? alias = null, bool shouldLoad = true) { var name = TableName(alias); if (!_tables.ContainsKey(name)) { var table = new CoreTable(); if(columns != null) { table.LoadColumns(columns); } else { table.LoadColumns(typeof(TType)); } _tables[name] = new DataModelTable(typeof(TType), table, isdefault, filter, columns, shouldLoad); } } public void AddChildTable(Expression> parentcolumn, Expression> childcolumn, Filter? filter = null, Columns? columns = null, bool isdefault = false, string? parentalias = null, string? childalias = null) { CheckTable(parentalias); AddTable(filter, columns, isdefault, childalias); LinkTable(parentcolumn, childcolumn, parentalias, childalias, false); } public void AddLookupTable(Expression> sourcecolumn, Expression> lookupcolumn, Filter? filter = null, Columns? columns = null, bool isdefault = false, string? sourcealias = null, string? lookupalias = null) { CheckTable(sourcealias); AddTable(filter, columns, isdefault, lookupalias); LinkTable(sourcecolumn, lookupcolumn, sourcealias, lookupalias, true); } public CoreTable GetTable(string? alias = null) { CheckTable(alias); var name = TableName(alias); return _tables[name].Table; } public DataModelTable GetDataModelTable(string name) { return _tables[name]; } public DataModelTable GetDataModelTable(string? alias = null) { CheckTable(alias); var name = TableName(alias); return _tables[name]; } protected bool IsRequired(IEnumerable requiredtables, string? alias = null) { var name = TableName(alias); return requiredtables == null || requiredtables.Contains(name); } protected bool IsRequired(Type type, IEnumerable requiredtables, string? alias = null) { var name = TableName(type, alias); return requiredtables == null || requiredtables.Contains(name); } public void LinkTable(Expression> parent, Expression> child, string? parentalias = null, string? childalias = null, bool isLookup = false) { CheckTable(parentalias); CheckTable(childalias); var relationship = new DataModelRelationship(parentalias, parent, childalias, child, isLookup); if (!_relationships.Any(x => x.ParentTable.Equals(relationship.ParentTable) && x.ChildTable.Equals(relationship.ChildTable))) _relationships.Add(relationship); //SetupIDs(relationship.ParentColumn); } #endregion #region Getting/Setting Table Data public Filter? GetFilter(string? alias = null) { var table = GetDataModelTable(alias); return table.Filter as Filter; } public Columns? GetColumns(string? alias = null) { var table = GetDataModelTable(alias); return table.Columns as Columns; } public IColumns? GetColumns(string alias) { var table = GetDataModelTable(alias); return table.Columns; } [Obsolete("Use SetColumns instead")] public void SetTableColumns(Columns columns, string? alias = null) => SetColumns(columns, alias); public void SetFilter(Filter? filter, string? alias = null) { var table = GetDataModelTable(alias); table.Filter = filter; } public void SetColumns(Columns? columns, string? alias = null) { var table = GetDataModelTable(alias); table.Columns = columns; } public void SetIsDefault(bool isDefault, string? alias = null) { var table = GetDataModelTable(alias); table.IsDefault = isDefault; } public void SetShouldLoad(bool shouldLoad, string? alias = null) { var table = GetDataModelTable(alias); table.ShouldLoad = shouldLoad; } #endregion #region Removing Tables private bool RemoveTable(Type type, string? alias = null) { var name = TableName(type, alias); return _tables.Remove(name); } public bool RemoveTable(string? alias = null) => RemoveTable(typeof(TType), alias); #endregion #region Cache of Link Values based on relationships // Type = Parent, String = ParentColumn, List=Values //private Dictionary>> _ids = new Dictionary>>(); // private void SetupIds(String columnname) //{ // if (!_ids.ContainsKey(typeof(TParent))) // _ids[typeof(TParent)] = new Dictionary>(); // _ids[typeof(TParent)][relationship.ParentColumn] = new List(); //} //private void ClearIDs() //{ // if (_ids.ContainsKey(typeof(TType))) // { // var cols = _ids[typeof(TType)]; // foreach (var col in cols.Keys) // cols[col].Clear(); // } //} //private void UpdateIDs() //{ // if (_ids.ContainsKey(typeof(TType))) // { // var cols = _ids[typeof(TType)]; // foreach (var col in cols.Keys) // cols[col].AddRange(GetTable().ExtractValues(col, true)); // } //} //protected object[] GetIDs(String column) //{ // if (_ids.ContainsKey(typeof(TType))) // { // var cols = _ids[typeof(TType)]; // if (cols.ContainsKey(column)) // return cols[column].ToArray(); // } // return new object[] { }; //} #endregion } public abstract class DataModel : DataModel, IDataModel where T : Entity, IRemotable, IPersistent, new() { public DataModel(Filter? filter, Columns? columns = null, SortOrder? sort = null) { Filter = filter; Columns = columns; Sort = sort; AddTable(filter, columns, true); AddChildTable(x => x.ID, x => x.EntityID); } public Filter? Filter { get; set; } public Columns? Columns { get; set; } public SortOrder? Sort { get; set; } } public class DataModelRelationship : IDataModelRelationship { public DataModelRelationship(string parenttable, string parentcolumn, string childtable, string childcolumn, bool isLookup = false) { ParentTable = parenttable; ParentColumn = parentcolumn; ChildTable = childtable; ChildColumn = childcolumn; IsLookup = isLookup; } public string ChildColumn { get; } public string ChildTable { get; } public string ParentColumn { get; } public string ParentTable { get; } public bool IsLookup { get; } public DataRelation AsDataRelation() { string parentTable, parentColumn, childTable, childColumn; // Reverse the relationships if it is a lookup if (IsLookup) { parentTable = ChildTable; parentColumn = ChildColumn; childTable = ParentTable; childColumn = ParentColumn; } else { parentTable = ParentTable; parentColumn = ParentColumn; childTable = ChildTable; childColumn = ChildColumn; } var result = new DataRelation( string.Format("{0}_{1}_{2}_{3}", parentTable, parentColumn, childTable, childColumn), parentTable, childTable, new[] { parentColumn }, new[] { childColumn }, false ); result.RelationName = string.Format("{0}_{1}_{2}_{3}", parentTable, parentColumn, childTable, childColumn); return result; } public string ParentColumnAsPropertyName() { return ParentColumn; } public string ChildColumnAsPropertyName() { return ChildColumn; } } public class DataModelRelationship : IDataModelRelationship { public DataModelRelationship(string? parentalias, Expression> parent, string? childalias, Expression> child, bool isLookup) { ParentTable = string.IsNullOrWhiteSpace(parentalias) ? typeof(TParent).EntityName().Split('.').Last() : parentalias; Parent = parent; ChildTable = string.IsNullOrWhiteSpace(childalias) ? typeof(TChild).EntityName().Split('.').Last() : childalias; Child = child; IsLookup = isLookup; } public Expression> Child { get; } public Expression> Parent { get; } public string ChildTable { get; } public string ChildColumn => CoreUtils.GetFullPropertyName(Child, ".").Replace('.', '_'); public string ParentTable { get; } public string ParentColumn => CoreUtils.GetFullPropertyName(Parent, ".").Replace('.', '_'); public bool IsLookup { get; } public string ParentColumnAsPropertyName() { return CoreUtils.GetFullPropertyName(Parent, "."); } public string ChildColumnAsPropertyName() { return CoreUtils.GetFullPropertyName(Child, "."); } public DataRelation AsDataRelation() { string parentTable, parentColumn, childTable, childColumn; // Reverse the relationships if it is a lookup if (IsLookup) { parentTable = ChildTable; parentColumn = ChildColumn; childTable = ParentTable; childColumn = ParentColumn; } else { parentTable = ParentTable; parentColumn = ParentColumn; childTable = ChildTable; childColumn = ChildColumn; } var result = new DataRelation( string.Format("{0}_{1}_{2}_{3}", parentTable, parentColumn, childTable, childColumn), parentTable, childTable, new[] { parentColumn }, new[] { childColumn }, false ); result.RelationName = string.Format("{0}_{1}_{2}_{3}", parentTable, parentColumn, childTable, childColumn); return result; } } }