Skip to content

EF Core Tasks NativeAOT/precompiled-query generation fails for genuinely multi-targeted projects ($(TargetFramework) empty in _EFGenerateFilesBeforePublish) #38955

Description

@ncipollina

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 <TargetFrameworks>, 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 <MSBuild Targets="Build" Properties="...">
re-invocation inside the Tasks targets executes with $(TargetFramework)
already empty, before MSBuildWorkspace is ever involved.

Repro

Repro.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net10.0;net11.0</TargetFrameworks>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
    <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.EntityFrameworkCore.GeneratedInterceptors</InterceptorsNamespaces>
  </PropertyGroup>
  <ItemGroup Condition="'$(TargetFramework)'=='net11.0'">
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="11.0.0-rc.1.26425.128" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="11.0.0-rc.1.26425.128">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive; compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tasks" Version="11.0.0-rc.1.26425.128">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
  <ItemGroup Condition="'$(TargetFramework)'=='net10.0'">
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.12" />
  </ItemGroup>
</Project>

Program.cs

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<Blog> Blogs => Set<Blog>();
    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):

<Target Name="_EFGenerateFilesBeforePublish"
        AfterTargets="GetCopyToPublishDirectoryItems"
        BeforeTargets="GeneratePublishDependencyFile"
        Condition="'$(_EFGenerationStage)'=='' And '$(DesignTimeBuild)' != 'True' And ('$(EFScaffoldModelStage)'=='publish' Or '$(EFPrecompileQueriesStage)'=='publish' Or '$(_EFPublishAot)'=='true' Or '$(PublishAot)'=='true')">
  <MSBuild Projects="$(MSBuildProjectFullPath)"
           Targets="_EFGenerateFiles"
           BuildInParallel="$(BuildInParallel)"
           ContinueOnError="$(ContinueOnError)"
           Properties="Configuration=$(Configuration);Platform=$(Platform);_EFGenerationStage=publish" />
</Target>

<Target Name="_EFGenerateFiles">
  <PropertyGroup>
    <_EFInnerBuildProperties>Configuration=$(Configuration);Platform=$(Platform);PublishAot=false;_EFGenerationStage=$(_EFGenerationStage)</_EFInnerBuildProperties>
    <_EFInnerBuildProperties Condition="'$(PublishAot)'=='true' Or '$(_EFPublishAot)'=='true'">$(_EFInnerBuildProperties);RuntimeIdentifier=;SelfContained=false</_EFInnerBuildProperties>
  </PropertyGroup>

  <MSBuild Projects="$(MSBuildProjectFullPath)" Targets="Build" BuildInParallel="$(BuildInParallel)" ContinueOnError="$(ContinueOnError)"
           Condition="'$(PublishAot)'=='true' Or '$(_EFPublishAot)'=='true'"
           Properties="$(_EFInnerBuildProperties)" />

  <MSBuild Projects="$(MSBuildProjectFullPath)" Targets="OptimizeDbContext" BuildInParallel="$(BuildInParallel)" ContinueOnError="$(ContinueOnError)"
           Properties="$(_EFInnerBuildProperties)" />

  <CallTarget Targets="Build" />
</Target>

A -v:diag build log shows the <MSBuild Targets="Build" Properties="$(_EFInnerBuildProperties)"> 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 ItemGroups 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:

<_EFInnerBuildProperties>Configuration=$(Configuration);Platform=$(Platform);PublishAot=false;_EFGenerationStage=$(_EFGenerationStage)</_EFInnerBuildProperties>

to:

<_EFInnerBuildProperties>Configuration=$(Configuration);Platform=$(Platform);PublishAot=false;_EFGenerationStage=$(_EFGenerationStage);TargetFramework=$(TargetFramework)</_EFInnerBuildProperties>

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 <MSBuild Properties="..."> 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

#38951dotnet 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions