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..a84c5b52a8 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,57 @@ 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 => + { + 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 + }) + .ToList(); + + if (ReferenceEquals(compilation, _compilation)) { - classSymbols.Add(x); + var entryPointType = compilation.GetEntryPoint(CancellationToken.None)?.ContainingType; + if (entryPointType is not null) + { + compilationClassSymbols.Add(entryPointType); + } } - }); - return classSymbols; + compilationClassSymbols?.ForEach(x => + { + if (x is not null) + { + classSymbols.Add(x); + } + }); + } + + return classSymbols + .Distinct(SymbolEqualityComparer.Default) + .ToList(); + } + + /// + /// 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 dependencyGraph = project.Solution.GetProjectDependencyGraph(); + return dependencyGraph.GetProjectsThatThisProjectTransitivelyDependsOn(project.Id) + .Select(project.Solution.GetProject) + .OfType(); } ///