Skip to content

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

Open

Description

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).

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

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions