Skip to content

Conversation

@AndriySvyryd
Copy link
Member

@AndriySvyryd AndriySvyryd commented May 17, 2024

Also adds publish integration for precompiled queries and combines it with compiled model generation.

For the compiled model and precompiled queries to be generated when publishing with NativeAOT the only action needed is to reference Microsoft.EntityFrameworkCore.Tasks from all projects containing a DbContext or a query.

For solutions where specifying the startup project is necessary, EFStartupProject should be set.

EFOptimizeContext can be set to true to enable code generation outside of NativeAOT.

EFScaffoldModelStage and EFPrecompileQueriesStage can be set to either publish or build to control at what stage will the code be generated. Any other value will disable the corresponding generation (in case the code is generated manually using dotnet ef dbcontext optimize)

If there's more than one context and DbContextName is not set, then the compiled model will be generated for all of them.

EFTargetNamespace and EFOutputDir can be used to further fine-tune the generation.

Fixes #33103
Fixes #33558

@AndriySvyryd AndriySvyryd requested a review from roji May 17, 2024 22:49
@AndriySvyryd AndriySvyryd force-pushed the Issue33103 branch 2 times, most recently from 881f8e9 to b840cc6 Compare May 21, 2024 01:07
@AndriySvyryd AndriySvyryd marked this pull request as ready for review May 21, 2024 01:07
@AndriySvyryd AndriySvyryd force-pushed the Issue33103 branch 4 times, most recently from 620a708 to 0eaf687 Compare May 24, 2024 23:17
ContinueOnError="$(ContinueOnError)"
Condition="'$(PublishAot)'=='true'"
Properties="Configuration=$(Configuration);Platform=$(Platform);EFOptimizeContext=false;PublishAot=false" />
Properties="Configuration=$(Configuration);Platform=$(Platform);PublishAot=false;_EFGenerationStage=$(_EFGenerationStage)" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to always pass Configuration and Platform? Should we pass other properties?

DependsOnTargets="_EFProcessGeneratedFiles"
Condition="'$(EFOptimizeContext)'=='true'"
Condition="'$(_EFGenerationStage)'==''"
Inputs="$(MSBuildAllProjects);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way of determining whether CoreCompile is going to be skipped other than duplicating its inputs and outputs?


MSBuildLocator.RegisterDefaults();
// TODO: pass through properties
var workspace = MSBuildWorkspace.Create();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What properties should we pass through from the MsBuild invocation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, I'm not sure... I'm assuming it's reasonable to expect the project to be buildable as-is (without properties), but the user may have tweaked their project with various properties...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if it's buildable the code might contain #ifdefs that significantly alter the logic based on DefineConstants.

@AndriySvyryd AndriySvyryd force-pushed the Issue33103 branch 4 times, most recently from 3d181c1 to b067140 Compare May 31, 2024 21:03
@roji
Copy link
Member

roji commented May 31, 2024

Just to say that this is on my list to review... Will work up to it in the next few days.

@AndriySvyryd
Copy link
Member Author

Just to say that this is on my list to review... Will work up to it in the next few days.

Ok. Just note that the longer you take to review the more code will need to be reviewed 😉

@roji
Copy link
Member

roji commented May 31, 2024

That's a pretty effective way to speed up a code review :)

Copy link
Member

@roji roji left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, the MSBuild Tasks part is a total 🐑 🇮🇹 though :) Is there someone who knows this part a bit (@ajcvickers)?

Just to confirm: after this PR, the precompiled query code will use the unsafe accessors generated into the compiled model, rather than generate its own (via the code I wrote in LinqToCSharpSyntaxTranslator), right? That would be a good thing, just making sure.


MSBuildLocator.RegisterDefaults();
// TODO: pass through properties
var workspace = MSBuildWorkspace.Create();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, I'm not sure... I'm assuming it's reasonable to expect the project to be buildable as-is (without properties), but the user may have tweaked their project with various properties...

"""
);

var name = Uniquifier.Uniquify(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also uniquify only at the very top, i.e. before actually dumping the files to disk, rather than dealing with it at each point where we create a ScaffoldedFile... In that sense the Name would only be the "desired" name (or a hint).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uniquifier needs to be before the suffix (e.g. .g) and before the extension. If we do it at the top then we'd either need to parse the name or make ScaffoldedFile to contain all the building parts of the name separately.

Another problem is that even if the filenames are uniquified, the class names could still be clashing. I haven't solved this yet, but it would need to be done at this level anyway.

@rainersigwald
Copy link
Member

I would find it very helpful if there were a Markdown doc describing what this is doing, ideally with some Mermaid diagrams. I was going to try to document it myself but I'm getting lost in the details. Can you share a binlog of a project building/publishing with this stuff turned on that I can look at?

@AndriySvyryd
Copy link
Member Author

Just to confirm: after this PR, the precompiled query code will use the unsafe accessors generated into the compiled model, rather than generate its own (via the code I wrote in LinqToCSharpSyntaxTranslator), right? That would be a good thing, just making sure.

Yes, and I added an Assert in case any unsafe accessor is generated by LinqToCSharpSyntaxTranslator

@AndriySvyryd
Copy link
Member Author

@rainersigwald There are basically two flows:
For Build

  1. _EFReadGeneratedFilesList and _EFProcessGeneratedFiles add the files generated previously to Compile to make incremental build work
  2. If compilation needs to be performed again then _EFPrepareForCompile removes the previously generated files from Compile as they are probably outdated. And if EFOptimizeContext is true it also calls _EFRegisterProjectToOptimize on the startup project to mark the current project for optimization. Startup project could also be the same as the project containing the derived DbContext, but if it's not the tooling needs the compiled startup assembly to be able to generate code for any other project.
  3. If any project was marked then _EFGenerateFilesAfterBuild in the startup project calls _EFGenerateFiles which in turn calls OptimizeDbContext on the marked projects
  4. OptimizeDbContext generated optimized/NativeAOT-compatible code and writes the list of generated files for _EFReadGeneratedFilesList to read when recompiling

For Publish

  1. If PublishAOT is true _EFPrepareDependenciesForPublishAOT in the startup project invokes _EFPrepareForPublish on all dependencies to mark them for optimization even if they don't set EFOptimizeContext to true. Otherwise _EFPrepareForPublish runs on the projects before Publish
  2. If any project was marked then _EFGenerateFilesBeforePublish in the startup project calls _EFGenerateFiles and the rest is similar to the Build flow

@rainersigwald
Copy link
Member

rainersigwald commented Jun 5, 2024

  • Startup project could also be the same as the project containing the derived DbContext, but if it's not the tooling needs the compiled startup assembly to be able to generate code for any other project.

Am I understanding correctly that this means you must first build the whole solution (or a meaningful subset), then generate code, then rebuild all of the assemblies that had code generated?

@AndriySvyryd
Copy link
Member Author

Am I understanding correctly that this means you must first build the whole solution (or a meaningful subset), then generate code, then rebuild all of the assemblies that had code generated?

Yes. And it's even worse when PublishAOT is true as the produced assembly cannot be used for code generation. So, in that case we build the solution with PublishAOT set to true, then if code needs to be generated we rebuild the solution with PublishAOT set to false, generate code then rebuild the solution with PublishAOT set to true

@roji
Copy link
Member

roji commented Jun 6, 2024

BTW @AndriySvyryd consider copy-pasting some version of the explanation above into the targets themselves for future us.

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

Labels

None yet

Projects

None yet

4 participants