Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Tracked by #34446 ...
roji opened this issue Apr 11, 2024 · 2 comments
Open
Tracked by #34446 ...

Comments

@roji
Copy link
Member

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

@cincuranet
Copy link
Contributor

I don't think a big file is an issue. It's generated code anyway. We just need to make sure to not go over whatever is supported by Roslyn as an input (or i.e. number of tokens).

@roji
Copy link
Member Author

roji commented Apr 11, 2024

Yeah, upper limits are the main part where I'm not too sure...

Another approach here is to dump file-scoped types, and just have our own special namespace for generated code; at that point resources can be used across multiple files.

@roji roji mentioned this issue Aug 15, 2024
24 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants