Expose `LambdaExpression` support for Compiled Queries
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
## Summary
I'd like to call `EF.CompileAsyncQuery()` with an expression with an arbitrary number of parameters, which otherwise conforms to the `Expression>` format expected by that function, but we don't know the number of parameters upfront.
## Context
We have shared `Sdk` types which can be returned in several places, so we have a `ISdkMapper` type like this:
```
public interface ISdkMapper {
public Expression> GetMapExpression();
...
}
```
and implementers are often internally based on lambdas with more parameters (eg `bool isUserAdmin`), and we closure the values in when constructing the map expression, eg
```
public class MyAdminBasedMapper : ISdkMapper {
private Expression> _mapExpr =
(TEntity entity, bool isUserAdmin) => new TSdk {
...
};
public Expression> GetMapExpression() =>
entity => _mapExpr.Invoke(entity, IsUserAdmin());
}
```
and we can use these mappers easily in any normal queries (using `LinqKit` due to the `Invoke()`, not shown)
```
var sdkResult = await _dbSet
.Where(...)
.Select(_mapper.GetMapExpression())
.SingleOrDefaultAsync();
```
## Problem
I'm looking into supporting this pattern in **Compiled Queries**, where of course I'd need to lift any parameters required by the query into the lambda we pass to `EF.CompileAsyncQuery`, but I also need to do the same for the `ISdkMapper` logic, except I can't do that in a generalized way because mapper expressions can have different numbers of underlying parameters, and `CompileAsyncQuery` requires a precisely-defined `Expression>`. Therefore I'd need to write a lot of boilerplate helper functions -- for each `ISdkMapper` -- which take in a precisely-defined expression with query logic, and return a new precisely-defined expression with the mapper's parameters + logic appended.
## Solution
I noticed that `CompileAsyncQuery` uses the parameter count-agnostic `LambdaExpression` under the hood, and I proved out a solution to the above by
1. Update `ISdkMapper` to provide a `LambdaExpression` of its internal map expression.
2. Do a few straightforward Expression surgeries to append that onto the expression with the query logic.
3. Pass that to the **EF-internal class** `new CompiledAsyncTaskQuery(lambdaExpression)`
4. Abuse **Reflection** to call the `protected ExecuteCore(TContext context, object?[] parameters)` function
and everything seems to work cleanly. But I'm hesitant to rely on internal logic which could change, not to mention the ugly Reflection hack.
Would you consider making the `LambdaExpression`- and `object[]`-based Compiled Query support publicly available, for example
```
Func> CompileAsyncQuery(LambdaExpression expression) {}
```
(probably with different name to avoid ambiguous overload)
Contributor guide
Assessment
This issue has not been assessed yet.