From cc4700395cd4acf3c620bb352bb3c620303c8960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Tue, 6 Aug 2024 09:36:31 +0200 Subject: [PATCH] fixes unit tests issues --- sdk/DTO/AttributeDefinition.cs | 23 +++- sdk/DTO/EntityDefinition.cs | 5 + sdk/DTO/ManifestDefinition.cs | 56 +++++++- src/DynamicContext.cs | 2 +- src/Shared/CodeGenerator.cs | 2 +- src/Shared/V2/CodeGenerationOptions.cs | 4 +- src/Shared/V2/ManifestService.cs | 122 ++++++++++++------ test/EAVFramework.UnitTest/EmitTests.cs | 9 +- .../ManifestTests/BaseManifestTests.cs | 1 + 9 files changed, 171 insertions(+), 53 deletions(-) diff --git a/sdk/DTO/AttributeDefinition.cs b/sdk/DTO/AttributeDefinition.cs index d7218bc..020d2e2 100644 --- a/sdk/DTO/AttributeDefinition.cs +++ b/sdk/DTO/AttributeDefinition.cs @@ -78,8 +78,7 @@ public class AttributeObjectDefinition : AttributeDefinitionBase [JsonExtensionData] public Dictionary AdditionalFields { get; set; } - [JsonPropertyName("maxLength")] - public int? MaxLength { get; set; } + [JsonPropertyName("isPrimaryKey")] public bool? IsPrimaryKey { get; set; } @@ -88,9 +87,7 @@ public class AttributeObjectDefinition : AttributeDefinitionBase public bool? IsRequired { get; set; } - [JsonPropertyName("required")] - - public bool? Required { get; set; } + [JsonPropertyName("isRowVersion")] public bool IsRowVersion { get; set; } @@ -146,6 +143,16 @@ public class CascadeOptions [JsonPropertyName("update")] [JsonConverter(typeof(JsonStringEnumConverter))] public CascadeAction? OnUpdate { get; set; } + + public override bool Equals(object obj) + { + if (obj is CascadeOptions cascadeOptions) + { + return OnDelete == cascadeOptions.OnDelete && OnUpdate == cascadeOptions.OnUpdate; + } + + return base.Equals(obj); + } } public enum CascadeAction @@ -201,6 +208,12 @@ public class TypeDefinition [JsonPropertyName("index")] public IndexInfo IndexInfo { get; set; } + [JsonPropertyName("required")] + + public bool? Required { get; set; } + [JsonPropertyName("maxLength")] + public int? MaxLength { get; set; } + /// /// Exclusively used to capture non-spec items /// diff --git a/sdk/DTO/EntityDefinition.cs b/sdk/DTO/EntityDefinition.cs index a0465c7..6128b64 100644 --- a/sdk/DTO/EntityDefinition.cs +++ b/sdk/DTO/EntityDefinition.cs @@ -39,6 +39,11 @@ public class EntityDefinition [JsonProperty("wizards")] public Dictionary Wizards { get; set; } + [JsonPropertyName("keys")] + [JsonProperty("keys")] + public Dictionary Keys { get; set; } + + /// /// Exclusively used to capture non-spec items /// diff --git a/sdk/DTO/ManifestDefinition.cs b/sdk/DTO/ManifestDefinition.cs index 974037b..ba9bc58 100644 --- a/sdk/DTO/ManifestDefinition.cs +++ b/sdk/DTO/ManifestDefinition.cs @@ -40,6 +40,60 @@ public class MigrationAttributeDefinition public AttributeObjectDefinition Target { get; set; } public string Key { get; set; } public MigrationEntityDefinition Entity { get; set; } + + public bool HasCascadeChanges() + { + + if (Source.AttributeType.Cascades == null && Target.AttributeType.Cascades != null) + return true; + if (Source.AttributeType.Cascades != null && Target.AttributeType.Cascades == null) + return true; + if (Source.AttributeType.Cascades != null && !Source.AttributeType.Cascades.Equals(Target.AttributeType.Cascades)) + return true; + + return false; + } + public bool HasChanged() + { + if ((Source.IsRequired??false) != (Target.IsRequired??false)) + return true; + if(Source.AttributeType.Type != Target.AttributeType.Type) + return true; + if (Source.AttributeType.MaxLength != Target.AttributeType.MaxLength) + return true; + + + //if (Source.AttributeType.Cascades == null && Target.AttributeType.Cascades != null) + // return true; + //if (Source.AttributeType.Cascades != null && Target.AttributeType.Cascades == null) + // return true; + //if (Source.AttributeType.Cascades != null && !Source.AttributeType.Cascades.Equals(Target.AttributeType.Cascades)) + // return true; + + + if (Source.AttributeType.SqlOptions == null && Target.AttributeType.SqlOptions != null) + return true; + if (Source.AttributeType.SqlOptions != null && Target.AttributeType.SqlOptions == null) + return true; + + if (Source.AttributeType.SqlOptions != null) + { + if (Source.AttributeType.SqlOptions.Count != Target.AttributeType.SqlOptions.Count) + return true; + if (Source.AttributeType.SqlOptions.Keys.Except(Target.AttributeType.SqlOptions.Keys).Any()) + return true; + if (Target.AttributeType.SqlOptions.Keys.Except(Source.AttributeType.SqlOptions.Keys).Any()) + return true; + if (Target.AttributeType.SqlOptions.Any(kv => Source.AttributeType.SqlOptions[kv.Key].Equals(kv.Value))) + return true; + } + + + + + + return false; + } } public enum MappingStrategyChangeEnum @@ -115,7 +169,7 @@ public MigrationEntityDefinition GetEntityMigration(string entityKey) return new MigrationEntityDefinition { MigrationDefinition = this, - Source = Source.Entities[entityKey], + Source = (Source?.Entities.ContainsKey(entityKey) ??false) ? Source.Entities[entityKey] : null, Target = Target.Entities[entityKey] }; diff --git a/src/DynamicContext.cs b/src/DynamicContext.cs index b2e5a5e..8e2f3b2 100644 --- a/src/DynamicContext.cs +++ b/src/DynamicContext.cs @@ -246,7 +246,7 @@ public MigrationsInfo GetMigrations() foreach (var migration in test.Migrations) { - var name = $"{modelOptions.Value.Schema}_{migration.Target.Version.Replace(".", "_") ?? MigrationDefaultName}"; + var name = $"{modelOptions.Value.Schema}_{migration.Target.Version?.Replace(".", "_") ?? MigrationDefaultName}"; var model = manager.CreateMigration(name, migration, this.modelOptions.Value); diff --git a/src/Shared/CodeGenerator.cs b/src/Shared/CodeGenerator.cs index a9ad443..0196814 100644 --- a/src/Shared/CodeGenerator.cs +++ b/src/Shared/CodeGenerator.cs @@ -2702,7 +2702,7 @@ private string GenerateSourceCode(Type type, bool generatePoco) } else { - sb.AppendLine($"\tpublic{(type.IsAbstract ? " abstract " : " ")}partial class {type.Name}{inherience}\r\n\t{{"); + sb.AppendLine($"\tpublic{(options.GenerateAbstractClasses && type.IsAbstract ? " abstract " : " ")}partial class {type.Name}{inherience}\r\n\t{{"); { foreach (var ctor in type.GetConstructors()) { diff --git a/src/Shared/V2/CodeGenerationOptions.cs b/src/Shared/V2/CodeGenerationOptions.cs index f686db6..98f2ac4 100644 --- a/src/Shared/V2/CodeGenerationOptions.cs +++ b/src/Shared/V2/CodeGenerationOptions.cs @@ -10,7 +10,9 @@ public class GeoSpatialOptions } public class CodeGenerationOptions { - + + public bool GenerateAbstractClasses { get; set; } = true; + /// /// The constructor used for the Newtonsoft JsonPropertyAttribute when setting DTO Property Attributes. /// diff --git a/src/Shared/V2/ManifestService.cs b/src/Shared/V2/ManifestService.cs index c497f06..0bab46f 100644 --- a/src/Shared/V2/ManifestService.cs +++ b/src/Shared/V2/ManifestService.cs @@ -863,18 +863,18 @@ private Dictionary BuildParametersForcolumn(ILGenerator en { - case "maxLength" when propertyInfo.MaxLength.HasValue: + case "maxLength" when propertyInfo.AttributeType.MaxLength.HasValue: - dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldc_I4, propertyInfo.MaxLength.Value), arg1); + dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldc_I4, propertyInfo.AttributeType.MaxLength.Value), arg1); - AddParameterComparison(parameters, argName, propertyInfo.MaxLength.Value); + 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.Required ?? false) || (propertyInfo.IsRequired ?? 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; @@ -885,8 +885,8 @@ private Dictionary BuildParametersForcolumn(ILGenerator en dynamicCodeService.EmitPropertyService.EmitNullable(entityCtorBuilderIL, () => entityCtorBuilderIL.Emit(OpCodes.Ldstr, "nvarchar(max)"), arg1); break; - case "type" when string.Equals(propertyInfo.AttributeType.Type, "text", StringComparison.OrdinalIgnoreCase) && !propertyInfo.MaxLength.HasValue: - case "type" when string.Equals(propertyInfo.AttributeType.Type, "string", StringComparison.OrdinalIgnoreCase) && !propertyInfo.MaxLength.HasValue: + 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: @@ -1067,6 +1067,7 @@ internal IDynamicTable[] CreateMigrationTables(MigrationDefinition migration, Dy var entityKey = pair.Key; var entity = pair.Value; + var entityMigration = migration.GetEntityMigration(entityKey); foreach (var field in GetFields(entity).Where(v => IsAttributeLookup(v.Value))) { @@ -1156,20 +1157,20 @@ internal IDynamicTable[] CreateMigrationTables(MigrationDefinition migration, Dy else { - var entityMigration = migration.GetEntityMigration(entityKey); + var migrationStrategy = entityMigration.MappingStrategyChange(); foreach (var newField in entityMigration.GetNewAttributes().OfType()) { - var required = (newField.IsRequired ?? false) || (newField.IsRequired ?? false); + 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.Required = false; + newField.IsRequired = newField.AttributeType.Required = false; EmitAddColumn(migrationBuilder.UpMethodIL, entity.CollectionSchemaName, schema, newField); - newField.IsRequired = newField.Required = required; + newField.IsRequired = newField.AttributeType.Required = required; if (IsFieldLookup(newField)) { @@ -1227,36 +1228,32 @@ INNER JOIN foreach (var existingField in entityMigration.GetExistingFields()) { - //if (!existingField.Target.IsRowVersion && IsBaseMember(migration.Entities, entity, existingField.Key, out var parent) && migrationStrategy == MappingStrategyChangeEnum.TPT2TPC) - //{ - // var upSql1 = $@"UPDATE - // [{schema}].[{entity.CollectionSchemaName}] - // SET - // [{schema}].[{entity.CollectionSchemaName}].[{existingField.Target.SchemaName}] = BaseRecords.[{existingField.Target.SchemaName}] - // FROM - // [{schema}].[{entity.CollectionSchemaName}] Records - // INNER JOIN - // [{schema}].[{parent.CollectionSchemaName}] BaseRecords - // ON - // records.Id = BaseRecords.Id;"; - - // EmitSQLUp(migrationBuilder.UpMethodIL, upSql1); - - // // see comment above for why we alter the column to required. - // if (IsFieldRequired(existingField.Source)) - // { - // EmitAlterColumn(migrationBuilder.UpMethodIL, entity.CollectionSchemaName, schema, existingField.Target); - // } - - //} - - 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) + 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(" ", "")); + $"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(" ", "")); + } + } } @@ -1267,7 +1264,19 @@ INNER JOIN } + + // var fields = entity.GetAllProperties(migration.Entities).Values.OfType().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); + } @@ -1308,7 +1317,7 @@ INNER JOIN private bool IsFieldRequired(AttributeObjectDefinition source) { - return source.IsRequired ?? source.Required ?? false; + return source.IsRequired ?? source.AttributeType.Required ?? false; } public bool IsFieldLookup(AttributeObjectDefinition source) @@ -1555,6 +1564,23 @@ public static EntityDefinition GetParentEntity(this EntityDefinition entity, Dic return null; } + public static AttributeObjectDefinition GetField(this EntityDefinition entity,string key, Dictionary 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 GetAllProperties(this EntityDefinition entity, Dictionary entities) { @@ -1598,14 +1624,24 @@ public static IEnumerable GetAttributesMovingFromBase(t return targetAttributes.Where(e => !sourceAttributes.ContainsKey(e.Key) && !target.Attributes.ContainsKey(e.Key)).Select(c => c.Value); } - public static Dictionary GetProperties(this EntityDefinition entity, + public static Dictionary GetProperties(this EntityDefinition entity, Dictionary entities) { - return (entity.GetMappingStrategy(entities) == MappingStrategy.TPC ? - entity.GetAllProperties(entities) : entity.Attributes); + return (entity.GetMappingStrategy(entities) == MappingStrategy.TPC ? + entity.GetAllProperties(entities) : entity.Attributes) + .OfType() + .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 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(); + } public static MappingStrategyChangeEnum MappingStrategyChange(this MigrationEntityDefinition migrationEntity) { diff --git a/test/EAVFramework.UnitTest/EmitTests.cs b/test/EAVFramework.UnitTest/EmitTests.cs index 4a26234..0ad7e0d 100644 --- a/test/EAVFramework.UnitTest/EmitTests.cs +++ b/test/EAVFramework.UnitTest/EmitTests.cs @@ -113,7 +113,14 @@ private static void AssertFiles(IDictionary code, string folder) { foreach (var file in code) { - Assert.AreEqual(RemoveWhitespace(File.ReadAllText("Specs/" + folder + "/" + file.Key + ".txt")), RemoveWhitespace(file.Value), file.Key); + var a = File.ReadAllText("Specs/" + folder + "/" + file.Key + ".txt"); + var b = file.Value; + if (RemoveWhitespace(a) != RemoveWhitespace(file.Value)) + { + + } + Assert.AreEqual( + RemoveWhitespace(File.ReadAllText("Specs/" + folder + "/" + file.Key + ".txt")), RemoveWhitespace(file.Value), file.Key); } } diff --git a/test/EAVFramework.UnitTest/ManifestTests/BaseManifestTests.cs b/test/EAVFramework.UnitTest/ManifestTests/BaseManifestTests.cs index ce7077f..c3bd237 100644 --- a/test/EAVFramework.UnitTest/ManifestTests/BaseManifestTests.cs +++ b/test/EAVFramework.UnitTest/ManifestTests/BaseManifestTests.cs @@ -87,6 +87,7 @@ protected static DynamicCodeService CreateOptions(Action //onconfig(o); var o =new DynamicCodeServiceFactory().CreateOptions(onconfig); + o.GenerateAbstractClasses = false; return new DynamicCodeService(o); }