/// <summary> /// Performs the required includes on the specified <paramref name="queryable"/> /// </summary> /// <param name="queryable">The queryable</param> /// <param name="joinMap">The join map</param> /// <returns></returns> private IQueryable PerformThenInclude(IQueryable queryable, JoinMap joinMap) { // Get the ThenInclude<TEntity, TPreviousProperty, TProperty> method var thenIncludeMethod = typeof(EntityFrameworkQueryableExtensions).GetMethods().Where(x => x.Name == nameof(EntityFrameworkQueryableExtensions.ThenInclude)).ElementAt(0); // Get the type of the model var modelType = queryable.GetType().GetGenericArguments()[0]; // Get the previous property type var previousPropertyType = TypeHelpers.GetNonEnumerableType(queryable.GetType().GetGenericArguments()[1]); // Get the navigation property name var navigationPropertyName = CeidDiplomatikiHelpers.GetPluralForm(joinMap.ReferencedTable.TableName); // Get the navigation property var navigationProperty = previousPropertyType.GetProperty(navigationPropertyName); // Create the expression var expression = ExpressionHelpers.CreatePropertySelectorExpression(previousPropertyType, navigationProperty); // Set the generic arguments (TEntity, TPreviousProperty, TProperty) thenIncludeMethod = thenIncludeMethod.MakeGenericMethod(modelType, previousPropertyType, navigationProperty.PropertyType); // Call the ThenInclude method queryable = (IQueryable)thenIncludeMethod.Invoke(null, new object[] { queryable, expression }); // Return the queryable return(queryable); }
/// <summary> /// Performs the required includes on the specified <paramref name="queryable"/> /// </summary> /// <param name="queryable">The queryable</param> /// <param name="initialTable">The initial table</param> /// <returns></returns> private IQueryable PerformIncludes(IQueryable queryable, IDbProviderTable initialTable) { // For every join... foreach (var join in Joins) { var joinsPath = ComputePath(join); // For every related join... foreach (var j in joinsPath) { // Get the type of the model var modelType = queryable.GetType().GetGenericArguments()[0]; // If it's the first join... if (joinsPath.ElementAt(0) == j) { // Get the navigation property name var navigationPropertyName = CeidDiplomatikiHelpers.GetPluralForm(j.ReferencedTable.TableName); // Get the navigation property var navigationProperty = modelType.GetProperty(navigationPropertyName); // Create the expression var expression = ExpressionHelpers.CreatePropertySelectorExpression(modelType, navigationProperty); // Get the Include<TEntity, TProperty> method var includeMethod = typeof(EntityFrameworkQueryableExtensions).GetMethods().First(x => x.Name == nameof(EntityFrameworkQueryableExtensions.Include) && x.GetParameters()[1].ParameterType != typeof(string)); // Set the generic arguments (TEntity and TProperty) includeMethod = includeMethod.MakeGenericMethod(modelType, navigationProperty.PropertyType); // Call the Include method queryable = (IQueryable)includeMethod.Invoke(null, new object[] { queryable, expression }); // Continue continue; } queryable = PerformThenInclude(queryable, j); } } // Return the queryable return(queryable); }
/// <summary> /// Creates and returns a <see cref="QueryMap"/> from the specified <paramref name="dataModel"/>. /// NOTE: If the model is not set, then the default values are used! /// </summary> /// <param name="databaseOptions">The database options</param> /// <param name="database">The database that contains the mapped table</param> /// <param name="columns">The columns of the tables</param> /// <param name="tables">The tables</param> /// <param name="joins">The joins</param> /// <param name="dataModel">The data model</param> /// <returns></returns> public static QueryMap FromDataModel(BaseDatabaseOptionsDataModel databaseOptions, IDbProviderDatabase database, IEnumerable <IDbProviderTable> tables, IEnumerable <IDbProviderColumn> columns, IEnumerable <JoinMap> joins, QueryMapDataModel dataModel = null) { // Create the map var queryMap = new QueryMap(databaseOptions, database, tables, columns, joins); // If there is a data model... if (dataModel != null) { queryMap.Id = dataModel.Id; queryMap.Color = dataModel.Color; queryMap.Description = dataModel.Description; queryMap.Name = dataModel.Name; } var tableToTypeBuilderMapper = new Dictionary <IDbProviderTable, TypeBuilder>(); // For every table... foreach (var table in tables) { // Create the builder var typeBuilder = CeidDiplomatikiHelpers.GetTypeBuilder(queryMap.Id + "-" + table.TableName); // For every table column... foreach (var column in columns.Where(x => x.TableName == table.TableName)) { // Create a property TypeBuilderHelpers.CreateProperty(typeBuilder, column.ColumnName, column.DataType); } // Map it tableToTypeBuilderMapper.Add(table, typeBuilder); } // For every join... foreach (var join in joins) { // Get the principle model type builder var principleModelTypeBuilder = tableToTypeBuilderMapper.First(x => x.Key == join.Table).Value; // Get the referenced model type builder var referencedModelTypeBuilder = tableToTypeBuilderMapper.First(x => x.Key == join.ReferencedTable).Value; // Create the principle navigation property type var principleNavigationPropertyType = typeof(IEnumerable <>).MakeGenericType(referencedModelTypeBuilder); // Add it to the principle model TypeBuilderHelpers.CreateProperty(principleModelTypeBuilder, CeidDiplomatikiHelpers.GetPluralForm(join.ReferencedTable.TableName), principleNavigationPropertyType); // Add the foreign navigation property type TypeBuilderHelpers.CreateProperty(referencedModelTypeBuilder, join.Table.TableName, principleModelTypeBuilder); } var tableToTypeMapper = new Dictionary <IDbProviderTable, Type>(); // For every table to type builder map... foreach (var map in tableToTypeBuilderMapper) { // Build the type var type = map.Value.CreateType(); if (joins.Any(x => x.Index == 0 && x.Table.TableName == map.Key.TableName)) { queryMap.RootType = type; } else if (joins.Count() == 0) { queryMap.RootType = type; } // Map it tableToTypeMapper.Add(map.Key, type); } // Set the types to the query queryMap.DataModelTypes = tableToTypeMapper.Select(x => x.Value).ToList(); // Create the db context type builder var dbContextTypeBuilder = CeidDiplomatikiHelpers.GetTypeBuilder(Guid.NewGuid().ToString() + "-DbContext"); // Set the base type var baseType = typeof(PresenterDbContext); // Inherit from the PresenterDbContext dbContextTypeBuilder.SetParent(baseType); // For every data model type... foreach (var map in tableToTypeMapper) { // Create the DbSet type that will be set as a property to the DbContext var dbSetType = typeof(DbSet <>).MakeGenericType(map.Value); // Add it to the type TypeBuilderHelpers.CreateProperty(dbContextTypeBuilder, map.Key.TableName, dbSetType); } // Create the constructors TypeBuilderHelpers.CreatePassThroughConstructors(dbContextTypeBuilder, baseType); // Create the db context type queryMap.DbContextType = dbContextTypeBuilder.CreateType(); // If there is a data model... // NOTE: We create the column and the data presenter maps at the end of the initialization // because some properties of the query map are required to get set! if (dataModel != null) { dataModel.PropertyMaps.ForEach(model => queryMap.Add(PropertyMap.FromDataModel(queryMap, queryMap.DataModelTypes.First(x => queryMap.GetTableName(x) == model.TableName), dataModel.PropertyMaps.First(x => x.TableName == model.TableName && x.ColumnName == model.ColumnName)))); dataModel.DataGridPresenterMaps.ForEach(model => queryMap.Add(DataGridPresenterMap.FromDataModel(queryMap, model))); } // Return the map return(queryMap); }
/// <summary> /// Override this method to further configure the model that was discovered by convention /// from the entity types exposed in Microsoft.EntityFrameworkCore.DbSet`1 properties /// on your derived context. The resulting model may be cached and re-used for subsequent /// instances of your derived context. /// </summary> /// <param name="modelBuilder">The model builder</param> protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // For every DbSet... foreach (var dbSetProperty in GetType().GetProperties().Where(x => x.PropertyType.IsGenericType && x.PropertyType.GetGenericTypeDefinition() == typeof(DbSet <>))) { // Get the table var table = QueryMap.Tables.First(x => x.TableName == dbSetProperty.Name); // Get the name of the primary key column var primaryKeyColumnName = QueryMap.Columns.First(x => x.TableName == table.TableName && x.IsPrimaryKey).ColumnName; // Get the generic type of the DbSet var modelType = dbSetProperty.PropertyType.GetGenericArguments()[0]; // We need to recreate the following Fluent API command using reflection // modelBuilder.Entity<TModel>().HasKey(new string[]{ IdColumnName }); // Get the Entity<> method var entityMethod = modelBuilder.GetType().GetMethods().First(x => x.Name == nameof(ModelBuilder.Entity) && x.IsGenericMethod) .MakeGenericMethod(modelType); // Call the Entity<> method var typeBuilder = entityMethod.Invoke(modelBuilder, null); // Get the HasKey method var hasKeyMethod = typeBuilder.GetType().GetMethods() .First(x => x.Name == nameof(EntityTypeBuilder <object> .HasKey) && x.GetParameters().Any(y => y.ParameterType == typeof(string[]))); // Call the HasKey method var keyBuilder = hasKeyMethod.Invoke(typeBuilder, new object[] { new string[] { primaryKeyColumnName } }); // For every join where the current DbSet property represents the principle table... foreach (var join in QueryMap.Joins.Where(x => x.Table.TableName == table.TableName)) { // We need to recreate the following Fluent API command using reflection // modelBuilder.Entity<TModel> // .HasMany<TRelatedModel>(navigationName) // .WithOne<TRelatedModel>(navigationName) // .HasForeignKey<TModel, TRelatedModel>(new string[]{ foreignKeyColumnName }) // .HasPrincipleKey<TModel, TRelatedModel>(new string[]{ principleKeyColumnName }) // .OnDelete<TModel, TRelatedModel>(DeleteBehavior.Cascade); // Get the related DbSet property var relatedDbSetProperty = GetType().GetProperty(join.ReferencedTable.TableName); // Get the related model type var relatedModelType = relatedDbSetProperty.PropertyType.GetGenericArguments()[0]; // Get the HasMany method var hasManyMethod = typeBuilder.GetType().GetMethods().First(x => x.Name == nameof(EntityTypeBuilder <object> .HasMany) && x.IsGenericMethod && x.GetParameters()[0].ParameterType == typeof(string)); // Call the HasMany method var collectionNavigationBuilder = hasManyMethod.MakeGenericMethod(relatedModelType).Invoke(typeBuilder, new object[] { CeidDiplomatikiHelpers.GetPluralForm(join.ReferencedTable.TableName) }); // Get the WithOne method var withOneMethod = collectionNavigationBuilder.GetType().GetMethods().First(x => x.Name == nameof(CollectionNavigationBuilder <object, object> .WithOne) && x.GetParameters()[0].ParameterType == typeof(string)); // Call the WithOne method var referenceCollectionBuilder = withOneMethod.Invoke(collectionNavigationBuilder, new object[] { join.Table.TableName }); // Get The HasForeignKey method var hasForeignKeyMethod = referenceCollectionBuilder.GetType().GetMethods().First(x => x.Name == nameof(ReferenceCollectionBuilder <object, object> .HasForeignKey) && x.GetParameters()[0].ParameterType == typeof(string[])); // Call the HasForeignKey method hasForeignKeyMethod.Invoke(referenceCollectionBuilder, new object[] { new string[] { join.ForeignKeyColumn.ColumnName } }); // Get the HasPrincipleKey method var hasPrincipleKeyMethod = referenceCollectionBuilder.GetType().GetMethods().First(x => x.Name == nameof(ReferenceCollectionBuilder <object, object> .HasPrincipalKey) && x.GetParameters()[0].ParameterType == typeof(string[])); // Call the HasPrincipleKey method hasPrincipleKeyMethod.Invoke(referenceCollectionBuilder, new object[] { new string[] { join.PrincipleKeyColumn.ColumnName } }); // Get the OnDelete method var onDeleteMethod = referenceCollectionBuilder.GetType().GetMethods().First(x => x.Name == nameof(ReferenceCollectionBuilder <object, object> .OnDelete)); // Call the OnDelete method // TODO: We should get the constraint condition from the analyzer and then user it to configure the delete behavior. onDeleteMethod.Invoke(referenceCollectionBuilder, new object[] { DeleteBehavior.Cascade }); } } }
/// <summary> /// Gets the data using the specified <paramref name="args"/>. /// NOTE: Only root types are returned by this method! /// </summary> /// <param name="args">The args</param> /// <returns></returns> public override async Task <IFailable <IEnumerable> > GetDataAsync(DataGridPresenterArgs args) { // Create the result var result = new Failable <IEnumerable>(); try { var queryable = GetDataQuery; // Get the data model type var dataModelType = PresenterMap.QueryMap.RootType; // If there is a date column... if (DataGridMap.DateColumn != null) { // If there is an after date... if (args.After != null) { // Add a where condition queryable = CeidDiplomatikiHelpers.AddWhereCondition(queryable, dataModelType, ExpressionHelpers.CreatePropertyEqualityExpression(dataModelType, DataGridMap.DateColumn, args.After.Value.DateTime, NumericEqualityOperator.GreaterOrEqualThan)); } // If there is a before date... if (args.Before != null) { // Add a where condition queryable = CeidDiplomatikiHelpers.AddWhereCondition(queryable, dataModelType, ExpressionHelpers.CreatePropertyEqualityExpression(dataModelType, DataGridMap.DateColumn, args.Before.Value.DateTime, NumericEqualityOperator.LessOrEqualThan)); } } // If there is a search value... if (!args.Search.IsNullOrEmpty()) { // If there are search column... if (!DataGridMap.SearchColumns.IsNullOrEmpty()) { var parameterExp = Expression.Parameter(dataModelType, "x"); var methodCallExpressions = new List <MethodCallExpression>(); // For every search column... foreach (var searchColumn in DataGridMap.SearchColumns) { var expr = CreateContainsExpression(parameterExp, dataModelType, searchColumn, args.Search); methodCallExpressions.Add(expr); } Expression orExpression = null; foreach (var expr in methodCallExpressions) { if (orExpression == null) { orExpression = expr; continue; } orExpression = Expression.OrElse(orExpression, expr); } var lambda = Expression.Lambda(typeof(Func <,>).MakeGenericType(dataModelType, typeof(bool)), orExpression, parameterExp); queryable = CeidDiplomatikiHelpers.AddWhereCondition(queryable, dataModelType, lambda); } } // Add the order by condition queryable = CeidDiplomatikiHelpers.AddOrderByDescendinCondition(queryable, dataModelType, PrimaryKeyProperty); // Add the skip condition queryable = CeidDiplomatikiHelpers.AddSkipCondition(queryable, dataModelType, args.Page * args.PerPage); // Add the take condition queryable = CeidDiplomatikiHelpers.AddTakeCondition(queryable, dataModelType, args.PerPage); // Get the models var models = await CeidDiplomatikiHelpers.ExecuteToListAsync(queryable, dataModelType); // Set the data result.Result = models; } catch (Exception ex) { // If there was an error... result.ErrorMessage = ex.Message; } // Return the result return(result); }