EF Core Tasks NativeAOT/precompiled-query generation fails for genuinely multi-targeted projects ($(TargetFramework) empty in _EFGenerateFilesBeforePublish)
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
## Summary
`Microsoft.EntityFrameworkCore.Tasks`' NativeAOT/precompiled-query generation
(`_EFGenerateFilesBeforePublish` / `_EFGenerateFiles` / `OptimizeDbContext` in
`Microsoft.EntityFrameworkCore.Tasks.targets`) fails for a project that
declares more than one TFM in ``, even when publishing for
a single, explicitly selected framework (`dotnet publish --framework
net11.0`).
Normal restore and build/publish (without `PublishAot`/the Tasks package's
generation step) work correctly for the same project. The failure is
specific to the EF Tasks generation path.
This is **distinct from #38951**. That issue is about `MSBuildWorkspace`
losing a custom `$(Configuration)` value when `dotnet ef dbcontext optimize`
reopens the project. This issue occurs earlier and through a different
mechanism: a plain MSBuild ``
re-invocation inside the Tasks targets executes with `$(TargetFramework)`
already empty, before `MSBuildWorkspace` is ever involved.
## Repro
**Repro.csproj**
```xml
Exe
net10.0;net11.0
enable
enable
true
$(InterceptorsNamespaces);Microsoft.EntityFrameworkCore.GeneratedInterceptors
all
runtime; build; native; contentfiles; analyzers; buildtransitive; compile
all
runtime; build; native; contentfiles; analyzers; buildtransitive
```
**Program.cs**
```csharp
using Microsoft.EntityFrameworkCore;
using var db = new BlogContext();
db.Database.EnsureCreated();
var count = db.Blogs.Count();
Console.WriteLine($"Blogs: {count}");
public class Blog
{
public int Id { get; set; }
public string Url { get; set; } = "";
}
public class BlogContext : DbContext
{
public DbSet Blogs => Set();
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite("Data Source=repro.db");
}
```
**Commands**
```
dotnet restore -p:TargetFrameworks=net11.0
dotnet publish --configuration Release --framework net11.0 --runtime osx-arm64 --no-restore -o out
```
**EF Tasks version tested**: `Microsoft.EntityFrameworkCore.Tasks` `11.0.0-rc.1.26425.128` (matching EF Core / EF Core Design / EF Core Sqlite at the same version). SDK: 11.0.100-rc.1.26425.128 on macOS (osx-arm64). Not tested against other EF11 preview/RC builds; the relevant targets-file logic looked unchanged across the ones inspected.
## Observed failure
A plain `dotnet build`/`dotnet publish` of the same project (without
`PublishAot`, or with the `Tasks` package removed) restores and builds
successfully for both `net10.0` and `net11.0` individually. The failure
appears specifically once `Microsoft.EntityFrameworkCore.Tasks`' NativeAOT
generation path runs:
```
Optimizing DbContext...
.../microsoft.entityframeworkcore.tasks/11.0.0-rc.1.26425.128/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.targets(105,5): error : Compilation failed with errors:
.../Program.cs(1,17): error CS0234: The type or namespace name 'EntityFrameworkCore' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)
.../Program.cs(14,28): error CS0246: The type or namespace name 'DbContext' could not be found (are you missing a using directive or an assembly reference?)
.../Program.cs(16,12): error CS0246: The type or namespace name 'DbSet<>' could not be found (are you missing a using directive or an assembly reference?)
.../Program.cs(17,43): error CS0246: The type or namespace name 'DbContextOptionsBuilder' could not be found (are you missing a using directive or an assembly reference?)
```
i.e. inside the Tasks-driven re-compilation, the project appears to have
**no package references at all** — not "wrong TFM's references", but none.
## Root cause (confirmed via `-v:diag` MSBuild log; not speculative beyond this)
`Microsoft.EntityFrameworkCore.Tasks.targets` (`buildTransitive/Microsoft.EntityFrameworkCore.Tasks.targets`):
```xml
<_EFInnerBuildProperties>Configuration=$(Configuration);Platform=$(Platform);PublishAot=false;_EFGenerationStage=$(_EFGenerationStage)
<_EFInnerBuildProperties Condition="'$(PublishAot)'=='true' Or '$(_EFPublishAot)'=='true'">$(_EFInnerBuildProperties);RuntimeIdentifier=;SelfContained=false
```
A `-v:diag` build log shows the `` task invocation with exactly these Global Properties (no `TargetFramework`):
```
Task "MSBuild" (TaskId:103)
Task Parameter:
Properties=
Configuration=Release
Platform=AnyCPU
PublishAot=false
_EFGenerationStage=publish
RuntimeIdentifier=
SelfContained=false (TaskId:103)
Task Parameter:Targets=Build (TaskId:103)
Global Properties: (TaskId:103)
Configuration=Release (TaskId:103)
Platform=AnyCPU (TaskId:103)
PublishAot=false (TaskId:103)
_EFGenerationStage=publish (TaskId:103)
RuntimeIdentifier= (TaskId:103)
SelfContained=false (TaskId:103)
```
Searching the entire re-invoked sub-build's log region (the project instance
that executes `Targets="Build"` for this call) for `Set Property:
TargetFramework=` finds **no occurrence at all**. Instead, the SDK's
cross-targeting logic computes:
```
Added Item(s):
_TargetFramework=
net10.0
net11.0
```
i.e. the re-invoked build evaluates the project in the **outer,
no-single-TFM-selected cross-targeting orchestrator shape** rather than a
scoped per-TFM inner build. Because `$(TargetFramework)` is empty throughout
that project instance's evaluation, the `Condition="'$(TargetFramework)' ==
'net11.0'"` `PackageReference` `ItemGroup`s never activate (item-group
conditions are evaluated once, at project load, using whatever global
properties the project instance was given) — so the sub-build compiles with
no EF Core/other package references at all, producing the `CS0234`/`CS0246`
errors above.
(The `[...::TargetFramework=net11.0]` suffix MSBuild appends to the error
text comes from the *entry-point*/outer logging context, not the actual
`$(TargetFramework)` state inside the failing sub-build — it does not
indicate the sub-build was actually scoped to net11.0.)
## Diagnostic experiment: does simply adding `TargetFramework=$(TargetFramework)` fix it?
**No.** I patched a local copy of the installed `Microsoft.EntityFrameworkCore.Tasks.targets` to change:
```xml
<_EFInnerBuildProperties>Configuration=$(Configuration);Platform=$(Platform);PublishAot=false;_EFGenerationStage=$(_EFGenerationStage)
```
to:
```xml
<_EFInnerBuildProperties>Configuration=$(Configuration);Platform=$(Platform);PublishAot=false;_EFGenerationStage=$(_EFGenerationStage);TargetFramework=$(TargetFramework)
```
and re-ran the identical repro. **The failure was unchanged** — same
`CS0234`/`CS0246` errors. A second `-v:diag` log showed why: at the point
`_EFInnerBuildProperties` is evaluated (inside `_EFGenerateFilesBeforePublish`,
which hooks in via `AfterTargets="GetCopyToPublishDirectoryItems"`
/`BeforeTargets="GeneratePublishDependencyFile"`), `$(TargetFramework)` is
**already empty in the entry-point project instance itself** — my added
`TargetFramework=$(TargetFramework)` simply evaluated to
`TargetFramework=` (empty) and changed nothing. So the defect isn't only
that the nested `` call drops an otherwise-correct
`$(TargetFramework)` value — `$(TargetFramework)` isn't bound to a single
value in the project instance these hook points execute in, for a
multi-targeted project. I do not have a confirmed fix; I'm reporting the
observed behavior and the boundary of what I verified, not a proposed patch.
## Related but distinct
#38951 — `dotnet ef dbcontext optimize --precompile-queries` doesn't pass
`$(Configuration)` to `MSBuildWorkspace`, breaking non-default build
configurations. That's a different mechanism (a C#-API `MSBuildWorkspace`
project reopen inside the `OptimizeDbContext` task itself) that only matters
*after* the project has a resolved `$(TargetFramework)`/`$(Configuration)` —
this issue happens earlier, in the plain-MSBuild-task re-invocation, and
affects a genuinely multi-targeted project shape regardless of whether
`$(Configuration)` is a standard or custom value.
Contributor guide
Assessment
This issue has not been assessed yet.