Skip to content

Commit 5a1bf93

Browse files
authored
Merge pull request #66 from delegateas/fix/handle-column-duplicate-names
Post-fix column names to avoid collisions
2 parents b500a7a + dddc407 commit 5a1bf93

2 files changed

Lines changed: 170 additions & 16 deletions

File tree

src/DataverseProxyGenerator.Core/Generation/Mappers/ProxyClassMapper.cs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,22 @@ namespace DataverseProxyGenerator.Core.Generation.Mappers;
55

66
public static class ProxyClassMapper
77
{
8+
/// <summary>
9+
/// The list is limited to properties where collisions have been identified.
10+
/// </summary>
11+
private static readonly HashSet<string> RestrictedAttributeNames = new(StringComparer.Ordinal)
12+
{
13+
"Attributes", // Collision on SdkMessageProcessingStepImage
14+
};
15+
816
public static object MapToTemplateModel((TableModel Table, IReadOnlyList<string> Interfaces) input, GenerationContext context)
917
{
1018
ArgumentNullException.ThrowIfNull(context);
1119
ArgumentNullException.ThrowIfNull(input.Table);
1220

1321
var (table, interfaces) = input;
1422

15-
var processedColumns = ProcessColumnsWithClassNameConflictResolution(table.Columns, table.SchemaName);
16-
17-
if (table.SchemaName == "EnvironmentVariableDefinition") {
18-
foreach (var key in table.Keys) {
19-
Console.WriteLine(key.SchemaName);
20-
foreach (var attr in key.KeyAttributes) {
21-
Console.WriteLine($" {attr.SchemaName} : {attr.TypeName}");
22-
}
23-
}
24-
}
23+
var processedColumns = ProcessColumnsWithNameConflictResolution(table.Columns, table.SchemaName);
2524

2625
return new
2726
{
@@ -43,8 +42,11 @@ public static object MapToTemplateModel((TableModel Table, IReadOnlyList<string>
4342
};
4443
}
4544

46-
private static IEnumerable<ColumnModel> ProcessColumnsWithClassNameConflictResolution(IEnumerable<ColumnModel> columns, string className)
45+
private static IEnumerable<ColumnModel> ProcessColumnsWithNameConflictResolution(IEnumerable<ColumnModel> columns, string className)
4746
{
47+
var usedNames = new HashSet<string>(RestrictedAttributeNames, StringComparer.Ordinal);
48+
usedNames.Add(className);
49+
4850
return columns.Select(c =>
4951
{
5052
var sanitizedColumn = c switch
@@ -60,14 +62,20 @@ private static IEnumerable<ColumnModel> ProcessColumnsWithClassNameConflictResol
6062
},
6163
};
6264

63-
// Check if sanitized schema name conflicts with class name (case-sensitive)
64-
if (string.Equals(sanitizedColumn.SchemaName, className, StringComparison.Ordinal))
65+
var defaultName = sanitizedColumn.SchemaName;
66+
67+
// Ensure the final name is unique (handle edge case where _1 suffix also conflicts)
68+
var candidateName = defaultName;
69+
var suffix = 0;
70+
while (usedNames.Contains(candidateName))
6571
{
66-
var finalName = $"{sanitizedColumn.SchemaName}_1";
67-
return sanitizedColumn with { SchemaName = finalName };
72+
suffix++;
73+
candidateName = $"{defaultName}_{suffix}";
6874
}
6975

70-
return sanitizedColumn;
76+
usedNames.Add(candidateName);
77+
78+
return sanitizedColumn with { SchemaName = candidateName };
7179
});
7280
}
7381
}

tests/DataverseProxyGenerator.Tests/ProxyClassMapperTests.cs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,152 @@ public void MapToTemplateModel_WithCaseSensitiveClassNameConflict_AppliesRenamin
151151
Assert.Contains(resultColumns, c => c.SchemaName == "testentity");
152152
}
153153

154+
[Fact]
155+
public void MapToTemplateModel_WithEntityBaseClassPropertyConflicts_AppendsUnderscoreOne()
156+
{
157+
// Arrange
158+
var table = new TableModel
159+
{
160+
SchemaName = "TestEntity",
161+
LogicalName = "testentity",
162+
DisplayName = "Test Entity",
163+
Description = "Test entity",
164+
EntityTypeCode = 10001,
165+
PrimaryNameAttribute = "name",
166+
PrimaryIdAttribute = "testentityid",
167+
IsIntersect = false,
168+
Columns = new ColumnModel[]
169+
{
170+
new StringColumnModel
171+
{
172+
SchemaName = "Attributes", // Conflicts with Entity.Attributes
173+
LogicalName = "attributes",
174+
DisplayName = "Attributes",
175+
MaxLength = 100,
176+
},
177+
},
178+
Relationships = new List<RelationshipModel>(),
179+
};
180+
181+
var context = CreateTestContext();
182+
183+
// Act
184+
var result = ProxyClassMapper.MapToTemplateModel((table, new List<string>()), context);
185+
var resultColumns = GetColumnsFromResult(result);
186+
187+
// Assert
188+
Assert.Single(resultColumns);
189+
Assert.Contains(resultColumns, c => c.SchemaName == "Attributes_1"); // Renamed due to conflict
190+
}
191+
192+
[Fact]
193+
public void MapToTemplateModel_WithCaseSensitiveEntityBaseClassConflict_OnlyRenamesNonVirtualExactMatch()
194+
{
195+
// Arrange
196+
var table = new TableModel
197+
{
198+
SchemaName = "TestEntity",
199+
LogicalName = "testentity",
200+
DisplayName = "Test Entity",
201+
Description = "Test entity",
202+
EntityTypeCode = 10001,
203+
PrimaryNameAttribute = "name",
204+
PrimaryIdAttribute = "testentityid",
205+
IsIntersect = false,
206+
Columns = new ColumnModel[]
207+
{
208+
new StringColumnModel
209+
{
210+
SchemaName = "Attributes", // Exact match with non-virtual property - should be renamed
211+
LogicalName = "attributes",
212+
DisplayName = "Attributes",
213+
MaxLength = 100,
214+
},
215+
new StringColumnModel
216+
{
217+
SchemaName = "attributes", // Different case - no conflict
218+
LogicalName = "attributes_lower",
219+
DisplayName = "attributes lower",
220+
MaxLength = 100,
221+
},
222+
new StringColumnModel
223+
{
224+
SchemaName = "ATTRIBUTES", // Different case - no conflict
225+
LogicalName = "attributes_upper",
226+
DisplayName = "ATTRIBUTES upper",
227+
MaxLength = 100,
228+
},
229+
},
230+
Relationships = new List<RelationshipModel>(),
231+
};
232+
233+
var context = CreateTestContext();
234+
235+
// Act
236+
var result = ProxyClassMapper.MapToTemplateModel((table, new List<string>()), context);
237+
var resultColumns = GetColumnsFromResult(result);
238+
239+
// Assert
240+
Assert.Equal(3, resultColumns.Count);
241+
Assert.Contains(resultColumns, c => c.SchemaName == "Attributes_1"); // Exact match renamed
242+
Assert.Contains(resultColumns, c => c.SchemaName == "attributes"); // Different case kept
243+
Assert.Contains(resultColumns, c => c.SchemaName == "ATTRIBUTES"); // Different case kept
244+
}
245+
246+
[Fact]
247+
public void MapToTemplateModel_WithMultipleConflictTypes_AppliesAllRenames()
248+
{
249+
// Arrange
250+
var table = new TableModel
251+
{
252+
SchemaName = "Account",
253+
LogicalName = "account",
254+
DisplayName = "Account",
255+
Description = "Test account",
256+
EntityTypeCode = 1,
257+
PrimaryNameAttribute = "name",
258+
PrimaryIdAttribute = "accountid",
259+
IsIntersect = false,
260+
Columns = new ColumnModel[]
261+
{
262+
new StringColumnModel
263+
{
264+
SchemaName = "Account", // Conflicts with class name
265+
LogicalName = "account_field",
266+
DisplayName = "Account Field",
267+
MaxLength = 100,
268+
},
269+
new StringColumnModel
270+
{
271+
SchemaName = "Attributes", // Conflicts with Entity base class
272+
LogicalName = "attributes",
273+
DisplayName = "Attributes",
274+
MaxLength = 100,
275+
},
276+
new StringColumnModel
277+
{
278+
SchemaName = "Name", // No conflict
279+
LogicalName = "name",
280+
DisplayName = "Name",
281+
MaxLength = 100,
282+
},
283+
},
284+
Relationships = new List<RelationshipModel>(),
285+
};
286+
287+
var context = CreateTestContext();
288+
289+
// Act
290+
var result = ProxyClassMapper.MapToTemplateModel((table, new List<string>()), context);
291+
var resultColumns = GetColumnsFromResult(result);
292+
293+
// Assert
294+
Assert.Equal(3, resultColumns.Count);
295+
Assert.Contains(resultColumns, c => c.SchemaName == "Account_1"); // Class name conflict
296+
Assert.Contains(resultColumns, c => c.SchemaName == "Attributes_1"); // Base class conflict (non-virtual)
297+
Assert.Contains(resultColumns, c => c.SchemaName == "Name"); // No conflict
298+
}
299+
154300
private static GenerationContext CreateTestContext()
155301
{
156302
return new GenerationContext

0 commit comments

Comments
 (0)