Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 5, 2025

When SelectExpr is used inside another SelectExpr, the generator was incorrectly producing a separate interceptor and DTO class for the inner call. The inner SelectExpr should only be converted to a regular Select by the outer one—no separate interceptor needed.

// Inner SelectExpr was generating its own interceptor + LinqraftGenerated_HASH.ItemsDto
query.SelectExpr<Entity, EntityDto>(x => new {
    Items = x.Items.SelectExpr<Item, ItemDto>(i => new { i.Id })
});

Changes

  • SelectExprGenerator.cs: Added IsNestedInsideAnotherSelectExpr to skip interceptor generation for nested SelectExpr invocations
  • SelectExprInfoExplicitDto.cs: When generating DTOs for nested structures, use the explicit type name from inner SelectExpr<TIn, TResult> rather than auto-generating a hash-based name; skip hash-namespace placement for these explicit DTOs

Result

  • Single interceptor generated (outer only)
  • Explicit DTO types (ItemDto) placed in user namespace
  • Auto-generated DTOs (from regular Select) still use hash namespace
  • No orphan DTO classes in LinqraftGenerated_* namespaces
Original prompt

This section details on the original issue you should resolve

<issue_title>bug: In nested SelectExpr, interceptors for the inner SelectExpr are generated incorrectly.</issue_title>
<issue_description>### Minimal Reproducible Example

The test case of Issue207_NestedSelectExprTest as it is.

Generated Output

namespace Linqraft
{
    file static partial class GeneratedExpression
    {
        /// <summary>
        /// generated select expression method NestedEntity207Dto (explicit) <br/>
        /// at Issue207_NestedSelectExprTest.cs(74,14)
        /// </summary>
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "/kj/2iWhKnhvxrTVTWp3ao4JAABJc3N1ZTIwN19OZXN0ZWRTZWxlY3RFeHByVGVzdC5jcw==")]
        public static IQueryable<TResult> SelectExpr_5C648E5B_3F501B38<TIn, TResult>(
            this IQueryable<TIn> query, Func<TIn, object> selector)
        {
            var matchedQuery = query as object as IQueryable<global::Linqraft.Tests.Issue207_NestedSelectExprTest.NestedEntity207>;
            var converted = matchedQuery.Select(x => new global::Linqraft.Tests.NestedEntity207Dto
            {
                Id = x.Id,
                Name = x.Name,
                Items = x.Items
                    .Select(i => new global::Linqraft.Tests.NestedItem207Dto
                    {
                        Id = i.Id,
                        Title = i.Title,
                        SubItem = i.SubItems
                            .Select(si => new global::Linqraft.Tests.LinqraftGenerated_0361ED9F.SubItemDto
                            {
                                Id = si.Id,
                                Value = si.Value
                            })
                    })
            });
            return converted as object as IQueryable<TResult>;
        }

        /// <summary>
        /// generated select expression method NestedItem207Dto (explicit) <br/>
        /// at Issue207_NestedSelectExprTest.cs(79,33)
        /// </summary>
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "/kj/2iWhKnhvxrTVTWp3ao0KAABJc3N1ZTIwN19OZXN0ZWRTZWxlY3RFeHByVGVzdC5jcw==")]
        public static IEnumerable<TResult> SelectExpr_8678544C_B426BEC2<TIn, TResult>(
            this IEnumerable<TIn> query, Func<TIn, object> selector)
        {
            var matchedQuery = query as object as IEnumerable<global::Linqraft.Tests.Issue207_NestedSelectExprTest.NestedItem207>;
            var converted = matchedQuery.Select(i => new global::Linqraft.Tests.NestedItem207Dto
            {
                Id = i.Id,
                Title = i.Title,
                SubItem = i.SubItems
                    .Select(si => new global::Linqraft.Tests.LinqraftGenerated_0361ED9F.SubItemDto
                    {
                        Id = si.Id,
                        Value = si.Value
                    })
            });
            return converted as object as IEnumerable<TResult>;
        }

    }
}
namespace Linqraft.Tests
{
    /// <summary>
    /// Test data classes for the nested SelectExpr test
    /// </summary>
    /// <remarks>
    /// From: <c>NestedEntity207</c>
    /// </remarks>
    public partial class NestedEntity207Dto
    {
        /// <remarks>
        /// From: <c>NestedEntity207.Id</c>
        /// </remarks>
        public required int Id { get; set; }
        /// <remarks>
        /// From: <c>NestedEntity207.Name</c>
        /// </remarks>
        public required string Name { get; set; }
        /// <remarks>
        /// From: <c>NestedEntity207.Items.SelectExpr&lt;NestedItem207,NestedItem207Dto&gt;(i=&gt;new{i.Id,i.Title,SubItem=i.SubItems.Select(...),})</c>
        /// </remarks>
        public required global::System.Collections.Generic.IEnumerable<Linqraft.Tests.NestedItem207Dto> Items { get; set; }
    }

    /// <remarks>
    /// From: <c>NestedItem207</c>
    /// </remarks>
    public partial class NestedItem207Dto
    {
        /// <remarks>
        /// From: <c>NestedItem207.Id</c>
        /// </remarks>
        public required int Id { get; set; }
        /// <remarks>
        /// From: <c>NestedItem207.Title</c>
        /// </remarks>
        public required string Title { get; set; }
        /// <remarks>
        /// From: <c>NestedItem207.SubItems.Select(...)</c>
        /// </remarks>
        public required global::System.Collections.Generic.IEnumerable<Linqraft.Tests.LinqraftGenerated_0361ED9F.SubItemDto> SubItem { get; set; }
    }

}
namespace Linqraft.Tests.LinqraftGenerated_0361ED9F
{
    /// <remarks>
    /// From: <c>NestedSubItem207</c>
    /// </remarks>
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    [Linqraft.LinqraftAutoGeneratedDtoAttribute]
    public partial class SubItemDto
    {
        /// <remarks>
        /// From: <c>NestedSubItem207.Id</c>
        /// </remarks>
        public required int Id { get; set; }
        ...

</details>

- Fixes arika0093/Linqraft#212

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for you](https://github.com/arika0093/Linqraft/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.

@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI changed the title [WIP] Fix interceptor generation for nested SelectExpr Fix nested SelectExpr interceptor generation Dec 5, 2025
Copilot AI requested a review from arika0093 December 5, 2025 14:29
@arika0093 arika0093 marked this pull request as ready for review December 5, 2025 14:53
@arika0093 arika0093 merged commit 8a370f8 into main Dec 5, 2025
7 checks passed
@arika0093 arika0093 deleted the copilot/fix-nested-selectexpr-interceptors branch December 5, 2025 14:57
arika0093 added a commit that referenced this pull request Dec 5, 2025
* Initial plan

* Fix: Skip nested SelectExpr interceptor generation and generate DTOs with correct names

Co-authored-by: arika0093 <[email protected]>

* Fix: Address code review - move global:: prefix removal before class name extraction

Co-authored-by: arika0093 <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: arika0093 <[email protected]>
(cherry picked from commit 8a370f8)
@arika0093 arika0093 mentioned this pull request Dec 5, 2025
arika0093 added a commit that referenced this pull request Dec 5, 2025
* chore: add permissions for changelog generation

(cherry picked from commit 46e133a)

* chore: update changelog for release

(cherry picked from commit b6a6f17)

* Fix type references in README for DTOs

(cherry picked from commit 405d76b)

* feat: add changelog generation step to release workflow

(cherry picked from commit dd72ff4)

* docs: add comparison section with other mapping libraries in README

(cherry picked from commit 4441364)

* docs: enhance README with detailed explanation of Linqraft's DTO generation feature and comparison with other libraries

(cherry picked from commit c7523ff)

* fix: correct spelling of "dependencies" in README files

(cherry picked from commit 11ac12c)

* docs: update README and nuget documentation to include new features for automatic DTO generation and calculated fields

(cherry picked from commit 44f3bec)

* docs: refine README overview to clarify Linqraft's features and eliminate redundancy

(cherry picked from commit f272837)

* Add AutoMapper, Mapperly, Mapster, and Facet to benchmark (#209)

* Initial plan

* Add AutoMapper, Mapperly, Mapster, and Facet to benchmark

Co-authored-by: arika0093 <[email protected]>

* Fix Mapperly warnings by ignoring unmapped source members

Co-authored-by: arika0093 <[email protected]>

* Fix Facet benchmark to use standard NestedFacets pattern for proper DTO mapping

Co-authored-by: arika0093 <[email protected]>

* refactor: update benchmark descriptions and set baseline for Linqraft Auto-Generated DTO

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: arika0093 <[email protected]>
Co-authored-by: Arika Ishinami <[email protected]>
(cherry picked from commit 0916634)

* Fix nested SelectExpr generation - convert inner SelectExpr to Select (#208)

* Initial plan

* Add support for nested SelectExpr - convert inner SelectExpr to Select

Co-authored-by: arika0093 <[email protected]>

* Refactor: Extract shared helper for Select expression generation

Co-authored-by: arika0093 <[email protected]>

* Address PR feedback: unify methods, use record class for LINQ info, extract explicit DTO type from syntax

Co-authored-by: arika0093 <[email protected]>

* Address PR feedback: rename test file, use SelectExpr with explicit DTO types, verify nested namespace, update record syntax, simplify code

Co-authored-by: arika0093 <[email protected]>

* Improve null safety: add explicit null coalescing for propertyType

Co-authored-by: arika0093 <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: arika0093 <[email protected]>
(cherry picked from commit 348f441)

* docs: update FAQ section in README to clarify Linqraft's compatibility with LINQ providers

(cherry picked from commit 24db040)

* fix: add playground CSS file to .gitignore

(cherry picked from commit 9c9740c)

* refactor: streamline template creation and remove unused methods

(cherry picked from commit 2d08f8e)

* chore: remove unused tailwind CSS file from playground

(cherry picked from commit 73e74c0)

* docs: update FAQ to clarify Linqraft's compatibility with LINQ providers

(cherry picked from commit 9773b53)

* chore: update CI workflows for .NET build and test with multi-platform support

(cherry picked from commit d3fbfc7)

* chore: Update target frameworks and add build error suppression (#210)

* fix: add SuppressTfmSupportBuildErrors property to Directory.Build.props

* I207 test only run .NET 9 or later temporary

* fix: remove .NET 6.0 from target frameworks in CI workflow

* fix: correct property name in NestedSelectExpr test for clarity

(cherry picked from commit c1bb6cf)

* docs: clarify usage of Linqraft for generating shared DTOs from OpenAPI Schema

(cherry picked from commit 400daf8)

* docs: add collapsible section for available MSBuild properties in README

(cherry picked from commit b9fdff6)

* docs: add clarification on .csproj package reference for Linqraft installation

(cherry picked from commit d67e521)

* docs: improve clarity in reverse conversion explanation in README

(cherry picked from commit 0add9bf)

* Fix nested SelectExpr interceptor generation (#213)

* Initial plan

* Fix: Skip nested SelectExpr interceptor generation and generate DTOs with correct names

Co-authored-by: arika0093 <[email protected]>

* Fix: Address code review - move global:: prefix removal before class name extraction

Co-authored-by: arika0093 <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: arika0093 <[email protected]>
(cherry picked from commit 8a370f8)

* docs: clarify target frameworks setup for Windows in CI workflow

(cherry picked from commit 1cfba41)

---------

Co-authored-by: arika0093 <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: arika0093 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: In nested SelectExpr, interceptors for the inner SelectExpr are generated incorrectly.

2 participants