diff --git a/src/DataverseProxyGenerator.Core/Generation/Mappers/ProxyClassMapper.cs b/src/DataverseProxyGenerator.Core/Generation/Mappers/ProxyClassMapper.cs index b25923a..9b49804 100644 --- a/src/DataverseProxyGenerator.Core/Generation/Mappers/ProxyClassMapper.cs +++ b/src/DataverseProxyGenerator.Core/Generation/Mappers/ProxyClassMapper.cs @@ -5,6 +5,14 @@ namespace DataverseProxyGenerator.Core.Generation.Mappers; public static class ProxyClassMapper { + /// + /// The list is limited to properties where collisions have been identified. + /// + private static readonly HashSet RestrictedAttributeNames = new(StringComparer.Ordinal) + { + "Attributes", // Collision on SdkMessageProcessingStepImage + }; + public static object MapToTemplateModel((TableModel Table, IReadOnlyList Interfaces) input, GenerationContext context) { ArgumentNullException.ThrowIfNull(context); @@ -12,16 +20,7 @@ public static object MapToTemplateModel((TableModel Table, IReadOnlyList var (table, interfaces) = input; - var processedColumns = ProcessColumnsWithClassNameConflictResolution(table.Columns, table.SchemaName); - - if (table.SchemaName == "EnvironmentVariableDefinition") { - foreach (var key in table.Keys) { - Console.WriteLine(key.SchemaName); - foreach (var attr in key.KeyAttributes) { - Console.WriteLine($" {attr.SchemaName} : {attr.TypeName}"); - } - } - } + var processedColumns = ProcessColumnsWithNameConflictResolution(table.Columns, table.SchemaName); return new { @@ -43,8 +42,11 @@ public static object MapToTemplateModel((TableModel Table, IReadOnlyList }; } - private static IEnumerable ProcessColumnsWithClassNameConflictResolution(IEnumerable columns, string className) + private static IEnumerable ProcessColumnsWithNameConflictResolution(IEnumerable columns, string className) { + var usedNames = new HashSet(RestrictedAttributeNames, StringComparer.Ordinal); + usedNames.Add(className); + return columns.Select(c => { var sanitizedColumn = c switch @@ -60,14 +62,20 @@ private static IEnumerable ProcessColumnsWithClassNameConflictResol }, }; - // Check if sanitized schema name conflicts with class name (case-sensitive) - if (string.Equals(sanitizedColumn.SchemaName, className, StringComparison.Ordinal)) + var defaultName = sanitizedColumn.SchemaName; + + // Ensure the final name is unique (handle edge case where _1 suffix also conflicts) + var candidateName = defaultName; + var suffix = 0; + while (usedNames.Contains(candidateName)) { - var finalName = $"{sanitizedColumn.SchemaName}_1"; - return sanitizedColumn with { SchemaName = finalName }; + suffix++; + candidateName = $"{defaultName}_{suffix}"; } - return sanitizedColumn; + usedNames.Add(candidateName); + + return sanitizedColumn with { SchemaName = candidateName }; }); } } \ No newline at end of file diff --git a/tests/DataverseProxyGenerator.Tests/ProxyClassMapperTests.cs b/tests/DataverseProxyGenerator.Tests/ProxyClassMapperTests.cs index c4ad270..b30846a 100644 --- a/tests/DataverseProxyGenerator.Tests/ProxyClassMapperTests.cs +++ b/tests/DataverseProxyGenerator.Tests/ProxyClassMapperTests.cs @@ -151,6 +151,152 @@ public void MapToTemplateModel_WithCaseSensitiveClassNameConflict_AppliesRenamin Assert.Contains(resultColumns, c => c.SchemaName == "testentity"); } + [Fact] + public void MapToTemplateModel_WithEntityBaseClassPropertyConflicts_AppendsUnderscoreOne() + { + // Arrange + var table = new TableModel + { + SchemaName = "TestEntity", + LogicalName = "testentity", + DisplayName = "Test Entity", + Description = "Test entity", + EntityTypeCode = 10001, + PrimaryNameAttribute = "name", + PrimaryIdAttribute = "testentityid", + IsIntersect = false, + Columns = new ColumnModel[] + { + new StringColumnModel + { + SchemaName = "Attributes", // Conflicts with Entity.Attributes + LogicalName = "attributes", + DisplayName = "Attributes", + MaxLength = 100, + }, + }, + Relationships = new List(), + }; + + var context = CreateTestContext(); + + // Act + var result = ProxyClassMapper.MapToTemplateModel((table, new List()), context); + var resultColumns = GetColumnsFromResult(result); + + // Assert + Assert.Single(resultColumns); + Assert.Contains(resultColumns, c => c.SchemaName == "Attributes_1"); // Renamed due to conflict + } + + [Fact] + public void MapToTemplateModel_WithCaseSensitiveEntityBaseClassConflict_OnlyRenamesNonVirtualExactMatch() + { + // Arrange + var table = new TableModel + { + SchemaName = "TestEntity", + LogicalName = "testentity", + DisplayName = "Test Entity", + Description = "Test entity", + EntityTypeCode = 10001, + PrimaryNameAttribute = "name", + PrimaryIdAttribute = "testentityid", + IsIntersect = false, + Columns = new ColumnModel[] + { + new StringColumnModel + { + SchemaName = "Attributes", // Exact match with non-virtual property - should be renamed + LogicalName = "attributes", + DisplayName = "Attributes", + MaxLength = 100, + }, + new StringColumnModel + { + SchemaName = "attributes", // Different case - no conflict + LogicalName = "attributes_lower", + DisplayName = "attributes lower", + MaxLength = 100, + }, + new StringColumnModel + { + SchemaName = "ATTRIBUTES", // Different case - no conflict + LogicalName = "attributes_upper", + DisplayName = "ATTRIBUTES upper", + MaxLength = 100, + }, + }, + Relationships = new List(), + }; + + var context = CreateTestContext(); + + // Act + var result = ProxyClassMapper.MapToTemplateModel((table, new List()), context); + var resultColumns = GetColumnsFromResult(result); + + // Assert + Assert.Equal(3, resultColumns.Count); + Assert.Contains(resultColumns, c => c.SchemaName == "Attributes_1"); // Exact match renamed + Assert.Contains(resultColumns, c => c.SchemaName == "attributes"); // Different case kept + Assert.Contains(resultColumns, c => c.SchemaName == "ATTRIBUTES"); // Different case kept + } + + [Fact] + public void MapToTemplateModel_WithMultipleConflictTypes_AppliesAllRenames() + { + // Arrange + var table = new TableModel + { + SchemaName = "Account", + LogicalName = "account", + DisplayName = "Account", + Description = "Test account", + EntityTypeCode = 1, + PrimaryNameAttribute = "name", + PrimaryIdAttribute = "accountid", + IsIntersect = false, + Columns = new ColumnModel[] + { + new StringColumnModel + { + SchemaName = "Account", // Conflicts with class name + LogicalName = "account_field", + DisplayName = "Account Field", + MaxLength = 100, + }, + new StringColumnModel + { + SchemaName = "Attributes", // Conflicts with Entity base class + LogicalName = "attributes", + DisplayName = "Attributes", + MaxLength = 100, + }, + new StringColumnModel + { + SchemaName = "Name", // No conflict + LogicalName = "name", + DisplayName = "Name", + MaxLength = 100, + }, + }, + Relationships = new List(), + }; + + var context = CreateTestContext(); + + // Act + var result = ProxyClassMapper.MapToTemplateModel((table, new List()), context); + var resultColumns = GetColumnsFromResult(result); + + // Assert + Assert.Equal(3, resultColumns.Count); + Assert.Contains(resultColumns, c => c.SchemaName == "Account_1"); // Class name conflict + Assert.Contains(resultColumns, c => c.SchemaName == "Attributes_1"); // Base class conflict (non-virtual) + Assert.Contains(resultColumns, c => c.SchemaName == "Name"); // No conflict + } + private static GenerationContext CreateTestContext() { return new GenerationContext