Description
openedon Apr 11, 2024
For each precompiled query, we generate the following to generate the query's executor:
private static object Query1_GenerateExecutor(DbContext dbContext, QueryContext queryContext)
{
var relationalModel = dbContext.Model.GetRelationalModel();
var relationalTypeMappingSource = dbContext.GetService<IRelationalTypeMappingSource>();
var materializerLiftableConstantContext = new RelationalMaterializerLiftableConstantContext(
dbContext.GetService<ShapedQueryCompilingExpressionVisitorDependencies>(),
dbContext.GetService<RelationalShapedQueryCompilingExpressionVisitorDependencies>(),
dbContext.GetService<RelationalCommandBuilderDependencies>());
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<string, object> 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).