diff --git a/README.md b/README.md index f696996..4f7f575 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,11 @@ Expression Mapping also supports writing queries against the mapped objects. Tak We can write LINQ expressions against the DTO collections. ```csharp -ICollection requests = await context.Request.GetItemsAsync(mapper, r => r.Id > 0 && r.Id < 3, null, new List, IIncludableQueryable>>>() { item => item.Include(s => s.Assignee) }); -ICollection users = await context.User.GetItemsAsync(mapper, u => u.Id > 0 && u.Id < 4, q => q.OrderBy(u => u.Name)); +ICollection requests = [.. context.Request.GetQuery1(mapper, r => r.Id > 0 && r.Id < 3, null, [r => r.Assignee])]; +ICollection users = [.. context.User.GetQuery1(mapper, u => u.Id > 0 && u.Id < 4, q => q.OrderBy(u => u.Name))]; int count = await context.Request.Query(mapper, q => q.Count(r => r.Id > 1)); ``` -The methods below map the DTO query expresions to the equivalent data query expressions. The call to IMapper.Map converts the data query results back to the DTO (or model) object types. +The methods below map the DTO query expresions to the equivalent data query expressions. The call to IMapper.Map converts the data query results back to the DTO (or model) object types. The call to IMapper.ProjectTo converts the data query to a DTO (or model) query. ```csharp static class Extensions { @@ -69,27 +69,62 @@ The methods below map the DTO query expresions to the equivalent data query expr return mapper.Map(mappedQueryFunc(query)); } - internal static async Task> GetItemsAsync(this IQueryable query, IMapper mapper, + //This version compiles the queryable expression. + internal static IQueryable GetQuery1(this IQueryable query, + IMapper mapper, Expression> filter = null, - Expression, IQueryable>> queryFunc = null, - ICollection, IIncludableQueryable>>> includeProperties = null) + Expression, IQueryable>> queryableExpression = null, + IEnumerable>> expansions = null) { - //Map the expressions - Expression> f = mapper.MapExpression>>(filter); - Func, IQueryable> mappedQueryFunc = mapper.MapExpression, IQueryable>>>(queryFunc)?.Compile(); - ICollection, IIncludableQueryable>>> includes = mapper.MapIncludesList, IIncludableQueryable>>>(includeProperties); + Func, IQueryable> mappedQueryDelegate = mapper.MapExpression, IQueryable>>>(queryableExpression)?.Compile(); + if (filter != null) + query = query.Where(mapper.MapExpression>>(filter)); - if (f != null) - query = query.Where(f); + return mappedQueryDelegate != null + ? mapper.ProjectTo(mappedQueryDelegate(query), null, GetExpansions()) + : mapper.ProjectTo(query, null, GetExpansions()); - if (includes != null) - query = includes.Select(i => i.Compile()).Aggregate(query, (list, next) => query = next(query)); + Expression>[] GetExpansions() => expansions?.ToArray() ?? []; + } - //Call the store - ICollection result = mappedQueryFunc != null ? await mappedQueryFunc(query).ToListAsync() : await query.ToListAsync(); + //This version updates IQueryable.Expression with the mapped queryable expression parameter. + internal static IQueryable GetQuery2(this IQueryable query, + IMapper mapper, + Expression> filter = null, + Expression, IQueryable>> queryableExpression = null, + IEnumerable>> expansions = null) + { + Expression, IQueryable>> mappedQueryExpression = mapper.MapExpression, IQueryable>>>(queryableExpression); + if (filter != null) + query = query.Where(mapper.MapExpression>>(filter)); + + if (mappedQueryExpression != null) + { + var queryableExpressionBody = GetUnconvertedExpression(mappedQueryExpression.Body); + queryableExpressionBody = ReplaceParameter(queryableExpressionBody, mappedQueryExpression.Parameters[0], query.Expression); + query = query.Provider.CreateQuery(queryableExpressionBody); + } + + return mapper.ProjectTo(query, null, GetExpansions()); + + Expression>[] GetExpansions() => expansions?.ToArray() ?? []; + static Expression GetUnconvertedExpression(Expression expression) => expression.NodeType switch + { + ExpressionType.Convert or ExpressionType.ConvertChecked or ExpressionType.TypeAs => GetUnconvertedExpression(((UnaryExpression)expression).Operand), + _ => expression, + }; + Expression ReplaceParameter(Expression expression, ParameterExpression source, Expression target) => new ParameterReplacer(source, target).Visit(expression); + } + + class ParameterReplacer(ParameterExpression source, Expression target) : ExpressionVisitor + { + private readonly ParameterExpression _source = source; + private readonly Expression _target = target; - //Map and return the data - return mapper.Map, IEnumerable>(result).ToList(); + protected override Expression VisitParameter(ParameterExpression node) + { + return node == _source ? _target : base.VisitParameter(node); + } } } ```