From 449665c53da7589a64411d838d2dbd12f2880e23 Mon Sep 17 00:00:00 2001 From: Arika Ishinami Date: Mon, 8 Dec 2025 00:18:22 +0900 Subject: [PATCH 1/4] docs: add Nested SelectExpr documentation for reusable DTOs --- README.md | 1 + docs/library/nested-selectexpr.md | 272 ++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 docs/library/nested-selectexpr.md diff --git a/README.md b/README.md index f7f2b6b9..6a282565 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,7 @@ For more usage patterns and examples, see the [Usage Patterns Guide](./docs/libr ### Customization * **[Local Variable Capture](./docs/library/local-variable-capture.md)** - Using local variables in SelectExpr +* **[Nested SelectExpr](./docs/library/nested-selectexpr.md) (beta)** - Using nested SelectExpr for reusable DTOs * **[Array Nullability Removal](./docs/library/array-nullability.md)** - Automatic null handling for collections * **[Partial Classes](./docs/library/partial-classes.md)** - Extending generated DTOs * **[Nested DTO Naming](./docs/library/nested-dto-naming.md)** - Customizing nested DTO names diff --git a/docs/library/nested-selectexpr.md b/docs/library/nested-selectexpr.md new file mode 100644 index 00000000..80cccd83 --- /dev/null +++ b/docs/library/nested-selectexpr.md @@ -0,0 +1,272 @@ +# Nested SelectExpr + +You can use `SelectExpr` inside another `SelectExpr` to explicitly control the DTO class generation for nested collections. + +## Overview + +When using nested collections in `SelectExpr`, you have two options: + +1. **Regular `Select`**: Nested DTOs are auto-generated in a hash namespace (not reusable) +2. **Nested `SelectExpr`**: Nested DTOs are generated in your namespace (reusable) + +## Basic Usage + +### Without Nested SelectExpr (Default) + +```csharp +var result = query + .SelectExpr(o => new + { + o.Id, + o.CustomerName, + // Using regular Select - ItemDto is auto-generated in LinqraftGenerated_ namespace + Items = o.OrderItems.Select(i => new + { + i.ProductName, + i.Quantity, + }), + }); +``` + +**Generated DTOs:** +```csharp +namespace MyProject +{ + public partial class OrderDto + { + public required int Id { get; set; } + public required string CustomerName { get; set; } + public required IEnumerable Items { get; set; } + } +} + +namespace MyProject.LinqraftGenerated_HASH +{ + // This DTO is not directly accessible or reusable + public partial class ItemDto + { + public required string ProductName { get; set; } + public required int Quantity { get; set; } + } +} +``` + +### With Nested SelectExpr (v0.6.2+) + +```csharp +var result = query + .SelectExpr(o => new + { + o.Id, + o.CustomerName, + // Using SelectExpr - ItemDto is generated in your namespace + Items = o.OrderItems.SelectExpr(i => new + { + i.ProductName, + i.Quantity, + }), + }); +``` + +**Generated DTOs:** +```csharp +namespace MyProject +{ + public partial class OrderDto + { + public required int Id { get; set; } + public required string CustomerName { get; set; } + public required IEnumerable Items { get; set; } + } + + // This DTO is directly accessible and reusable + public partial class OrderItemDto + { + public required string ProductName { get; set; } + public required int Quantity { get; set; } + } +} +``` + +## Controlling DTO Generation with Empty Partial Classes + +To ensure DTOs are generated in the correct location, you must declare empty partial class definitions: + +```csharp +public class MyService +{ + public void GetOrders(IQueryable query) + { + var result = query + .SelectExpr(o => new + { + o.Id, + Items = o.OrderItems.SelectExpr(i => new + { + i.ProductName, + }), + }); + } + + // Empty partial class definitions to control DTO generation location + internal partial class OrderDto; + internal partial class OrderItemDto; +} +``` + +**Why is this necessary?** + +The source generator determines where to generate DTOs based on where the empty partial class is declared. Without these declarations: +- The generator might place DTOs in the wrong namespace +- DTO generation might fail +- The generated code might not compile + +## Multiple Nesting Levels + +You can nest `SelectExpr` calls multiple levels deep: + +```csharp +var result = query + .SelectExpr(x => new + { + x.Id, + x.Name, + Items = x.Items.SelectExpr(i => new + { + i.Id, + i.Title, + SubItems = i.SubItems.SelectExpr(si => new + { + si.Id, + si.Value, + }), + }), + }) + .ToList(); + +// Declare all DTO types +internal partial class EntityDto; +internal partial class ItemDto; +internal partial class SubItemDto; +``` + +## Mixing Select and SelectExpr + +You can mix regular `Select` and `SelectExpr` within the same query: + +```csharp +var result = query + .SelectExpr(x => new + { + x.Id, + // Reusable DTO + Items = x.Items.SelectExpr(i => new + { + i.Id, + // Auto-generated DTO in hash namespace + SubItems = i.SubItems.Select(si => new { si.Value }), + }), + }); + +internal partial class EntityDto; +internal partial class ItemDto; +// No need to declare SubItemDto - it's auto-generated +``` + +## Collection Types + +Nested `SelectExpr` works with various collection types: + +```csharp +var result = query + .SelectExpr(x => new + { + // IEnumerable (default) + ItemsEnumerable = x.Items.SelectExpr(i => new { i.Id }), + + // List + ItemsList = x.Items.SelectExpr(i => new { i.Id }).ToList(), + + // Array + ItemsArray = x.Items.SelectExpr(i => new { i.Id }).ToArray(), + }); + +internal partial class EntityDto; +internal partial class ItemDtoEnumerable; +internal partial class ItemDtoList; +internal partial class ItemDtoArray; +``` + +## When to Use Nested SelectExpr + +**Use nested `SelectExpr` when:** +* You need to reuse nested DTOs across multiple queries +* You want full control over nested DTO naming +* You need to extend nested DTOs with partial classes +* You want to reference nested DTOs in your API documentation + +**Use regular `Select` when:** +* The nested DTO is used only once +* You don't need to reference the nested DTO type +* You prefer simpler, less verbose code + +## Important Notes + +### Beta Feature Warning + +This feature is currently in **beta**. While it works correctly, the API and behavior may change in future versions. Please report any issues on GitHub. + +### .NET 9+ Recommended + +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. + +If you must use this feature on older .NET versions: +* Test thoroughly +* Watch for type inference errors +* Consider upgrading to .NET 9+ if possible + +### Compilation Requirements + +When using nested `SelectExpr`: +1. Always declare empty partial class definitions for all explicit DTO types +2. Place the partial class definitions in the same scope as the `SelectExpr` call +3. Use the correct access modifier (`public`, `internal`, etc.) + +```csharp +// Good: DTOs declared in the same class +public class MyService +{ + public void Query() { + /* SelectExpr here */ + } + + internal partial class EntityDto; + internal partial class ItemDto; +} + +// Bad: DTOs not declared +public class MyService +{ + public void Query() { + /* SelectExpr here - may fail to compile */ + } +} +``` + +## Comparison + +| Feature | Regular Select | Nested SelectExpr | +|---------|---------------|-------------------| +| DTO Location | `LinqraftGenerated_HASH` namespace | Your namespace | +| Reusability | Not reusable | Reusable | +| Partial Class Support | Limited | Full support | +| Declaration Required | No | Yes (empty partial class) | +| Verbosity | Low | Medium | +| Type Safety | High | High | +| .NET Version | Any | .NET 9+ recommended | + +## See Also + +* [Usage Patterns](usage-patterns.md) - Overview of all SelectExpr patterns +* [Nested DTO Naming](nested-dto-naming.md) - Configure nested DTO naming strategy +* [Partial Classes](partial-classes.md) - Extend generated DTOs From 3fddaf5bc66bba3f5d33b40f46af231ad011b750 Mon Sep 17 00:00:00 2001 From: Arika Ishinami Date: Mon, 8 Dec 2025 00:26:23 +0900 Subject: [PATCH 2/4] docs: update Nested SelectExpr documentation with important notes and usage requirements --- docs/library/nested-selectexpr.md | 167 +++++++++--------------------- 1 file changed, 48 insertions(+), 119 deletions(-) diff --git a/docs/library/nested-selectexpr.md b/docs/library/nested-selectexpr.md index 80cccd83..24409d12 100644 --- a/docs/library/nested-selectexpr.md +++ b/docs/library/nested-selectexpr.md @@ -1,57 +1,61 @@ -# Nested SelectExpr +# Nested SelectExpr (Beta) -You can use `SelectExpr` inside another `SelectExpr` to explicitly control the DTO class generation for nested collections. +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. -## Overview +## Important Notes -When using nested collections in `SelectExpr`, you have two options: +### Beta Feature Warning -1. **Regular `Select`**: Nested DTOs are auto-generated in a hash namespace (not reusable) -2. **Nested `SelectExpr`**: Nested DTOs are generated in your namespace (reusable) +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. -## Basic Usage +### .NET 9+ Recommended -### Without Nested SelectExpr (Default) +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. -```csharp -var result = query - .SelectExpr(o => new - { - o.Id, - o.CustomerName, - // Using regular Select - ItemDto is auto-generated in LinqraftGenerated_ namespace - Items = o.OrderItems.Select(i => new - { - i.ProductName, - i.Quantity, - }), - }); -``` +If you must use this feature on older .NET versions: +* Test thoroughly +* Watch for type inference errors +* Consider upgrading to .NET 9+ if possible + +### Required: Empty Partial Class Declarations + +To ensure DTOs are generated in the correct location, you **must** declare empty partial class definitions for all explicit DTO types: -**Generated DTOs:** ```csharp -namespace MyProject +public class MyService { - public partial class OrderDto + public void GetOrders(IQueryable query) { - public required int Id { get; set; } - public required string CustomerName { get; set; } - public required IEnumerable Items { get; set; } + var result = query + .SelectExpr(o => new + { + o.Id, + Items = o.OrderItems.SelectExpr(i => new + { + i.ProductName, + }), + }); } -} -namespace MyProject.LinqraftGenerated_HASH -{ - // This DTO is not directly accessible or reusable - public partial class ItemDto - { - public required string ProductName { get; set; } - public required int Quantity { get; set; } - } + // Empty partial class definitions - REQUIRED + internal partial class OrderDto; + internal partial class OrderItemDto; } ``` -### With Nested SelectExpr (v0.6.2+) +**Why is this necessary?** + +The source generator determines where to generate DTOs based on where the empty partial class is declared. Without these declarations: +- The generator might place DTOs in the wrong namespace +- DTO generation might fail +- The generated code might not compile + +**Requirements:** +1. Always declare empty partial class definitions for all explicit DTO types +2. Place the partial class definitions in the same scope as the `SelectExpr` call +3. Use the correct access modifier (`public`, `internal`, etc.) + +## Basic Usage ```csharp var result = query @@ -66,6 +70,10 @@ var result = query i.Quantity, }), }); + +// Required partial class declarations +internal partial class OrderDto; +internal partial class OrderItemDto; ``` **Generated DTOs:** @@ -88,39 +96,6 @@ namespace MyProject } ``` -## Controlling DTO Generation with Empty Partial Classes - -To ensure DTOs are generated in the correct location, you must declare empty partial class definitions: - -```csharp -public class MyService -{ - public void GetOrders(IQueryable query) - { - var result = query - .SelectExpr(o => new - { - o.Id, - Items = o.OrderItems.SelectExpr(i => new - { - i.ProductName, - }), - }); - } - - // Empty partial class definitions to control DTO generation location - internal partial class OrderDto; - internal partial class OrderItemDto; -} -``` - -**Why is this necessary?** - -The source generator determines where to generate DTOs based on where the empty partial class is declared. Without these declarations: -- The generator might place DTOs in the wrong namespace -- DTO generation might fail -- The generated code might not compile - ## Multiple Nesting Levels You can nest `SelectExpr` calls multiple levels deep: @@ -159,7 +134,7 @@ var result = query .SelectExpr(x => new { x.Id, - // Reusable DTO + // Reusable DTO - generated in your namespace Items = x.Items.SelectExpr(i => new { i.Id, @@ -210,59 +185,13 @@ internal partial class ItemDtoArray; * You don't need to reference the nested DTO type * You prefer simpler, less verbose code -## Important Notes - -### Beta Feature Warning - -This feature is currently in **beta**. While it works correctly, the API and behavior may change in future versions. Please report any issues on GitHub. - -### .NET 9+ Recommended - -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. - -If you must use this feature on older .NET versions: -* Test thoroughly -* Watch for type inference errors -* Consider upgrading to .NET 9+ if possible - -### Compilation Requirements - -When using nested `SelectExpr`: -1. Always declare empty partial class definitions for all explicit DTO types -2. Place the partial class definitions in the same scope as the `SelectExpr` call -3. Use the correct access modifier (`public`, `internal`, etc.) - -```csharp -// Good: DTOs declared in the same class -public class MyService -{ - public void Query() { - /* SelectExpr here */ - } - - internal partial class EntityDto; - internal partial class ItemDto; -} - -// Bad: DTOs not declared -public class MyService -{ - public void Query() { - /* SelectExpr here - may fail to compile */ - } -} -``` - ## Comparison | Feature | Regular Select | Nested SelectExpr | |---------|---------------|-------------------| | DTO Location | `LinqraftGenerated_HASH` namespace | Your namespace | -| Reusability | Not reusable | Reusable | -| Partial Class Support | Limited | Full support | +| Reusability | No | Yes | | Declaration Required | No | Yes (empty partial class) | -| Verbosity | Low | Medium | -| Type Safety | High | High | | .NET Version | Any | .NET 9+ recommended | ## See Also From 782d2db85d7cbc2b0d0a259a26f0e8e5aa3d827c Mon Sep 17 00:00:00 2001 From: Arika Ishinami Date: Mon, 8 Dec 2025 00:29:48 +0900 Subject: [PATCH 3/4] upd --- docs/library/nested-selectexpr.md | 43 +++++-------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/docs/library/nested-selectexpr.md b/docs/library/nested-selectexpr.md index 24409d12..85a0d4fe 100644 --- a/docs/library/nested-selectexpr.md +++ b/docs/library/nested-selectexpr.md @@ -1,6 +1,7 @@ # Nested SelectExpr (Beta) -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. +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. ## Important Notes @@ -10,14 +11,11 @@ This feature is currently in **beta** (available since v0.6.2). While it works c ### .NET 9+ Recommended -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. +This feature is **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. -If you must use this feature on older .NET versions: -* Test thoroughly -* Watch for type inference errors -* Consider upgrading to .NET 9+ if possible - -### Required: Empty Partial Class Declarations +### Empty Partial Class Declarations Required To ensure DTOs are generated in the correct location, you **must** declare empty partial class definitions for all explicit DTO types: @@ -50,11 +48,6 @@ The source generator determines where to generate DTOs based on where the empty - DTO generation might fail - The generated code might not compile -**Requirements:** -1. Always declare empty partial class definitions for all explicit DTO types -2. Place the partial class definitions in the same scope as the `SelectExpr` call -3. Use the correct access modifier (`public`, `internal`, etc.) - ## Basic Usage ```csharp @@ -148,30 +141,6 @@ internal partial class ItemDto; // No need to declare SubItemDto - it's auto-generated ``` -## Collection Types - -Nested `SelectExpr` works with various collection types: - -```csharp -var result = query - .SelectExpr(x => new - { - // IEnumerable (default) - ItemsEnumerable = x.Items.SelectExpr(i => new { i.Id }), - - // List - ItemsList = x.Items.SelectExpr(i => new { i.Id }).ToList(), - - // Array - ItemsArray = x.Items.SelectExpr(i => new { i.Id }).ToArray(), - }); - -internal partial class EntityDto; -internal partial class ItemDtoEnumerable; -internal partial class ItemDtoList; -internal partial class ItemDtoArray; -``` - ## When to Use Nested SelectExpr **Use nested `SelectExpr` when:** From 224d24f9c4125ec16d08205837c7d2198b72595f Mon Sep 17 00:00:00 2001 From: arika Date: Mon, 8 Dec 2025 07:58:08 +0900 Subject: [PATCH 4/4] Fix GitHub Issue link in nested-selectexpr.md --- docs/library/nested-selectexpr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/library/nested-selectexpr.md b/docs/library/nested-selectexpr.md index 85a0d4fe..63967f99 100644 --- a/docs/library/nested-selectexpr.md +++ b/docs/library/nested-selectexpr.md @@ -13,7 +13,7 @@ This feature is currently in **beta** (available since v0.6.2). While it works c This feature is **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. +See [GitHub Issue #211](https://github.com/arika0093/linqraft/issues/211) for details. ### Empty Partial Class Declarations Required