Added support for query filter factories to "lazy load" query filter expressions #19522
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Query filters are a powerful tool in Entity Framework, but limited as they are restricted to being defined ahead of being used.
Suppose the following scenario
A business application with 200 tables. Extreme performance is not imperative as this does not need to scale to millions of users, but correct business security, per user, is paramount.
Currently, as the data context is generated, the logic behind query filters for each of those tables must be created before any of those tables are even queried. Additionally, the logic is static, sealed in a single
LambdaExpression
.If we needed different logic per tenants we can either use a new derivative of our
DbContext
or we can use a newIModelCacheKeyFactory
implementation (or perhaps some usage ofICompiledQueryCacheKeyGenerator
), and configure the query filters depending on the current user/request.But again, the current set-up forces us to determine the query filters for all 200 tables ahead of time.
A better set-up would be to only construct the query filters if a request requires it, essentially lazy load them.
EF Core has a strong opinion about sealing the model once it's built - it becomes read-only. I have attempted to adhere to this opinion in this PR, albeit with the singular exception of internally swapping a query filter factory in the entity type annotations for the actual
LambdaExpression
once the factory has been invoked for the first time. This maintains the idea of defining what tables have query filters ahead of time, and keeps the factory invoked only once per model-cache.The result is the ability to do this (when combined with a per-request
IModelCacheKeyFactory
):Given we can inject anything we like into the
DbContext
, we can check details about the request, perhaps some user information stored elsewhere, anything we like to construct our query filter, but at no expense if the filter is not used in that request.