-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
EAVFramework/src/Shared/V2/ManifestService.cs
Line 1345 in 75ddb9c
| //TODO : SQL UP |
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to generate field {entityDefinition.Name}:{attributeDefinition.Name}:{ex.ToString()}", ex);
}
}
}
foreach (var entity in manifest.SelectToken("$.entities").OfType<JProperty>())
{
var table = tables[entity.Name];
table.BuildType();
}
foreach (var table in manifest.SelectToken("$.entities").OfType<JProperty>().Select(t => tables[t.Name]).TSort(d => d.Dependencies))
{
var schema = table.Schema ?? options.Schema ?? "dbo";
var result = this.options.DTOAssembly?.GetTypes().FirstOrDefault(t => t.GetCustomAttribute<EntityDTOAttribute>() is EntityDTOAttribute attr && attr.LogicalName == table.LogicalName && (this.options.SkipValidateSchemaNameForRemoteTypes || string.Equals(attr.Schema, schema, StringComparison.OrdinalIgnoreCase)))?.GetTypeInfo();
try
{
if (result != null)
{
this.options.EntityDTOs[table.CollectionSchemaName] = result;
}
else
{
// table.FinalizeType();
this.options.EntityDTOs[table.CollectionSchemaName] = table.CreateTypeInfo();
}
this.options.EntityDTOConfigurations[table.CollectionSchemaName] = table.CreateConfigurationTypeInfo();
}
catch (Exception)
{
throw;
}
}
foreach (var entity in manifest.SelectToken("$.entities").OfType<JProperty>())
{
var table = tables[entity.Name];
if (entity.Name == "Identity")
{
}
// var a = table.Builder.CreateTypeInfo();
}
}
return (CreateDynamicMigration(dynamicCodeService, manifest),
tables.Values.TSort(d => d.Dependencies).Select(entity => entity.CreateMigrationType(this.options.Namespace, this.options.MigrationName, this.options.PartOfMigration)).Select(entity => Activator.CreateInstance(entity) as IDynamicTable).ToArray());
}
private Dictionary<string, Stopwatch> Measurements { get; set; } = new Dictionary<string, Stopwatch>();
public void StartMeasurement(string name)
{
if (!Measurements.ContainsKey(name))
Measurements[name] = new Stopwatch();
Measurements[name].Start();
}
public void StopMeasurement(string name)
{
if (!Measurements.ContainsKey(name))
return;
Measurements[name].Stop();
}
public (Type migrationType, IDynamicTable[] tables) BuildDynamicModel(DynamicCodeService dynamicCodeService, MigrationDefinition migration)
{
var builder = dynamicCodeService.CreateAssemblyBuilder(this.options.ModuleName, this.options.Namespace);
var options = dynamicCodeService.Options;
// var tables = new Dictionary<string, DynamicTableBuilder>();
//foreach (var (entityKey, entity) in migration.GetNewEntities())
//{
// var schema = entity.Schema ?? options.Schema ?? "dbo";
// StartMeasurement("ResolveDTOTypes");
// //We will find a DTO generated entity type for this entity if defined.
// var result = this.options.DTOAssembly
// ?.GetTypes()
// .FirstOrDefault(t => t.GetCustomAttribute<EntityDTOAttribute>() is EntityDTOAttribute attr && attr.LogicalName == entity.LogicalName && (this.options.SkipValidateSchemaNameForRemoteTypes || string.Equals(attr.Schema, schema, StringComparison.OrdinalIgnoreCase)))?.GetTypeInfo();
// StopMeasurement("ResolveDTOTypes");
// StartMeasurement("InitializeTables");
// var table = builder.WithTable(entityKey,
// tableSchemaname: entity.SchemaName,
// tableLogicalName: entity.LogicalName,
// tableCollectionSchemaName: entity.CollectionSchemaName,
// entity.Schema ?? options.Schema,
// entity.Abstract ?? false,
// entity.MappingStrategy
// ).External(entity.External ?? false, result);
// tables.Add(entityKey, table);
// StopMeasurement("InitializeTables");
//}
//foreach (var (entityKey, entity) in migration.GetNewEntities())
//{
// var table = tables[entityKey];
// var parentName = entityDefinition.Value.SelectToken("$.TPT")?.ToString() ?? entityDefinition.Value.SelectToken("$.TPC")?.ToString();
// if (!string.IsNullOrEmpty(parentName))
// {
// table.WithBaseEntity(tables[parentName]);
// }
//}
var migrationType = CreateDynamicMigration(dynamicCodeService, migration);
return (migrationType,
CreateMigrationTables(migration, dynamicCodeService, builder));
// tables.Values.TSort(d => d.Dependencies).Select(entity => entity.CreateMigrationType(this.options.Namespace, this.options.MigrationName, this.options.PartOfMigration)).Select(entity => Activator.CreateInstance(entity) as IDynamicTable).ToArray());
}
public MethodInfo[] GetPrimaryKeys(EntityDefinition entity, Type columnsCLRType)
{
var properties = entity.Attributes.Values.OfType<AttributeObjectDefinition>();
var primaryKeys = properties.Where(p => p.IsPrimaryKey ?? false)
.Where(p => columnsCLRType.GetProperty(p.SchemaName) != null) // members.ContainsKey(p.LogicalName))
.Select(p => columnsCLRType.GetProperty(p.SchemaName).GetMethod)
.ToArray();
return primaryKeys;
}
public bool IsAttributeLookup(AttributeObjectDefinition attribute)
{
return string.Equals(attribute.AttributeType.Type, "lookup", StringComparison.OrdinalIgnoreCase);
}
public string[] GetForeignKeys(Dictionary<string, EntityDefinition> entities, EntityDefinition entity)
{
var properties = entity.GetAllProperties(entities).Values.OfType<AttributeObjectDefinition>(); ; // entity.Attributes.Values.OfType<AttributeObjectDefinition>();
var fKeys = properties.Where(IsAttributeLookup) // entityDefinition.Value.SelectToken("$.attributes").OfType<JProperty>()
// .Where(attribute => attribute.Value.SelectToken("$.type.type")?.ToString() == "lookup")
// .Where(attribute => members.ContainsKey(attribute.Value.SelectToken("$.logicalName")?.ToString()))
.Select(attribute => $"FK_{entity.CollectionSchemaName}_{entities[attribute.AttributeType.ReferenceType].CollectionSchemaName}_{attribute.SchemaName}".Replace(" ", ""))
.OrderBy(n => n)
.ToArray();
return fKeys;
}
public ForeignKeyModel[] GetForeignKeys(Dictionary<string, EntityDefinition> entities, EntityDefinition entity, Type columnsCLRType, CascadeAction referentialActionNoAction)
{
var properties = entity.Attributes.Values.OfType<AttributeObjectDefinition>();
var fKeys = properties.Where(IsAttributeLookup) // entityDefinition.Value.SelectToken("$.attributes").OfType<JProperty>()
// .Where(attribute => attribute.Value.SelectToken("$.type.type")?.ToString() == "lookup")
// .Where(attribute => members.ContainsKey(attribute.Value.SelectToken("$.logicalName")?.ToString()))
.Where(attribute => columnsCLRType.GetProperty(attribute.SchemaName) != null)
.Where(attribute => entities[attribute.AttributeType.ReferenceType].GetMappingStrategy(entities) == MappingStrategy.TPT)
.Select(attribute => new ForeignKeyModel
{
Name = $"FK_{entity.CollectionSchemaName}_{entities[attribute.AttributeType.ReferenceType].CollectionSchemaName}_{attribute.SchemaName}".Replace(" ", ""),
AttributeSchemaName = attribute.SchemaName, //attribute.Value.SelectToken("$.schemaName").ToString(),
PropertyGetMethod = columnsCLRType.GetProperty(attribute.SchemaName).GetMethod,
ReferenceType = entities[attribute.AttributeType.ReferenceType],
OnDeleteCascade = (ReferentialAction) (attribute.AttributeType?.Cascades?.OnDelete ?? referentialActionNoAction),
OnUpdateCascade = (ReferentialAction) (attribute.AttributeType?.Cascades?.OnUpdate ?? referentialActionNoAction),
// ForeignKey = attribute.ForeignKey
}).OrderBy(n => n.Name)
.ToArray();
return fKeys;
}
public class ForeignKeyModel
{
public string Name { get; set; }
public string AttributeSchemaName { get; set; }
public MethodInfo PropertyGetMethod { get; set; }
public EntityDefinition ReferenceType { get; set; }
public ReferentialAction OnDeleteCascade { get; set; }
public ReferentialAction OnUpdateCascade { get; set; }
}
private Dictionary<PropertyInfo, object> BuildParametersForcolumn(ILGenerator entityCtorBuilderIL, AttributeObjectDefinition propertyInfo, MethodInfo method, string tableName = null, string schema = null)
{
// The following parameters can be set from the typeobject
// Column<T>([CanBeNullAttribute] string type = null, bool? unicode = null, int? maxLength = null, bool rowVersion = false, [CanBeNullAttribute] string name = null, bool nullable = false, [CanBeNullAttribute] object defaultValue = null, [CanBeNullAttribute] string defaultValueSql = null, [CanBeNullAttribute] string computedColumnSql = null, bool? fixedLength = null, [CanBeNullAttribute] string comment = null, [CanBeNullAttribute] string collation = null, int? precision = null, int? scale = null, bool? stored = null)
// type:
// The database type of the column.
//
// unicode:
// Indicates whether or not the column will store Unicode data.
//
// maxLength:
// The maximum length for data in the column.
//
// rowVersion:
// Indicates whether or not the column will act as a rowversion/timestamp concurrency
// token.
//
// name:
// The column name.
//
// nullable:
// Indicates whether or not the column can store null values.
//
// defaultValue:
// The default value for the column.
//
// defaultValueSql:
// The SQL expression to use for the column's default constraint.
//
// computedColumnSql:
// The SQL expression to use to compute the column value.
//
// fixedLength:
// Indicates whether or not the column is constrained to fixed-length data.
//
// comment:
// A comment to be applied to the column.
//
// collation:
// A collation to be applied to the column.
//
// precision:
// The maximum number of digits for data in the column.
//
// scale:
// The maximum number of decimal places for data in the column.
//
// stored:
// Whether the value of the computed column is stored in the database or not.
//
// Type parameters:
// T:
// The CLR type of the column.
//
//
// Summary:
// Builds an Microsoft.EntityFrameworkCore.Migrations.Operations.AddColumnOperation
// to add a new column to a table.
//
// Parameters:
// name:
// The column name.
//
// table:
// The name of the table that contains the column.
//
// type:
// The store/database type of the column.
//
// unicode:
// Indicates whether or not the column can contain Unicode data, or null if not
// specified or not applicable.
//
// maxLength:
// The maximum length of data that can be stored in the column, or null if not specified
// or not applicable.
//
// rowVersion:
// Indicates whether or not the column acts as an automatic concurrency token, such
// as a rowversion/timestamp column in SQL Server.
//
// schema:
// The schema that contains the table, or null if the default schema should be used.
//
// nullable:
// Indicates whether or not the column can store null values.
//
// defaultValue:
// The default value for the column.
//
// defaultValueSql:
// The SQL expression to use for the column's default constraint.
//
// computedColumnSql:
// The SQL expression to use to compute the column value.
//
// fixedLength:
// Indicates whether or not the column is constrained to fixed-length data.
//
// comment:
// A comment to associate with the column.
//
// collation:
// A collation to apply to the column.
//
// precision:
// The maximum number of digits that is allowed in this column, or null if not specified
// or not applicable.
//
// scale:
// The maximum number of decimal places that is allowed in this column, or null
// if not specified or not applicable.
//
// stored:
// Whether the value of the computed column is stored in the database or not.
//
// Type parameters:
// T:
// The CLR type that the column is mapped to.
//
// Returns:
// A builder to allow annotations to be added to the operation.
var options = dynamicCodeService.Options;
var parameters = new Dictionary<PropertyInfo, object>();
var locals = new Dictionary<Type, LocalBuilder>
{
[typeof(bool?)] = entityCtorBuilderIL.DeclareLocal(typeof(bool?)),
[typeof(int?)] = entityCtorBuilderIL.DeclareLocal(typeof(int?)),
};
foreach (var arg1 in method.GetParameters())
{
var argName = arg1.Name;
if (argName == "name")
argName = "columnName";
switch (argName)
{
case "comment" when !string.IsNullOrEmpty(propertyInfo.Description):
dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldstr, propertyInfo.Description), arg1);
continue;
}
//var value = propertyInfo.GetColumnParam(argName);
if (propertyInfo.AttributeType.SqlOptions?.TryGetValue(argName, out var value) ?? false)
{
switch (value.ValueKind)
{
case System.Text.Json.JsonValueKind.String: // string stringvalue:
dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldstr, value.GetString()), arg1);
break;
case System.Text.Json.JsonValueKind.Number:
dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldc_I4, value.GetInt32()), arg1);
break;
case JsonValueKind.True:
case JsonValueKind.False:
dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(value.GetBoolean() ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0), arg1);
break;
default:
if (Nullable.GetUnderlyingType(arg1.ParameterType) != null)
{
entityCtorBuilderIL.Emit(OpCodes.Ldloca_S, locals[arg1.ParameterType].LocalIndex);
entityCtorBuilderIL.Emit(OpCodes.Initobj, arg1.ParameterType);
entityCtorBuilderIL.Emit(OpCodes.Ldloc, locals[arg1.ParameterType]);
}
else
{
entityCtorBuilderIL.Emit(OpCodes.Ldnull);
}
break;
}
}
else
{
// var hasMaxLength = propertyInfo.MaxLength.HasValue;
switch (argName)
{
case "maxLength" when propertyInfo.AttributeType.MaxLength.HasValue:
dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldc_I4, propertyInfo.AttributeType.MaxLength.Value), arg1);
AddParameterComparison(parameters, argName, propertyInfo.AttributeType.MaxLength.Value);
break;
case "table" when !string.IsNullOrEmpty(tableName): entityCtorBuilderIL.Emit(OpCodes.Ldstr, tableName); break;
case "schema" when !string.IsNullOrEmpty(schema): dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldstr, schema), arg1); break;
case "columnName": entityCtorBuilderIL.Emit(OpCodes.Ldstr, propertyInfo.SchemaName); break;
case "nullable" when (propertyInfo.IsPrimaryKey ?? false):
case "nullable" when options.RequiredSupport && ((propertyInfo.AttributeType.Required ?? false) || (propertyInfo.IsRequired ?? false)):
case "nullable" when (propertyInfo.IsRowVersion):
dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldc_I4_0), arg1);
break;
case "nullable":
entityCtorBuilderIL.Emit(OpCodes.Ldc_I4_1);
break;
case "type" when string.Equals(propertyInfo.AttributeType.Type, "multilinetext", StringComparison.OrdinalIgnoreCase):
dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldstr, "nvarchar(max)"), arg1);
break;
case "type" when string.Equals(propertyInfo.AttributeType.Type, "text", StringComparison.OrdinalIgnoreCase) && !propertyInfo.AttributeType.MaxLength.HasValue:
case "type" when string.Equals(propertyInfo.AttributeType.Type, "string", StringComparison.OrdinalIgnoreCase) && !propertyInfo.AttributeType.MaxLength.HasValue:
dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldstr, $"nvarchar({((propertyInfo.IsPrimaryField) ? 255 : 100)})"), arg1);
break;
case "rowVersion" when propertyInfo.IsRowVersion:
entityCtorBuilderIL.Emit(OpCodes.Ldc_I4_1);
break;
default:
if (Nullable.GetUnderlyingType(arg1.ParameterType) != null)
{
entityCtorBuilderIL.Emit(OpCodes.Ldloca_S, locals[arg1.ParameterType].LocalIndex);
entityCtorBuilderIL.Emit(OpCodes.Initobj, arg1.ParameterType);
entityCtorBuilderIL.Emit(OpCodes.Ldloc, locals[arg1.ParameterType]);
}
else if (arg1.ParameterType == typeof(bool))
{
entityCtorBuilderIL.Emit(Convert.ToBoolean(arg1.DefaultValue) == true ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
}
else
{
entityCtorBuilderIL.Emit(OpCodes.Ldnull);
}
break;
}
}
//
//new ColumnsBuilder(null).Column<Guid>(
// type:"",unicode:false,maxLength:0,rowVersion:false,name:"a",nullable:false,defaultValue:null,defaultValueSql:null,
// computedColumnSql:null, fixedLength:false,comment:"",collation:"",precision:0,scale:0,stored:false);
//type:
}
return parameters;
void AddParameterComparison<T>(Dictionary<PropertyInfo, object> _parameters, string argName, T value)
{
var prop = typeof(EntityMigrationColumnsAttribute).GetProperties().FirstOrDefault(x => string.Equals(x.Name, argName, StringComparison.OrdinalIgnoreCase));
if (prop != null)
_parameters[prop] = value;
}
}
public void EmitAddColumn(ILGenerator il, string table, string schema, AttributeObjectDefinition attributeDefinition)
{
var clrType = dynamicCodeService.TypeMapper.GetCLRType(attributeDefinition.AttributeType.Type);
var method = dynamicCodeService.Options.MigrationsBuilderAddColumn.MakeGenericMethod(clrType);
il.Emit(OpCodes.Ldarg_1); //first argument
//MigrationsBuilderAddColumn
BuildParametersForcolumn(il, attributeDefinition, method, table, schema);
il.Emit(OpCodes.Callvirt, method);
il.Emit(OpCodes.Pop);
}
public void AddForeignKey(string EntityCollectionSchemaName, string schema, ILGenerator UpMethodIL,
AttributeObjectDefinition field, EntityDefinition referenceType)
{
//
// Summary:
// Builds an Microsoft.EntityFrameworkCore.Migrations.Operations.AddForeignKeyOperation
// to add a new foreign key to a table.
//
// Parameters:
// name:
// The foreign key constraint name.
//
// table:
// The table that contains the foreign key.
//
// column:
// The column that is constrained.
//
// principalTable:
// The table to which the foreign key is constrained.
//
// schema:
// The schema that contains the table, or null if the default schema should be used.
//
// principalSchema:
// The schema that contains principal table, or null if the default schema should
// be used.
//
// principalColumn:
// The column to which the foreign key column is constrained, or null to constrain
// to the primary key column.
//
// onUpdate:
// The action to take on updates.
//
// onDelete:
// The action to take on deletes.
//
// Returns:
// A builder to allow annotations to be added to the operation.
UpMethodIL.Emit(OpCodes.Ldarg_1);
// var entityName = attributeDefinition.Value.SelectToken("$.type.referenceType");
var principalSchema = referenceType.Schema ?? dynamicCodeService.Options.Schema ?? "dbo"; // dynamicPropertyBuilder.ReferenceType.Schema; // manifest.SelectToken($"$.entities['{entityName}'].schema")?.ToString() ?? options.Schema ?? "dbo";
var principalTable = referenceType.CollectionSchemaName;// manifest.SelectToken($"$.entities['{entityName}'].pluralName").ToString().Replace(" ", "");
var principalColumn = referenceType.Attributes.Values.OfType<AttributeObjectDefinition>().Single(p => p.IsPrimaryKey ?? false).SchemaName; // manifest.SelectToken($"$.entities['{entityName}'].attributes").OfType<JProperty>()
// .Single(a => a.Value.SelectToken("$.isPrimaryKey")?.ToObject<bool>() ?? false).Name.Replace(" ", "");
var onDeleteCascade = field.AttributeType?.Cascades?.OnDelete ?? dynamicCodeService.Options.ReferentialActionNoAction;
var onUpdateCascade = field.AttributeType?.Cascades?.OnUpdate ?? dynamicCodeService.Options.ReferentialActionNoAction;
foreach (var arg1 in dynamicCodeService.Options.MigrationsBuilderAddForeignKey.GetParameters())
{
var argName = arg1.Name.ToLower();
switch (argName)
{
case "table" when !string.IsNullOrEmpty(EntityCollectionSchemaName): UpMethodIL.Emit(OpCodes.Ldstr, EntityCollectionSchemaName); break;
case "schema" when !string.IsNullOrEmpty(schema): UpMethodIL.Emit(OpCodes.Ldstr, schema); break;
case "name": UpMethodIL.Emit(OpCodes.Ldstr, $"FK_{EntityCollectionSchemaName}_{referenceType.CollectionSchemaName}_{field.SchemaName}".Replace(" ", "")); break;
case "column": UpMethodIL.Emit(OpCodes.Ldstr, field.SchemaName); break;
case "principalschema": UpMethodIL.Emit(OpCodes.Ldstr, principalSchema); break;
case "principaltable": UpMethodIL.Emit(OpCodes.Ldstr, principalTable); break;
case "principalcolumn": UpMethodIL.Emit(OpCodes.Ldstr, principalColumn); break;
case "onupdate": UpMethodIL.Emit(OpCodes.Ldc_I4, (int) onUpdateCascade); break;
case "ondelete": UpMethodIL.Emit(OpCodes.Ldc_I4, (int) onDeleteCascade); break;
default:
UpMethodIL.Emit(OpCodes.Ldnull);
break;
}
}
UpMethodIL.Emit(OpCodes.Callvirt, dynamicCodeService.Options.MigrationsBuilderAddForeignKey);
UpMethodIL.Emit(OpCodes.Pop);
}
class TableResult
{
public HashSet<string> Dependencies { get; set; } = new HashSet<string>();
public string Key { get; set; }
public Type Type { get; set; }
}
internal IDynamicTable[] CreateMigrationTables(MigrationDefinition migration, DynamicCodeService dynamicCodeService, DynamicAssemblyBuilder builder)
{
var results = new Dictionary<string, TableResult>();
var module = builder.Module;
var migrationName = options.MigrationName.Replace(".", "_");
foreach (var pair in migration.Entities)
{
var result = new TableResult
{
Key = pair.Key
};
results.Add(pair.Key, result);
var entityKey = pair.Key;
var entity = pair.Value;
var entityMigration = migration.GetEntityMigration(entityKey);
foreach (var field in GetFields(entity).Where(v => IsAttributeLookup(v.Value)))
{
result.Dependencies.Add(field.Value.AttributeType.ReferenceType);
}
var schema = entity.Schema ?? dynamicCodeService.Options.Schema ?? "dbo";
var entityTypeBuilder = module.DefineType($"{builder.Namespace}.{entity.CollectionSchemaName}Builder_{migrationName}", TypeAttributes.Public);
CustomAttributeBuilder EntityAttributeBuilder = new CustomAttributeBuilder(typeof(EntityAttribute).GetConstructor(new Type[] { }), new object[] { }, new[] { typeof(EntityAttribute).GetProperty(nameof(EntityAttribute.LogicalName)) }, new[] { entity.LogicalName });
entityTypeBuilder.SetCustomAttribute(EntityAttributeBuilder);
entityTypeBuilder.AddInterfaceImplementation(dynamicCodeService.Options.DynamicTableType);
if (entity.External ?? false)
{
var UpMethod = entityTypeBuilder.DefineMethod("Up", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, null, new[] { dynamicCodeService.Options.MigrationBuilderCreateTable.DeclaringType });
var UpMethodIL = UpMethod.GetILGenerator();
UpMethodIL.Emit(OpCodes.Ret);
var DownMethod = entityTypeBuilder.DefineMethod("Down", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, null, new[] { dynamicCodeService.Options.MigrationBuilderDropTable.DeclaringType });
var DownMethodIL = DownMethod.GetILGenerator();
DownMethodIL.Emit(OpCodes.Ret);
result.Type = entityTypeBuilder.CreateTypeInfo();
continue;
}
{
var UpMethod = entityTypeBuilder.DefineMethod("Up", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, null, new[] { dynamicCodeService.Options.MigrationBuilderCreateTable.DeclaringType });
var migrationBuilder = new MigrationBuilderBuilder(builder, UpMethod, dynamicCodeService, dynamicCodeService.Options);
if (migration.IsTableNew(entityKey))
{
var (columnsCLRType, columnsctor, members) = CreateColumnsType(
builder, entity.SchemaName, entity.LogicalName, migrationName, true,
entity.GetProperties(migration.Entities).Values.OfType<AttributeObjectDefinition>().ToList());
var columsMethod = entityTypeBuilder.DefineMethod("Columns", MethodAttributes.Public, columnsCLRType, new[] { dynamicCodeService.Options.ColumnsBuilderType });
var columsMethodIL = columsMethod.GetILGenerator();
columsMethodIL.Emit(OpCodes.Ldarg_1);
columsMethodIL.Emit(OpCodes.Newobj, columnsctor);
columsMethodIL.Emit(OpCodes.Ret);
var ConstraintsMethod = entityTypeBuilder.DefineMethod("Constraints",
MethodAttributes.Public, null, new[] { dynamicCodeService.Options.CreateTableBuilderType.MakeGenericType(columnsCLRType) });
var ConstraintsMethodIL = ConstraintsMethod.GetILGenerator();
dynamicCodeService.EmitPropertyService.CreateTableImpl(entity.CollectionSchemaName, schema, columnsCLRType, columsMethod, ConstraintsMethod, migrationBuilder.UpMethodIL);
dynamicCodeService.LookupPropertyBuilder.CreateLookupIndexes(migrationBuilder.UpMethodIL, entity, dynamicCodeService.Options.Schema ?? "dbo");
var primaryKeys = GetPrimaryKeys(entity, columnsCLRType);
var foreignKeys = GetForeignKeys(migration.Entities, entity, columnsCLRType,
dynamicCodeService.Options.ReferentialActionNoAction);
if (primaryKeys.Any() || foreignKeys.Any())
{
ConstraintsMethodIL.DeclareLocal(typeof(ParameterExpression));
}
HandlePrimaryKeys(dynamicCodeService, builder, entity, columnsCLRType, ConstraintsMethodIL, primaryKeys);
HandleForeignKeys(dynamicCodeService, builder, columnsCLRType, ConstraintsMethodIL, foreignKeys);
ConstraintsMethodIL.Emit(OpCodes.Ret);
}
else
{
var migrationStrategy = entityMigration.MappingStrategyChange();
foreach (var newField in entityMigration.GetNewAttributes().OfType<AttributeObjectDefinition>())
{
var required = (newField.IsRequired ?? false) || (newField.AttributeType.Required ?? false);
//We cant add a required column to existing table, rely on it being altered after data is set.
//this is a case when we are changing from TPT to TPC
newField.IsRequired = newField.AttributeType.Required = false;
EmitAddColumn(migrationBuilder.UpMethodIL, entity.CollectionSchemaName, schema, newField);
newField.IsRequired = newField.AttributeType.Required = required;
if (IsFieldLookup(newField))
{
var refrenceType = migration.Entities[newField.AttributeType.ReferenceType];
if (refrenceType.GetMappingStrategy(migration.Entities) == MappingStrategy.TPT)
AddForeignKey(entity.CollectionSchemaName, schema, migrationBuilder.UpMethodIL, newField,
refrenceType);
if (newField.AttributeType.IndexInfo != null)
dynamicCodeService.LookupPropertyBuilder.CreateLoopupIndex(migrationBuilder.UpMethodIL, entity.CollectionSchemaName, entity.Schema ?? dynamicCodeService.Options.Schema ?? "dbo",
newField.SchemaName, newField.AttributeType.IndexInfo);
}
}
if (migrationStrategy == MappingStrategyChangeEnum.TPT2TPC && !(entity.Abstract ?? false))
{
var columnsToMove = entityMigration.GetAttributesMovingFromBase()
.OfType<AttributeObjectDefinition>()
.Where(c => !c.IsRowVersion)
.ToArray();
var columnsToMoveSql = string.Join("\n",
columnsToMove
.Select(attr => $"\t[{schema}].[{entity.CollectionSchemaName}].[{attr.SchemaName}] = BaseRecords.[{attr.SchemaName}],"));
var upSql1 = $"""
UPDATE
[{schema}].[{entity.CollectionSchemaName}]
SET
{columnsToMoveSql.Trim(',')}
FROM
[{schema}].[{entity.CollectionSchemaName}] Records
INNER JOIN
[{schema}].[{entity.GetBaseEntity(migration.Source.Entities).CollectionSchemaName}] BaseRecords
ON
records.Id = BaseRecords.Id;
""";
EmitSQLUp(migrationBuilder.UpMethodIL, upSql1);
foreach (var existingField in columnsToMove)
{
if (IsFieldRequired(existingField))
EmitAlterColumn(migrationBuilder.UpMethodIL, entity.CollectionSchemaName, schema, existingField);
}
}
foreach (var existingField in entityMigration.GetExistingFields())
{
if (existingField.HasChanged())
{
EmitAlterColumn(migrationBuilder.UpMethodIL, entity.CollectionSchemaName, schema, existingField.Target);
}
if (IsFieldLookup(existingField.Target) && existingField.HasCascadeChanges())
{
var referenceType = migration.Entities[existingField.Target.AttributeType.ReferenceType];
migrationBuilder.DropForeignKey(entity.CollectionSchemaName, schema,
$"FK_{entity.CollectionSchemaName}_{referenceType.CollectionSchemaName}_{existingField.Target.SchemaName}".Replace(" ", ""));
AddForeignKey(entity.CollectionSchemaName, schema, migrationBuilder.UpMethodIL, existingField.Target,
referenceType);
}
{
if (IsFieldLookup(existingField.Target)
&& !string.IsNullOrEmpty(existingField.Target.AttributeType.ReferenceType) && migration.Entities[existingField.Target.AttributeType.ReferenceType] is EntityDefinition referenceType
&& (referenceType.Abstract ?? false)
&& migration.GetEntityMigration(existingField.Target.AttributeType.ReferenceType).MappingStrategyChange() == MappingStrategyChangeEnum.TPT2TPC)
{
migrationBuilder.DropForeignKey(entity.CollectionSchemaName, schema,
$"FK_{entity.CollectionSchemaName}_{referenceType.CollectionSchemaName}_{existingField.Target.SchemaName}".Replace(" ", ""));
}
}
}
}
// var fields = entity.GetAllProperties(migration.Entities).Values.OfType<AttributeObjectDefinition>().ToArray();
foreach (var key in entityMigration.GetNewKeys())
{
var props = key.Value;
var name = key.Key;
var colums = props.Select(p => entity.GetField(p, migration.Entities).SchemaName).ToArray();
migrationBuilder.CreateIndex(entity.CollectionSchemaName, entity.Schema ?? dynamicCodeService.Options.Schema ?? "dbo",
name, true, colums);
}
migrationBuilder.UpMethodIL.Emit(OpCodes.Ret);
var DownMethod = entityTypeBuilder.DefineMethod("Down", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, null, new[] { dynamicCodeService.Options.MigrationBuilderDropTable.DeclaringType });
var DownMethodIL = DownMethod.GetILGenerator();
DownMethodIL.Emit(OpCodes.Ldarg_1); //first argument
DownMethodIL.Emit(OpCodes.Ldstr, entity.CollectionSchemaName); //Constant
DownMethodIL.Emit(OpCodes.Ldstr, entity.Schema ?? dynamicCodeService.Options.Schema ?? "dbo");
DownMethodIL.Emit(OpCodes.Callvirt, dynamicCodeService.Options.MigrationBuilderDropTable);
DownMethodIL.Emit(OpCodes.Pop);
DownMethodIL.Emit(OpCodes.Ret);
result.Type = entityTypeBuilder.CreateTypeInfo();
}
//TODO : SQL UP
}
return results.Keys.TSort(x => results[x].Dependencies)
.Select(entity => Activator.CreateInstance(results[entity].Type) as IDynamicTable).ToArray();
}
private bool IsFieldRequired(AttributeObjectDefinition source)
{
return source.IsRequired ?? source.AttributeType.Required ?? false;
}
public bool IsFieldLookup(AttributeObjectDefinition source)
{
return string.Equals(source.AttributeType.Type, "lookup", StringComparison.OrdinalIgnoreCase)
|| string.Equals(source.AttributeType.Type, "polylookup", StringComparison.OrdinalIgnoreCase);
}
public void EmitAlterColumn(ILGenerator UpMethodIL, string table, string schema, AttributeObjectDefinition attributeDefinition)
{
var clrType = dynamicCodeService.TypeMapper.GetCLRType(attributeDefinition.AttributeType.Type);
var method = dynamicCodeService.Options.MigrationsBuilderAlterColumn.MakeGenericMethod(clrType);
UpMethodIL.Emit(OpCodes.Ldarg_1); //first argument
//MigrationsBuilderAddColumn
BuildParametersForcolumn(UpMethodIL, attributeDefinition, method, table, schema);
UpMethodIL.Emit(OpCodes.Callvirt, method);
UpMethodIL.Emit(OpCodes.Pop);
}
private void EmitSQLUp(ILGenerator UpMethodIL, string upSql1)
{
UpMethodIL.Emit(OpCodes.Ldarg_1); //first argument
UpMethodIL.Emit(OpCodes.Ldstr, upSql1);
UpMethodIL.Emit(OpCodes.Ldc_I4_0);
UpMethodIL.Emit(OpCodes.Callvirt, dynamicCodeService.Options.MigrationBuilderSQL);
UpMethodIL.Emit(OpCodes.Pop);
}
private bool IsBaseMember(Dictionary<string, EntityDefinition> entities, EntityDefinition entity, string key, out EntityDefinition parentEntity)
{
parentEntity = null;
var parent = entity.GetParentEntity(entities);
while (parent != null)
{
if (GetFields(parent).Any(p => p.Key == key))
{
parentEntity = parent;
return true;
}
parent = parent.GetParentEntity(entities);
}
return false;
}
public IEnumerable<KeyValuePair<string, AttributeObjectDefinition>> GetFields(EntityDefinition entityDefinition)
{
return entityDefinition.Attributes.OfType<string, AttributeDefinitionBase, AttributeObjectDefinition>()
.OrderByDescending(c => c.Value.IsPrimaryKey).ThenByDescending(c => c.Value.IsPrimaryField).ThenBy(c => c.Value.LogicalName).ToArray();
}
private static void HandleForeignKeys(DynamicCodeService dynamicCodeService, DynamicAssemblyBuilder builder, Type columnsCLRType, ILGenerator ConstraintsMethodIL, ForeignKeyModel[] foreignKeys)
{
if (foreignKeys.Any())
{
/**
*
* TPT for base classe
* constraints: table =>
{
table.PrimaryKey("PK_Identities", x => x.Id);
table.ForeignKey(
name: "FK_Identities_Identities_CreatedById",
column: x => x.CreatedById,
principalTable: "Identities",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Identities_Identities_ModifiedById",
column: x => x.ModifiedById,
principalTable: "Identities",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Identities_Identities_OwnerId",
column: x => x.OwnerId,
principalTable: "Identities",
principalColumn: "Id");
});
*
* TPC for base class
* constraints: table =>
{
table.PrimaryKey("PK_Identity", x => x.Id);
});
*/
foreach (var fk in foreignKeys)
{
/**
* We will skip the FKs if its a TPC and base class. See above comment
*/
if ((fk.ReferenceType.Abstract ?? false) && fk.ReferenceType.MappingStrategy == MappingStrategy.TPC)
{
continue;
}
ConstraintsMethodIL.Emit(OpCodes.Ldarg_1); //first argument
ConstraintsMethodIL.Emit(OpCodes.Ldstr, fk.Name);
dynamicCodeService.EmitPropertyService.WriteLambdaExpression(builder.Module, ConstraintsMethodIL, columnsCLRType, new[] { fk.PropertyGetMethod });// fk.Select(c => c.PropertyGetMethod).ToArray());
var createTableMethod = dynamicCodeService.Options.CreateTableBuilderType.MakeGenericType(columnsCLRType)
.GetMethod(dynamicCodeService.Options.CreateTableBuilderForeignKeyName, BindingFlags.Public | BindingFlags.Instance, null,
new[] {
typeof(string),
typeof(Expression<>).MakeGenericType(
typeof(Func<,>).MakeGenericType(columnsCLRType, typeof(object))),
typeof(string),typeof(string),typeof(string),
dynamicCodeService.Options.ReferentialActionType,dynamicCodeService.Options.ReferentialActionType }, null);
var principalSchema = fk.ReferenceType.Schema ?? dynamicCodeService.Options.Schema ?? "dbo";
var principalTable = fk.ReferenceType.CollectionSchemaName;
var principalColumn = fk.ReferenceType.Attributes.Values.OfType<AttributeObjectDefinition>().SingleOrDefault(p => p.IsPrimaryKey ?? false)?.SchemaName;
if (string.IsNullOrEmpty(principalColumn))
{
throw new InvalidOperationException($"No reference type primary key defined for foreignkey {fk.ReferenceType.SchemaName} on {fk.Name}");
}
ConstraintsMethodIL.Emit(OpCodes.Ldstr, principalTable);
ConstraintsMethodIL.Emit(OpCodes.Ldstr, principalColumn);
ConstraintsMethodIL.Emit(OpCodes.Ldstr, principalSchema);
ConstraintsMethodIL.Emit(OpCodes.Ldc_I4, (int) fk.OnUpdateCascade); //OnUpdate
ConstraintsMethodIL.Emit(OpCodes.Ldc_I4, (int) fk.OnDeleteCascade); //OnDelete
//
//onupdate
//ondelete
ConstraintsMethodIL.Emit(OpCodes.Callvirt, createTableMethod);
ConstraintsMethodIL.Emit(OpCodes.Pop);
}
}
}
private static void HandlePrimaryKeys(DynamicCodeService dynamicCodeService, DynamicAssemblyBuilder builder, EntityDefinition entity, Type columnsCLRType, ILGenerator ConstraintsMethodIL, MethodInfo[] primaryKeys)
{
if (primaryKeys.Any())
{
ConstraintsMethodIL.Emit(OpCodes.Ldarg_1); //first argument
ConstraintsMethodIL.Emit(OpCodes.Ldstr, $"PK_{entity.CollectionSchemaName}"); //PK Name
dynamicCodeService.EmitPropertyService.WriteLambdaExpression(builder.Module, ConstraintsMethodIL, columnsCLRType, primaryKeys.Select(c => columnsCLRType.GetProperty(c.Name.Substring(4)).GetMethod).ToArray());
var createTableMethod = dynamicCodeService.Options.CreateTableBuilderType.MakeGenericType(columnsCLRType).GetMethod(dynamicCodeService.Options.CreateTableBuilderPrimaryKeyName, BindingFlags.Public | BindingFlags.Instance, null,
new[] { typeof(string), typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(columnsCLRType, typeof(object))) }, null);
ConstraintsMethodIL.Emit(OpCodes.Callvirt, createTableMethod);
ConstraintsMethodIL.Emit(OpCodes.Pop);
}
}
public (Type, ConstructorBuilder, Dictionary<string, PropertyBuilder>) CreateColumnsType(DynamicAssemblyBuilder builder, string schemaName, string logicalName,
string migrationName, bool partOfMigration, IReadOnlyCollection<AttributeObjectDefinition> props)
{
var members = new Dictionary<string, PropertyBuilder>();
var columnsType = builder.Module.DefineType($"{builder.Namespace}.{schemaName}Columns_{migrationName.Replace(".", "_")}", TypeAttributes.Public);
CustomAttributeBuilder EntityAttributeBuilder = new CustomAttributeBuilder(typeof(EntityAttribute).GetConstructor(new Type[] { }), new object[] { }, new[] { typeof(EntityAttribute).GetProperty(nameof(EntityAttribute.LogicalName)) }, new[] { logicalName });
columnsType.SetCustomAttribute(EntityAttributeBuilder);
var dfc = columnsType.DefineDefaultConstructor(MethodAttributes.Public);
ConstructorBuilder entityCtorBuilder =
columnsType.DefineConstructor(MethodAttributes.Public,
CallingConventions.Standard, new[] { dynamicCodeService.Options.ColumnsBuilderType });
ILGenerator entityCtorBuilderIL = entityCtorBuilder.GetILGenerator();
entityCtorBuilderIL.Emit(OpCodes.Ldarg_0);
entityCtorBuilderIL.Emit(OpCodes.Call, dfc);
foreach (var propertyInfo in props)
{
var attributeLogicalName = propertyInfo.LogicalName;
var attributeSchemaName = propertyInfo.SchemaName;
var clrType = dynamicCodeService.TypeMapper.GetCLRType(propertyInfo.AttributeType.Type);
if (clrType == null)
continue;
var method = dynamicCodeService.Options.ColumnsBuilderColumnMethod.MakeGenericMethod(clrType);
entityCtorBuilderIL.Emit(OpCodes.Ldarg_0);
entityCtorBuilderIL.Emit(OpCodes.Ldarg_1);
var (attProp, attField) = dynamicCodeService.EmitPropertyService.CreateProperty(columnsType, attributeSchemaName, dynamicCodeService.Options.OperationBuilderAddColumnOptionType); //CreateProperty(entityType, attributeSchemaName, options.OperationBuilderAddColumnOptionType);
var columparams = BuildParametersForcolumn(entityCtorBuilderIL, propertyInfo, method);
entityCtorBuilderIL.Emit(OpCodes.Callvirt, method);
entityCtorBuilderIL.Emit(OpCodes.Call, attProp.SetMethod);
members[attributeLogicalName] = attProp;
}
entityCtorBuilderIL.Emit(OpCodes.Ret);
var entityClrType = columnsType.CreateTypeInfo();
return (entityClrType, entityCtorBuilder, members);
}
}
public static class ManifestQueryExtensions
{
public static EntityDefinition GetBaseEntity(this EntityDefinition entity, Dictionary<string, EntityDefinition> entities)
{
var parent = entity.GetParentEntity(entities);
while (parent != null)
{
entity = parent;
parent = entity.GetParentEntity(entities);
}
return entity;
}
public static EntityDefinition GetParentEntity(this EntityDefinition entity, Dictionary<string, EntityDefinition> entities)
{
var parentName = entity.TPC ?? entity.TPT;
if (!string.IsNullOrEmpty(parentName))
{
return entities[parentName];
}
return null;
}
public static AttributeObjectDefinition GetField(this EntityDefinition entity, string key, Dictionary<string, EntityDefinition> entities)
{
while (entity != null)
{
if (entity.Attributes.ContainsKey(key) && entity.Attributes[key] is AttributeObjectDefinition attr)
return attr;
entity = entity.GetParentEntity(entities);
}
throw new KeyNotFoundException($"Field {key} not found in entity {entity.CollectionSchemaName} or its parent entities.");
}
public static Dictionary<string, AttributeDefinitionBase> GetAllProperties(this EntityDefinition entity, Dictionary<string, EntityDefinition> entities)
{
var properties = new Dictionary<string, AttributeDefinitionBase>(entity.Attributes);
var parent = entity.GetParentEntity(entities);
while (parent != null)
{
foreach (var prop in parent.Attributes)
{
if (!properties.ContainsKey(prop.Key))
properties.Add(prop.Key, prop.Value);
}
parent = parent.GetParentEntity(entities);
}
return properties;
}
public static IEnumerable<AttributeDefinitionBase> GetNewAttributes(this MigrationEntityDefinition migrationEntity)
{
var target = migrationEntity.Target;
var source = migrationEntity.Source;
var targetAttributes = target.GetProperties(migrationEntity.MigrationDefinition.Target.Entities);
var sourceAttributes = source.GetProperties(migrationEntity.MigrationDefinition.Source.Entities);
return targetAttributes.Where(e => !sourceAttributes.ContainsKey(e.Key)).Select(c => c.Value);
}
public static IEnumerable<AttributeDefinitionBase> GetAttributesMovingFromBase(this MigrationEntityDefinition migrationEntity)
{
var target = migrationEntity.Target;
var source = migrationEntity.Source;
var targetAttributes = target.GetProperties(migrationEntity.MigrationDefinition.Target.Entities);
var sourceAttributes = source.GetProperties(migrationEntity.MigrationDefinition.Source.Entities);
return targetAttributes.Where(e => !sourceAttributes.ContainsKey(e.Key) && !target.Attributes.ContainsKey(e.Key)).Select(c => c.Value);
}
public static Dictionary<string, AttributeObjectDefinition> GetProperties(this EntityDefinition entity,
Dictionary<string, EntityDefinition> entities)
{
return (entity.GetMappingStrategy(entities) == MappingStrategy.TPC ?
entity.GetAllProperties(entities) : entity.Attributes)
.OfType<string, AttributeDefinitionBase, AttributeObjectDefinition>()
.OrderByDescending(c => c.Value.IsPrimaryKey)
.ThenByDescending(c => c.Value.IsPrimaryField)
.ThenBy(c => c.Value.LogicalName)
.ToDictionary(k => k.Key, v => v.Value);
}
public static Dictionary<string, string[]> GetNewKeys(this MigrationEntityDefinition migrationEntity)
{
return migrationEntity.Target?.Keys?
.Where(kv => !(migrationEntity.Source?.Keys?.ContainsKey(kv.Key) ?? false))
.ToDictionary(k => k.Key, v => v.Value) ?? new Dictionary<string, string[]>();
}
public static MappingStrategyChangeEnum MappingStrategyChange(this MigrationEntityDefinition migrationEntity)
{
var source = migrationEntity.Source.GetMappingStrategy(migrationEntity.MigrationDefinition.Source.Entities);
var target = migrationEntity.Target.GetMappingStrategy(migrationEntity.MigrationDefinition.Target.Entities);
if (source == target)
{
return MappingStrategyChangeEnum.None;
}
return source switch
{
MappingStrategy.TPT when target == MappingStrategy.TPC => MappingStrategyChangeEnum.TPT2TPC,
MappingStrategy.TPC when target == MappingStrategy.TPT => MappingStrategyChangeEnum.TPC2TPT,
_ => throw new NotImplementedException($"{source} => {target}"),
};
}
public static MappingStrategy GetMappingStrategy(this EntityDefinition entity, Dictionary<string, EntityDefinition> entities)
{
// TPC, TPT, TPH
// TPC = Table Per Concrete Type
// TPT = Table Per Type
// TPH = Table Per Hierarchy
// TPT is the default
// The stategy is stored on the base class, and if not provided
// its indicated based on the navigation properties TPT,TPC on entity
var mappingstrategy = entity.MappingStrategy ?? (!string.IsNullOrEmpty(entity.TPT) ?
MappingStrategy.TPT : !string.IsNullOrEmpty(entity.TPC) ? MappingStrategy.TPC : MappingStrategy.TPT);
var parent = GetParentEntity(entity, entities);
if (parent != null)
{
mappingstrategy = parent.MappingStrategy ?? mappingstrategy;
parent = parent.GetParentEntity(entities);
}
return mappingstrategy;
}
}
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels