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