Skip to content

Commit dd2d380

Browse files
Copilotarika0093
andauthored
Fix: Remove parent class nesting from implicit DTOs in hash namespaces (#219)
* Initial plan * Add test for issue: implicit DTOs should not be nested in parent class when using hash namespace Co-authored-by: arika0093 <4524647+arika0093@users.noreply.github.com> * Fix: Implicit DTOs should not be nested in parent class when using hash namespace Co-authored-by: arika0093 <4524647+arika0093@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: arika0093 <4524647+arika0093@users.noreply.github.com>
1 parent ae2ba5f commit dd2d380

File tree

2 files changed

+84
-10
lines changed

2 files changed

+84
-10
lines changed

src/Linqraft.Core/SelectExprInfoExplicitDto.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,23 @@ private List<GenerateDtoClassInfo> GenerateDtoClasses(
135135
var actualNamespace = GetActualDtoNamespace();
136136

137137
// When NestedDtoUseHashNamespace option is enabled, child DTOs are placed in
138-
// a hash-named sub-namespace (e.g., LinqraftGenerated_{Hash}.ClassName)
138+
// a hash-named sub-namespace (e.g., LinqraftGenerated_{Hash}) WITHOUT parent class nesting
139139
// However, DTOs with explicit names from nested SelectExpr should NOT use hash namespace
140+
// and should maintain their parent class structure
141+
List<string> finalParentClasses = currentParentClasses;
142+
List<string> finalParentAccessibilities = currentParentAccessibilities;
143+
140144
if (!isExplicitDto && Configuration?.NestedDtoUseHashNamespace == true)
141145
{
142146
var hash = structure.GetUniqueId();
143147
actualNamespace = string.IsNullOrEmpty(actualNamespace)
144148
? $"LinqraftGenerated_{hash}"
145149
: $"{actualNamespace}.LinqraftGenerated_{hash}";
150+
151+
// Implicit DTOs in hash namespace should NOT be nested inside parent classes
152+
// They are managed by the hash, so they don't need to exist within a class
153+
finalParentClasses = [];
154+
finalParentAccessibilities = [];
146155
}
147156

148157
var dtoClassInfo = new GenerateDtoClassInfo
@@ -152,8 +161,8 @@ private List<GenerateDtoClassInfo> GenerateDtoClasses(
152161
ClassName = className,
153162
Structure = structure,
154163
NestedClasses = [.. result],
155-
ParentClasses = currentParentClasses,
156-
ParentAccessibilities = currentParentAccessibilities,
164+
ParentClasses = finalParentClasses,
165+
ParentAccessibilities = finalParentAccessibilities,
157166
ExistingProperties = existingProperties,
158167
IsExplicitRootDto = isExplicitDto, // Mark explicit DTOs (main or from nested SelectExpr) to avoid adding the attribute
159168
};
@@ -258,7 +267,8 @@ protected override string GetClassName(DtoStructure structure)
258267

259268
/// <summary>
260269
/// Gets the full name for a nested DTO class using the structure.
261-
/// When NestedDtoUseHashNamespace is enabled, includes the LinqraftGenerated_{hash} namespace.
270+
/// When NestedDtoUseHashNamespace is enabled, includes the LinqraftGenerated_{hash} namespace
271+
/// WITHOUT parent class nesting (implicit DTOs are managed by hash, not class hierarchy).
262272
/// </summary>
263273
protected override string GetNestedDtoFullNameFromStructure(DtoStructure nestedStructure)
264274
{
@@ -269,22 +279,20 @@ protected override string GetNestedDtoFullNameFromStructure(DtoStructure nestedS
269279
var actualNamespace = GetActualDtoNamespace();
270280

271281
// When NestedDtoUseHashNamespace option is enabled, include LinqraftGenerated_{hash} in namespace
282+
// Implicit DTOs should NOT be nested inside parent classes - they are placed directly
283+
// in the LinqraftGenerated_{hash} namespace
272284
if (Configuration?.NestedDtoUseHashNamespace == true)
273285
{
274286
var hash = nestedStructure.GetUniqueId();
275287
var generatedNamespace = string.IsNullOrEmpty(actualNamespace)
276288
? $"LinqraftGenerated_{hash}"
277289
: $"{actualNamespace}.LinqraftGenerated_{hash}";
278290

279-
// Handle parent classes
280-
if (ParentClasses.Count > 0)
281-
{
282-
return $"global::{generatedNamespace}.{string.Join(".", ParentClasses)}.{className}";
283-
}
291+
// Implicit DTOs in hash namespace should NOT include parent classes
284292
return $"global::{generatedNamespace}.{className}";
285293
}
286294

287-
// Default behavior: use GetNestedDtoFullName
295+
// Default behavior: use GetNestedDtoFullName (includes parent classes)
288296
return GetNestedDtoFullName(className);
289297
}
290298

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
namespace Linqraft.Tests;
5+
6+
/// <summary>
7+
/// Test case for issue: Minor issues when generating multi-layer DTO classes for classes nested within other classes
8+
/// When NestedDtoUseHashNamespace is enabled (default), implicit DTOs should be placed in LinqraftGenerated_{hash} namespace
9+
/// WITHOUT being nested inside the parent class
10+
/// </summary>
11+
public partial class ClassInClassGeneratedExpr
12+
{
13+
private readonly List<NestedEntity> TestData = [];
14+
15+
[Fact]
16+
public void NestedSelectExpr_WithExplicitDtoTypes_ShouldWork()
17+
{
18+
var query = TestData.AsQueryable();
19+
20+
var result = query
21+
.SelectExpr<NestedEntity, NestedEntityDto>(x => new
22+
{
23+
x.Id,
24+
x.Name,
25+
Items = x.Items.Select(i => new { i.Id }),
26+
})
27+
.ToList();
28+
29+
result.Count.ShouldBe(0);
30+
31+
// Verify that NestedEntityDto is NOT in the LinqraftGenerated_ namespace
32+
var nestedEntityDtoType = typeof(NestedEntityDto);
33+
nestedEntityDtoType.Namespace!.ShouldNotContain("LinqraftGenerated");
34+
nestedEntityDtoType.Namespace.ShouldBe("Linqraft.Tests");
35+
36+
// Verify that the auto-generated ItemsDto IS in the LinqraftGenerated_ namespace
37+
// and NOT nested inside ClassInClassGeneratedExpr
38+
var itemsProperty = nestedEntityDtoType.GetProperty("Items");
39+
itemsProperty.ShouldNotBeNull();
40+
var itemsElementType = itemsProperty!
41+
.PropertyType.GetGenericArguments()
42+
.FirstOrDefault();
43+
itemsElementType.ShouldNotBeNull();
44+
itemsElementType!.Namespace!.ShouldContain("LinqraftGenerated");
45+
46+
// The key assertion: ItemsDto should NOT be nested inside ClassInClassGeneratedExpr
47+
// It should be directly in the LinqraftGenerated_{hash} namespace
48+
var itemsDtoFullName = itemsElementType.FullName!;
49+
itemsDtoFullName.ShouldNotContain("ClassInClassGeneratedExpr");
50+
}
51+
52+
internal class NestedEntity
53+
{
54+
public int Id { get; set; }
55+
public string Name { get; set; } = null!;
56+
public List<NestedItem> Items { get; set; } = [];
57+
}
58+
59+
internal class NestedItem
60+
{
61+
public int Id { get; set; }
62+
public string Title { get; set; } = null!;
63+
}
64+
65+
internal partial class NestedEntityDto;
66+
}

0 commit comments

Comments
 (0)