Skip to content

Commit 97439c9

Browse files
committed
docs: add comprehensive documentation for Nested SelectExpr feature
1 parent a34d686 commit 97439c9

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

docs/library/nested-selectexpr.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Nested SelectExpr (Beta)
2+
3+
You can use `SelectExpr` inside another `SelectExpr` to explicitly control the DTO class generation for nested collections. This allows you to create reusable DTOs for nested entities instead of auto-generated DTOs in hash namespaces.
4+
5+
## Important Notes
6+
7+
### Beta Feature Warning
8+
9+
This feature is currently in **beta** (available since v0.6.2). While it works correctly, the API and behavior may change in future versions. Please report any issues on GitHub.
10+
11+
### .NET 9+ Recommended
12+
13+
This feature is **strongly recommended for .NET 9 or later**. In older .NET versions, type inference may fail for unknown reasons. See [GitHub Issue #211](https://github.com/your-org/linqraft/issues/211) for details.
14+
15+
If you must use this feature on older .NET versions:
16+
* Test thoroughly
17+
* Watch for type inference errors
18+
* Consider upgrading to .NET 9+ if possible
19+
20+
### Required: Empty Partial Class Declarations
21+
22+
To ensure DTOs are generated in the correct location, you **must** declare empty partial class definitions for all explicit DTO types:
23+
24+
```csharp
25+
public class MyService
26+
{
27+
public void GetOrders(IQueryable<Order> query)
28+
{
29+
var result = query
30+
.SelectExpr<Order, OrderDto>(o => new
31+
{
32+
o.Id,
33+
Items = o.OrderItems.SelectExpr<OrderItem, OrderItemDto>(i => new
34+
{
35+
i.ProductName,
36+
}),
37+
});
38+
}
39+
40+
// Empty partial class definitions - REQUIRED
41+
internal partial class OrderDto;
42+
internal partial class OrderItemDto;
43+
}
44+
```
45+
46+
**Why is this necessary?**
47+
48+
The source generator determines where to generate DTOs based on where the empty partial class is declared. Without these declarations:
49+
- The generator might place DTOs in the wrong namespace
50+
- DTO generation might fail
51+
- The generated code might not compile
52+
53+
**Requirements:**
54+
1. Always declare empty partial class definitions for all explicit DTO types
55+
2. Place the partial class definitions in the same scope as the `SelectExpr` call
56+
3. Use the correct access modifier (`public`, `internal`, etc.)
57+
58+
## Basic Usage
59+
60+
```csharp
61+
var result = query
62+
.SelectExpr<Order, OrderDto>(o => new
63+
{
64+
o.Id,
65+
o.CustomerName,
66+
// Using SelectExpr - ItemDto is generated in your namespace
67+
Items = o.OrderItems.SelectExpr<OrderItem, OrderItemDto>(i => new
68+
{
69+
i.ProductName,
70+
i.Quantity,
71+
}),
72+
});
73+
74+
// Required partial class declarations
75+
internal partial class OrderDto;
76+
internal partial class OrderItemDto;
77+
```
78+
79+
**Generated DTOs:**
80+
```csharp
81+
namespace MyProject
82+
{
83+
public partial class OrderDto
84+
{
85+
public required int Id { get; set; }
86+
public required string CustomerName { get; set; }
87+
public required IEnumerable<OrderItemDto> Items { get; set; }
88+
}
89+
90+
// This DTO is directly accessible and reusable
91+
public partial class OrderItemDto
92+
{
93+
public required string ProductName { get; set; }
94+
public required int Quantity { get; set; }
95+
}
96+
}
97+
```
98+
99+
## Multiple Nesting Levels
100+
101+
You can nest `SelectExpr` calls multiple levels deep:
102+
103+
```csharp
104+
var result = query
105+
.SelectExpr<Entity, EntityDto>(x => new
106+
{
107+
x.Id,
108+
x.Name,
109+
Items = x.Items.SelectExpr<Item, ItemDto>(i => new
110+
{
111+
i.Id,
112+
i.Title,
113+
SubItems = i.SubItems.SelectExpr<SubItem, SubItemDto>(si => new
114+
{
115+
si.Id,
116+
si.Value,
117+
}),
118+
}),
119+
})
120+
.ToList();
121+
122+
// Declare all DTO types
123+
internal partial class EntityDto;
124+
internal partial class ItemDto;
125+
internal partial class SubItemDto;
126+
```
127+
128+
## Mixing Select and SelectExpr
129+
130+
You can mix regular `Select` and `SelectExpr` within the same query:
131+
132+
```csharp
133+
var result = query
134+
.SelectExpr<Entity, EntityDto>(x => new
135+
{
136+
x.Id,
137+
// Reusable DTO - generated in your namespace
138+
Items = x.Items.SelectExpr<Item, ItemDto>(i => new
139+
{
140+
i.Id,
141+
// Auto-generated DTO in hash namespace
142+
SubItems = i.SubItems.Select(si => new { si.Value }),
143+
}),
144+
});
145+
146+
internal partial class EntityDto;
147+
internal partial class ItemDto;
148+
// No need to declare SubItemDto - it's auto-generated
149+
```
150+
151+
## Collection Types
152+
153+
Nested `SelectExpr` works with various collection types:
154+
155+
```csharp
156+
var result = query
157+
.SelectExpr<Entity, EntityDto>(x => new
158+
{
159+
// IEnumerable (default)
160+
ItemsEnumerable = x.Items.SelectExpr<Item, ItemDtoEnumerable>(i => new { i.Id }),
161+
162+
// List
163+
ItemsList = x.Items.SelectExpr<Item, ItemDtoList>(i => new { i.Id }).ToList(),
164+
165+
// Array
166+
ItemsArray = x.Items.SelectExpr<Item, ItemDtoArray>(i => new { i.Id }).ToArray(),
167+
});
168+
169+
internal partial class EntityDto;
170+
internal partial class ItemDtoEnumerable;
171+
internal partial class ItemDtoList;
172+
internal partial class ItemDtoArray;
173+
```
174+
175+
## When to Use Nested SelectExpr
176+
177+
**Use nested `SelectExpr` when:**
178+
* You need to reuse nested DTOs across multiple queries
179+
* You want full control over nested DTO naming
180+
* You need to extend nested DTOs with partial classes
181+
* You want to reference nested DTOs in your API documentation
182+
183+
**Use regular `Select` when:**
184+
* The nested DTO is used only once
185+
* You don't need to reference the nested DTO type
186+
* You prefer simpler, less verbose code
187+
188+
## Comparison
189+
190+
| Feature | Regular Select | Nested SelectExpr |
191+
|---------|---------------|-------------------|
192+
| DTO Location | `LinqraftGenerated_HASH` namespace | Your namespace |
193+
| Reusability | No | Yes |
194+
| Declaration Required | No | Yes (empty partial class) |
195+
| .NET Version | Any | .NET 9+ recommended |
196+
197+
## See Also
198+
199+
* [Usage Patterns](usage-patterns.md) - Overview of all SelectExpr patterns
200+
* [Nested DTO Naming](nested-dto-naming.md) - Configure nested DTO naming strategy
201+
* [Partial Classes](partial-classes.md) - Extend generated DTOs

0 commit comments

Comments
 (0)