From 51eeb4014aa6aa343e0cd1dc4e1fdc4f3305264f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:21:06 +0000 Subject: [PATCH 1/3] Initial plan From ed52b24f2ba8999138a4fe1c22ea79134e62c704 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:32:17 +0000 Subject: [PATCH 2/3] Fix GetAllClassSymbolsAsync to discover types in referenced projects Co-authored-by: haileymck <111816896+haileymck@users.noreply.github.com> --- .../Services/CodeService.cs | 89 +++++++++++++++---- 1 file changed, 73 insertions(+), 16 deletions(-) diff --git a/src/dotnet-scaffolding/Microsoft.DotNet.Scaffolding.Roslyn/Services/CodeService.cs b/src/dotnet-scaffolding/Microsoft.DotNet.Scaffolding.Roslyn/Services/CodeService.cs index f29d621263..1a3c44bbc2 100644 --- a/src/dotnet-scaffolding/Microsoft.DotNet.Scaffolding.Roslyn/Services/CodeService.cs +++ b/src/dotnet-scaffolding/Microsoft.DotNet.Scaffolding.Roslyn/Services/CodeService.cs @@ -273,6 +273,7 @@ public async Task> GetAllClassSymbolsAsync() { EnsureInitialized(); List classSymbols = []; + var compilations = new List(); if (_compilation is null) { // Explicitly use only the MSBuildWorkspace for compilation — the fallback @@ -284,6 +285,18 @@ public async Task> GetAllClassSymbolsAsync() if (project is not null) { _compilation = await project.GetCompilationAsync(); + + // Also gather compilations for any referenced projects (e.g. a DbContext/model + // class library referenced via a ProjectReference) so that types declared there + // are discoverable, not just types in the main scaffolding project. + foreach (var referencedProject in GetTransitiveProjectReferences(project)) + { + var referencedCompilation = await referencedProject.GetCompilationAsync(); + if (referencedCompilation is not null) + { + compilations.Add(referencedCompilation); + } + } } // Fallback: MSBuildWorkspace can fail to open projects when the SDK resolver @@ -296,27 +309,71 @@ public async Task> GetAllClassSymbolsAsync() } } - List? compilationClassSymbols = _compilation?.SyntaxTrees.SelectMany(tree => + if (_compilation is not null) { - var model = _compilation.GetSemanticModel(tree); - return tree.GetRoot().DescendantNodes().OfType() - .Select(classSyntax => model.GetDeclaredSymbol(classSyntax)) - .Where(classSymbol => classSymbol is not null && - !classSymbol.MetadataName.StartsWith("<")); //if the metadata name starts with < it is a compiler generated class - }) - .Append(_compilation.GetEntryPoint(CancellationToken.None)?.ContainingType) - .Distinct(SymbolEqualityComparer.Default) - .ToList(); - - compilationClassSymbols?.ForEach(x => + compilations.Insert(0, _compilation); + } + + foreach (var compilation in compilations) { - if (x is not null) + var compilationClassSymbols = compilation.SyntaxTrees.SelectMany(tree => { - classSymbols.Add(x); + var model = compilation.GetSemanticModel(tree); + return tree.GetRoot().DescendantNodes().OfType() + .Select(classSyntax => model.GetDeclaredSymbol(classSyntax)) + .Where(classSymbol => classSymbol is not null && + !classSymbol.MetadataName.StartsWith("<")); //if the metadata name starts with < it is a compiler generated class + }) + .Append(compilation.GetEntryPoint(CancellationToken.None)?.ContainingType) + .ToList(); + + compilationClassSymbols?.ForEach(x => + { + if (x is not null) + { + classSymbols.Add(x); + } + }); + } + + return classSymbols + .Distinct(SymbolEqualityComparer.Default) + .ToList(); + } + + /// + /// Recursively collects all Roslyn instances referenced (directly or + /// transitively) by via ProjectReference (e.g. a class library + /// containing a DbContext and/or model classes referenced from the main scaffolding project). + /// + private static IEnumerable GetTransitiveProjectReferences(Project project) + { + var visited = new HashSet(); + var toVisit = new Queue(); + foreach (var referencedProject in project.Solution.GetProjectDependencyGraph().GetProjectsThatThisProjectDirectlyDependsOn(project.Id) + .Select(project.Solution.GetProject) + .OfType()) + { + toVisit.Enqueue(referencedProject); + } + + while (toVisit.Count > 0) + { + var current = toVisit.Dequeue(); + if (!visited.Add(current.Id)) + { + continue; } - }); - return classSymbols; + yield return current; + + foreach (var referencedProject in current.Solution.GetProjectDependencyGraph().GetProjectsThatThisProjectDirectlyDependsOn(current.Id) + .Select(current.Solution.GetProject) + .OfType()) + { + toVisit.Enqueue(referencedProject); + } + } } /// From cf16d1ed2c28343c7052cdea0ffb647ef1fa47a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:34:06 +0000 Subject: [PATCH 3/3] Address code review feedback: simplify dependency traversal and entry-point handling Co-authored-by: haileymck <111816896+haileymck@users.noreply.github.com> --- .../Services/CodeService.cs | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/dotnet-scaffolding/Microsoft.DotNet.Scaffolding.Roslyn/Services/CodeService.cs b/src/dotnet-scaffolding/Microsoft.DotNet.Scaffolding.Roslyn/Services/CodeService.cs index 1a3c44bbc2..a84c5b52a8 100644 --- a/src/dotnet-scaffolding/Microsoft.DotNet.Scaffolding.Roslyn/Services/CodeService.cs +++ b/src/dotnet-scaffolding/Microsoft.DotNet.Scaffolding.Roslyn/Services/CodeService.cs @@ -324,9 +324,17 @@ public async Task> GetAllClassSymbolsAsync() .Where(classSymbol => classSymbol is not null && !classSymbol.MetadataName.StartsWith("<")); //if the metadata name starts with < it is a compiler generated class }) - .Append(compilation.GetEntryPoint(CancellationToken.None)?.ContainingType) .ToList(); + if (ReferenceEquals(compilation, _compilation)) + { + var entryPointType = compilation.GetEntryPoint(CancellationToken.None)?.ContainingType; + if (entryPointType is not null) + { + compilationClassSymbols.Add(entryPointType); + } + } + compilationClassSymbols?.ForEach(x => { if (x is not null) @@ -342,38 +350,16 @@ public async Task> GetAllClassSymbolsAsync() } /// - /// Recursively collects all Roslyn instances referenced (directly or + /// Collects all Roslyn instances referenced (directly or /// transitively) by via ProjectReference (e.g. a class library /// containing a DbContext and/or model classes referenced from the main scaffolding project). /// private static IEnumerable GetTransitiveProjectReferences(Project project) { - var visited = new HashSet(); - var toVisit = new Queue(); - foreach (var referencedProject in project.Solution.GetProjectDependencyGraph().GetProjectsThatThisProjectDirectlyDependsOn(project.Id) + var dependencyGraph = project.Solution.GetProjectDependencyGraph(); + return dependencyGraph.GetProjectsThatThisProjectTransitivelyDependsOn(project.Id) .Select(project.Solution.GetProject) - .OfType()) - { - toVisit.Enqueue(referencedProject); - } - - while (toVisit.Count > 0) - { - var current = toVisit.Dequeue(); - if (!visited.Add(current.Id)) - { - continue; - } - - yield return current; - - foreach (var referencedProject in current.Solution.GetProjectDependencyGraph().GetProjectsThatThisProjectDirectlyDependsOn(current.Id) - .Select(current.Solution.GetProject) - .OfType()) - { - toVisit.Enqueue(referencedProject); - } - } + .OfType(); } ///