dotnet / dotnet/efcore

Precompiled queries: lift objects to make them common across query interceptors

Open
#33,515 2 comments 1 reaction 0 assignees View on GitHub
area-perf area-precompiled-queries
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

For each precompiled query, we generate the following to generate the query's executor:

```c#
private static object Query1_GenerateExecutor(DbContext dbContext, QueryContext queryContext)
{
var relationalModel = dbContext.Model.GetRelationalModel();
var relationalTypeMappingSource = dbContext.GetService();
var materializerLiftableConstantContext = new RelationalMaterializerLiftableConstantContext(
dbContext.GetService(),
dbContext.GetService(),
dbContext.GetService());
var dependencies = ((MaterializerLiftableConstantContext)materializerLiftableConstantContext).Dependencies;
var relationalDependencies = materializerLiftableConstantContext.RelationalDependencies;
var relationalCommandCache = ...;
var emptyValueBuffer = ValueBuffer.Empty;
var blogEntityType = dependencies.Model.FindEntityType("Microsoft.EntityFrameworkCore.Query.PrecompiledQueryRelationalTestBase+Blog");
var key = blogEntityType.FindPrimaryKey();
var emptySnapshot = Snapshot.Empty;
var blogEntityType0 = ((RuntimeEntityType)(blogEntityType));
return (QueryContext queryContext) => SingleQueryingEnumerable.Create(((RelationalQueryContext)(queryContext)), (IReadOnlyDictionary parameters) => relationalCommandCache.GetRelationalCommandTemplate(parameters), null, (QueryContext queryContext, DbDataReader dataReader, ResultContext resultContext, SingleQueryResultCoordinator resultCoordinator) => ...
```

These "lifted" variables are originally constants referenced in the shaper expression tree; many of these are common across multiple queries, e.g. many queries will have a lookup for the Blog entity type (and most of the other things in the above code).

We could lift these values further, storing them as static members on the interceptor class; they'd be initially null, and would be populated when the first relevant GenerateExecutor method is invoked. This would both improve startup time through less lookups in the model, and reduce the amount of generated code.

Note: this raises the question of how we split generated interceptor code across files. The current generates a file for each original user source file that contains an interceptor, and types within are file-scoped, so as not to be externally visible. Since resources such as the above common objects can only be shared within the same file, we may want to put the generated interceptors for the entire user project into a single file, for maximum reuse (but it would be a pretty big file).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.