Provide additional parameters to support provision of execution context to Load and LoadAsync
Hi Aleksey
Statler here - you may remember me from such posts as
- https://github.com/DevExpress/DevExtreme.AspNet.Data/issues/367
- https://github.com/DevExpress/DevExtreme.AspNet.Data/issues/277
So, just when I thought I had every base covered, I have come across a new use case that I need to manage. I am hoping you can help me out here.
My case deals successfully with automapper projection by:
- Registering CustomAccessors automatically from the automapper config (https://github.com/DevExpress/DevExtreme.AspNet.Data/issues/367)
- Getting the IDs of all results from the Devexpress.AspNet.data expression compilation query
- Querying the base type for all ids in (2), and using ProjectTo
This all works really well for ProjectTo(Mapconfig)
Where I have come unstuck is the overload of ProjectTo(Mapconfig, MapParameter) (https://docs.automapper.org/en/stable/Queryable-Extensions.html#parameterization)
This enables the injection of runtime variables into the projection and is a case I can't see a way to resolve as it is not possible to provide context in the CustomAccessors or CustomFilters. Would you be open to modifying the signatures for these methods to support the provision of context via an additional parameter to DataSourceLoader.LoadAsync / DataSourceLoader.Load
I have just put together a PR (https://github.com/DevExpress/DevExtreme.AspNet.Data/pull/589) that shows what I am thinking. You would have different thoughts on how it should be done, but the solution works and shows the principle.
The main thing this PR does is:
- Add additional property to DataSourceLoadOptions - an object called RuntimeResolutionContext. The idea is that this can be passed into the LoadAsync and becomes available in the RegisterBinaryExpressionCompiler registered methods
- Added new RegisterBinaryExpressionCompilerWithContext for methods that need the context. This is added with a different signature so backwards compatibility isn't affected. The additional information available to registered methods is actually the FilterExpressionCompiler that calls the customfuncs. The runtimecontext is available through this reference.
- The reason for exposing FilterExpressionCompiler (and a new method CompileNonCustomBinary) is so that when writing customfuncs, we can leverage the heavy lifting done by CompileBinary regarding multiple different operators, rather than having to do all this by hand. This necessitated changing some accessibility on the class.
As I started out, I expect you may want to refactor the exact way this is done, but do you see the utility and would you be willing to implement in the root library?
Here is an example of it in use (real world). This method:
- Gets the userid of the current session through the RuntimeResolutionContext that was passed in to the options in the Load call
- Uses the newly exposed method on FilterExpressionCompiler to access CompileBinary and resolve the Expression for querying a subcollection of the object
- Joins this query to an additional operator that also limits by the current userId
- Returns the new Expression
Subsequently the operator is projected to a DTO, which is achieved using the same object passed in the runtimecontext - but that's part of my other library.
CustomFilterCompilers.RegisterBinaryExpressionCompilerWithContext((info, rtContext) =>
{
if (info.DataItemExpression.Type == typeof(Notification))
{
if (info.AccessorText == "DateDismissed")
{
dynamic dynObj = new DynamicWrapper(rtContext.RuntimeResolutionContext);
if (int.TryParse(dynObj.UserId.ToString(), out int _ui))
{
var pBaseExp = info.DataItemExpression as ParameterExpression;
var pBaseProperty = Expression.PropertyOrField(pBaseExp, "NotificationTos");
var _p = Expression.Parameter(typeof(NotificationTo), "nTo");
var baseResult = rtContext.CompileNonCustomBinary(_p, GetParametersAsList(info));
var predicate = PredicateBuilder.New(Expression.Lambda<Func<NotificationTo, bool>>(baseResult, _p));
predicate.And((p) => p.UserId == _ui);
var result = Expression.Call(
typeof(Enumerable), "Any", new[] { _p.Type },
pBaseProperty, predicate);
var ex22 = Expression.Lambda(result, pBaseExp) as Expression<Func<Notification, bool>>;
return CompileWhereExpression(info, ex22).Body;
}
}
}
return null;
});
`
OK - so I have expanded the scope of this somewhat. I have finally gotten around to implementing a non-intrusive integration of Automapper. PR is at https://github.com/DevExpress/DevExtreme.AspNet.Data/pull/589.
The PR is mainly to start a discussion about what (if any) you want in the main library.
Anyone wanting an automapper-ready implementation in the meantime can pull https://github.com/statler/DevExtreme.AspNet.Data
I will have a nuget up shortly
OK - nuget package is available.
Available as nuget at https://www.nuget.org/packages/DXAutomap.AspNet.Data/1.12.0
NuGet\Install-Package DXAutomap.AspNet.Data -Version 1.12.0