diff --git a/Signum.Engine/DynamicQuery/AutoDynamicQuery.cs b/Signum.Engine/DynamicQuery/AutoDynamicQuery.cs index 8d49e9b31e..5049f3cc61 100644 --- a/Signum.Engine/DynamicQuery/AutoDynamicQuery.cs +++ b/Signum.Engine/DynamicQuery/AutoDynamicQuery.cs @@ -27,6 +27,8 @@ public override ResultTable ExecuteQuery(QueryRequest request) var result = query.TryPaginate(request.Pagination, request.SystemTime); + result = result.SelectManySubQueries(); + if (inMemoryOrders != null) { result = result.OrderBy(inMemoryOrders); @@ -44,7 +46,9 @@ public override async Task ExecuteQueryAsync(QueryRequest request, var result = await query.TryPaginateAsync(request.Pagination, request.SystemTime, token); - if(inMemoryOrders != null) + result = result.SelectManySubQueries(); + + if (inMemoryOrders != null) { result = result.OrderBy(inMemoryOrders); } @@ -92,29 +96,48 @@ private DQueryable GetDQueryable(QueryRequest request, out List? inMem if (!request.Columns.Where(c => c is _EntityColumn).Any()) request.Columns.Insert(0, new _EntityColumn(EntityColumnFactory().BuildColumnDescription(), QueryName)); - var query = Query - .ToDQueryable(GetQueryDescription()) - .SelectMany(request.Multiplications()) - .Where(request.Filters); - - if (request.Pagination is Pagination.All) + if (request.MultiplicationsInSubQueries()) { - var allColumns = request.Columns.Select(a => a.Token) - .Concat(request.Orders.Select(a => a.Token)) - .Distinct() - .Select(t => new Column(t, null)).ToList(); + var columnAndOrderTokens = request.Columns.Select(a => a.Token) + .Concat(request.Orders.Select(a => a.Token)) + .Distinct() + .ToHashSet(); - inMemoryOrders = request.Orders.ToList(); + inMemoryOrders = request.Orders; + + var query = Query + .ToDQueryable(GetQueryDescription()) + .Where(request.Filters) + .SelectWithSubQueries(columnAndOrderTokens); - return query.Select(allColumns); + return query; } else { - inMemoryOrders = null; + var query = Query + .ToDQueryable(GetQueryDescription()) + .SelectMany(request.Multiplications()) + .Where(request.Filters); + + if (request.Pagination is Pagination.All) + { + var allColumns = request.Columns.Select(a => a.Token) + .Concat(request.Orders.Select(a => a.Token)) + .Distinct() + .Select(t => new Column(t, null)).ToList(); + + inMemoryOrders = request.Orders.ToList(); - return query - .OrderBy(request.Orders) - .Select(request.Columns); + return query.Select(allColumns); + } + else + { + inMemoryOrders = null; + + return query + .OrderBy(request.Orders) + .Select(request.Columns); + } } } diff --git a/Signum.Engine/DynamicQuery/DynamicQuery.cs b/Signum.Engine/DynamicQuery/DQueryable.cs similarity index 64% rename from Signum.Engine/DynamicQuery/DynamicQuery.cs rename to Signum.Engine/DynamicQuery/DQueryable.cs index 0d22dcd8b0..359f0d6c22 100644 --- a/Signum.Engine/DynamicQuery/DynamicQuery.cs +++ b/Signum.Engine/DynamicQuery/DQueryable.cs @@ -1,1328 +1,1101 @@ -using Signum.Entities.DynamicQuery; -using Signum.Utilities.Reflection; -using Signum.Engine.Linq; -using Signum.Entities.Basics; -using System.Diagnostics.CodeAnalysis; -using System.Collections; - -namespace Signum.Engine.DynamicQuery; - -public class DynamicQueryBucket -{ - public ResetLazy Core { get; private set; } - - public object QueryName { get; private set; } - - public Implementations EntityImplementations { get; private set; } - - public DynamicQueryBucket(object queryName, Func lazyQueryCore, Implementations entityImplementations) - { - if (lazyQueryCore == null) - throw new ArgumentNullException(nameof(lazyQueryCore)); - - this.QueryName = queryName ?? throw new ArgumentNullException(nameof(queryName)); - this.EntityImplementations = entityImplementations; - - this.Core = new ResetLazy(() => - { - var core = lazyQueryCore(); - - core.QueryName = QueryName; - - core.StaticColumns.Where(sc => sc.IsEntity).SingleEx(() => "Entity column on {0}".FormatWith(QueryUtils.GetKey(QueryName))); - - core.EntityColumnFactory().Implementations = entityImplementations; - - var errors = core.StaticColumns.Where(sc => sc.Implementations == null && sc.Type.CleanType().IsIEntity()); - - if (errors.Any()) - throw new InvalidOperationException("Column {0} of query '{1}' do(es) not have implementations defined. Use Column extension method".FormatWith(errors.CommaAnd(a => $"'{a.Name}'"), QueryUtils.GetKey(QueryName))); - - return core; - }); - } - - - public QueryDescription GetDescription() - { - return Core.Value.GetQueryDescription(); - } -} - - -public interface IDynamicQueryCore -{ - object QueryName { get; set; } - ColumnDescriptionFactory[] StaticColumns { get; } - Expression? Expression { get; } - - ColumnDescriptionFactory EntityColumnFactory(); - QueryDescription GetQueryDescription(); - - ResultTable ExecuteQuery(QueryRequest request); - Task ExecuteQueryAsync(QueryRequest request, CancellationToken cancellationToken); - ResultTable ExecuteQueryGroup(QueryRequest request); - Task ExecuteQueryGroupAsync(QueryRequest request, CancellationToken cancellationToken); - object? ExecuteQueryValue(QueryValueRequest request); - Task ExecuteQueryValueAsync(QueryValueRequest request, CancellationToken cancellationToken); - Lite? ExecuteUniqueEntity(UniqueEntityRequest request); - Task?> ExecuteUniqueEntityAsync(UniqueEntityRequest request, CancellationToken cancellationToken); - - IQueryable> GetEntitiesLite(QueryEntitiesRequest request); - IQueryable GetEntitiesFull(QueryEntitiesRequest request); -} - - -public static class DynamicQueryCore -{ - public static AutoDynamicQueryCore Auto(IQueryable query) - { - return new AutoDynamicQueryCore(query); - } - - public static ManualDynamicQueryCore Manual(Func>> execute) - { - return new ManualDynamicQueryCore(execute); - } - - internal static IDynamicQueryCore FromSelectorUntyped(Expression> expression) - where T : Entity - { - var eType = expression.Parameters.SingleEx().Type; - var tType = expression.Body.Type; - var typedSelector = Expression.Lambda(expression.Body, expression.Parameters); - - return giAutoPrivate.GetInvoker(eType, tType)(typedSelector); - } - - static readonly GenericInvoker> giAutoPrivate = - new(lambda => FromSelector((Expression>)lambda)); - public static AutoDynamicQueryCore FromSelector(Expression> selector) - where E : Entity - { - return new AutoDynamicQueryCore(Database.Query().Select(selector)); - } - - public static Dictionary? QueryMetadata(IQueryable query) - { - return MetadataVisitor.GatherMetadata(query.Expression); - } - -} - -public abstract class DynamicQueryCore : IDynamicQueryCore -{ - public object QueryName { get; set; } = null!; - - public ColumnDescriptionFactory[] StaticColumns { get; protected set; } = null!; - - public abstract ResultTable ExecuteQuery(QueryRequest request); - public abstract Task ExecuteQueryAsync(QueryRequest request, CancellationToken cancellationToken); - - public abstract ResultTable ExecuteQueryGroup(QueryRequest request); - public abstract Task ExecuteQueryGroupAsync(QueryRequest request, CancellationToken cancellationToken); - - public abstract object? ExecuteQueryValue(QueryValueRequest request); - public abstract Task ExecuteQueryValueAsync(QueryValueRequest request, CancellationToken cancellationToken); - - public abstract Lite? ExecuteUniqueEntity(UniqueEntityRequest request); - public abstract Task?> ExecuteUniqueEntityAsync(UniqueEntityRequest request, CancellationToken cancellationToken); - - public abstract IQueryable> GetEntitiesLite(QueryEntitiesRequest request); - public abstract IQueryable GetEntitiesFull(QueryEntitiesRequest request); - - - protected virtual ColumnDescriptionFactory[] InitializeColumns() - { - var result = MemberEntryFactory.GenerateList(MemberOptions.Properties | MemberOptions.Fields) - .Select((e, i) => new ColumnDescriptionFactory(i, e.MemberInfo, null)).ToArray(); - - return result; - } - - public DynamicQueryCore ColumnDisplayName(Expression> column, Enum messageValue) - { - return this.Column(column, c => c.OverrideDisplayName = () => messageValue.NiceToString()); - } - - public DynamicQueryCore ColumnDisplayName(Expression> column, Func messageValue) - { - return this.Column(column, c => c.OverrideDisplayName = messageValue); - } - - public DynamicQueryCore ColumnProperyRoutes(Expression> column, params PropertyRoute[] routes) - { - return this.Column(column, c => c.PropertyRoutes = routes); - } - - public DynamicQueryCore Column(Expression> column, Action change) - { - MemberInfo member = ReflectionTools.GetMemberInfo(column); - ColumnDescriptionFactory col = StaticColumns.SingleEx(a => a.Name == member.Name); - change(col); - - return this; - } - - public ColumnDescriptionFactory EntityColumnFactory() - { - return StaticColumns.Where(c => c.IsEntity).SingleEx(() => "Entity column on {0}".FormatWith(QueryUtils.GetKey(QueryName))); - } - - public virtual Expression? Expression - { - get { return null; } - } - - public QueryDescription GetQueryDescription() - { - var entity = EntityColumnFactory(); - string? allowed = entity.IsAllowed(); - if (allowed != null) - throw new InvalidOperationException( - "Not authorized to see Entity column on {0} because {1}".FormatWith(QueryUtils.GetKey(QueryName), allowed)); - - var columns = StaticColumns.Where(f => f.IsAllowed() == null).Select(f => f.BuildColumnDescription()).ToList(); - - return new QueryDescription(QueryName, columns); - } -} - -public interface IDynamicInfo -{ - BuildExpressionContext Context { get; } -} - -/// Unraleted with the content, only with the original anonymous type -public class DQueryable : IDynamicInfo -{ - public DQueryable(IQueryable query, BuildExpressionContext context) - { - this.Query = query; - this.Context = context; - } - - public IQueryable Query { get; private set; } - public BuildExpressionContext Context { get; private set; } -} - -/// Unraleted with the content, only with the original anonymous type -public class DQueryableCount : DEnumerable -{ - public DQueryableCount(IQueryable query, BuildExpressionContext context, int totalElements) : - base(query, context) - { - this.TotalElements = totalElements; - } - - public int TotalElements { get; private set; } -} - -/// Unraleted with the content, only with the original anonymous type -public class DEnumerable : IDynamicInfo -{ - public DEnumerable(IEnumerable collection, BuildExpressionContext context) - { - this.Collection = collection; - this.Context = context; - } - - public IEnumerable Collection { get; private set; } - public BuildExpressionContext Context { get; private set; } -} - -/// Unraleted with the content, only with the original anonymous type -public class DEnumerableCount : DEnumerable -{ - public DEnumerableCount(IEnumerable collection, BuildExpressionContext context, int? totalElements) : - base(collection, context) - { - this.TotalElements = totalElements; - } - - public int? TotalElements {get; private set;} -} - - -public static class DQueryable -{ - #region ToDQueryable - - public static DQueryable ToDQueryable(this IQueryable query, QueryDescription description) - { - ParameterExpression pe = Expression.Parameter(typeof(T)); - - var dic = description.Columns.ToDictionary( - cd => (QueryToken)new ColumnToken(cd, description.QueryName), - cd => new ExpressionBox(Expression.PropertyOrField(pe, cd.Name).BuildLiteNullifyUnwrapPrimaryKey(cd.PropertyRoutes!))); - - return new DQueryable(query, new BuildExpressionContext(typeof(T), pe, dic)); - } - - - public static Task> AllQueryOperationsAsync(this DQueryable query, QueryRequest request, CancellationToken token) - { - return query - .SelectMany(request.Multiplications()) - .Where(request.Filters) - .OrderBy(request.Orders) - .Select(request.Columns) - .TryPaginateAsync(request.Pagination, request.SystemTime, token); - } - - public static DEnumerableCount AllQueryOperations(this DQueryable query, QueryRequest request) - { - return query - .SelectMany(request.Multiplications()) - .Where(request.Filters) - .OrderBy(request.Orders) - .Select(request.Columns) - .TryPaginate(request.Pagination, request.SystemTime); - } - - #endregion - - #region Select - - public static IEnumerable SelectOne(this DEnumerable collection, QueryToken token) - { - var exp = Expression.Lambda(Expression.Convert(token.BuildExpression(collection.Context), typeof(object)), collection.Context.Parameter); - - return (IEnumerable)Untyped.Select(collection.Collection, exp.Compile()); - } - - public static IQueryable SelectOne(this DQueryable query, QueryToken token) - { - var exp = Expression.Lambda(Expression.Convert(token.BuildExpression(query.Context), typeof(object)), query.Context.Parameter); - - return (IQueryable)Untyped.Select(query.Query, exp); - } - - public static DQueryable Select(this DQueryable query, List columns) - { - return Select(query, new HashSet(columns.Select(c => c.Token))); - } - - public static DQueryable Select(this DQueryable query, HashSet columns) - { - var selector = SelectTupleConstructor(query.Context, columns, out BuildExpressionContext newContext); - - return new DQueryable(Untyped.Select(query.Query, selector), newContext); - } - - public static DEnumerable Select(this DEnumerable collection, List columns) - { - return Select(collection, new HashSet(columns.Select(c => c.Token))); - } - - public static DEnumerable Select(this DEnumerable collection, HashSet columns) - { - var selector = SelectTupleConstructor(collection.Context, columns, out BuildExpressionContext newContext); - - return new DEnumerable(Untyped.Select(collection.Collection, selector.Compile()), newContext); - } - - - static LambdaExpression SelectTupleConstructor(BuildExpressionContext context, HashSet tokens, out BuildExpressionContext newContext) - { - string str = tokens.Select(t => QueryUtils.CanColumn(t)).NotNull().ToString("\r\n"); - if (str == null) - throw new ApplicationException(str); - - List expressions = tokens.Select(t => t.BuildExpression(context)).ToList(); - Expression ctor = TupleReflection.TupleChainConstructor(expressions); - - var pe = Expression.Parameter(ctor.Type); - - newContext = new BuildExpressionContext( - ctor.Type, pe, - tokens.Select((t, i) => new - { - Token = t, - Expr = TupleReflection.TupleChainProperty(pe, i) - }).ToDictionary(t => t.Token!, t => new ExpressionBox(t.Expr))); - - return Expression.Lambda(ctor, context.Parameter); - } - - - - public static DEnumerable Concat(this DEnumerable collection, DEnumerable other) - { - if (collection.Context.ElementType != other.Context.ElementType) - throw new InvalidOperationException("Enumerable's TupleType does not match Other's one.\r\n Enumerable: {0}: \r\n Other: {1}".FormatWith( - collection.Context.ElementType.TypeName(), - other.Context.ElementType.TypeName())); - - return new DEnumerable(Untyped.Concat(collection.Collection, other.Collection, collection.Context.ElementType), collection.Context); - } - - public static DEnumerableCount Concat(this DEnumerableCount collection, DEnumerableCount other) - { - if (collection.Context.ElementType != other.Context.ElementType) - throw new InvalidOperationException("Enumerable's TupleType does not match Other's one.\r\n Enumerable: {0}: \r\n Other: {1}".FormatWith( - collection.Context.ElementType.TypeName(), - other.Context.ElementType.TypeName())); - - return new DEnumerableCount(Untyped.Concat(collection.Collection,other.Collection, collection.Context.ElementType), collection.Context, collection.TotalElements + other.TotalElements); - } - #endregion - - public static DEnumerable ToDEnumerable(this DQueryable query) - { - return new DEnumerable(Untyped.ToList(query.Query, query.Context.ElementType), query.Context); - } - - public static DEnumerable ToDEnumerable(this IEnumerable query, QueryDescription description) - { - ParameterExpression pe = Expression.Parameter(typeof(T)); - - var dic = description.Columns.ToDictionary( - cd => (QueryToken)new ColumnToken(cd, description.QueryName), - cd => new ExpressionBox(Expression.PropertyOrField(pe, cd.Name).BuildLiteNullifyUnwrapPrimaryKey(cd.PropertyRoutes!))); - - return new DEnumerable(query, new BuildExpressionContext(typeof(T), pe, dic)); - } - - public static DEnumerableCount WithCount(this DEnumerable result, int? totalElements) - { - return new DEnumerableCount(result.Collection, result.Context, totalElements); - } - - public static async Task> ToDEnumerableAsync(this DQueryable query, CancellationToken token) - { - var list = await Untyped.ToListAsync(query.Query, token, query.Context.ElementType); - return new DEnumerable(list, query.Context); - } - - #region SelectMany - public static DQueryable SelectMany(this DQueryable query, List elementTokens) - { - foreach (var cet in elementTokens) - { - query = query.SelectMany(cet); - } - - return query; - } - - static MethodInfo miSelectMany = ReflectionTools.GetMethodInfo(() => Database.Query().SelectMany(t => t.Namespace, (t, c) => t)).GetGenericMethodDefinition(); - static MethodInfo miDefaultIfEmptyE = ReflectionTools.GetMethodInfo(() => Database.Query().AsEnumerable().DefaultIfEmpty()).GetGenericMethodDefinition(); - - public static DQueryable SelectMany(this DQueryable query, CollectionElementToken cet) - { - var eptML = MListElementPropertyToken.AsMListEntityProperty(cet.Parent!); - - Type elementType = eptML != null ? - MListElementPropertyToken.MListElementType(eptML) : - cet.Parent!.Type.ElementType()!; - - var collectionSelector = Expression.Lambda(typeof(Func<,>).MakeGenericType(query.Context.ElementType, typeof(IEnumerable<>).MakeGenericType(elementType)), - Expression.Call(miDefaultIfEmptyE.MakeGenericMethod(elementType), - eptML != null ? MListElementPropertyToken.BuildMListElements(eptML, query.Context) : - cet.Parent!.BuildExpression(query.Context)), - query.Context.Parameter); - - var elementParameter = Expression.Parameter(elementType); - - var properties = query.Context.Replacements.Values.Select(box => box.RawExpression).And(elementParameter.BuildLite().Nullify()).ToList(); - - var ctor = TupleReflection.TupleChainConstructor(properties); - - var resultSelector = Expression.Lambda(ctor, query.Context.Parameter, elementParameter); - - var resultQuery = query.Query.Provider.CreateQuery(Expression.Call(null, miSelectMany.MakeGenericMethod(query.Context.ElementType, elementType, ctor.Type), - new Expression[] { query.Query.Expression, Expression.Quote(collectionSelector), Expression.Quote(resultSelector) })); - - var parameter = Expression.Parameter(ctor.Type); - - var newReplacements = query.Context.Replacements.Select((kvp, i) => new - { - Token = kvp.Key, - Expression = new ExpressionBox(TupleReflection.TupleChainProperty(parameter, i), - mlistElementRoute: kvp.Value.MListElementRoute) - }).ToDictionary(a => a.Token, a => a.Expression); - - newReplacements.Add(cet, - new ExpressionBox(TupleReflection.TupleChainProperty(parameter, query.Context.Replacements.Keys.Count), - mlistElementRoute: eptML != null ? cet.GetPropertyRoute() : null - )); - - var newContext = new BuildExpressionContext(ctor.Type, parameter, newReplacements); - - return new DQueryable(resultQuery, newContext); - } - - #endregion - - #region Where - - public static DQueryable Where(this DQueryable query, params Filter[] filters) - { - return Where(query, filters.NotNull().ToList()); - } - - public static DQueryable Where(this DQueryable query, List filters) - { - LambdaExpression? predicate = GetPredicateExpression(query.Context, filters); - if (predicate == null) - return query; - - return new DQueryable(Untyped.Where(query.Query, predicate), query.Context); - } - - public static DQueryable Where(this DQueryable query, Expression> filter) - { - return new DQueryable(Untyped.Where(query.Query, filter), query.Context); - } - - public static DEnumerable Where(this DEnumerable collection, params Filter[] filters) - { - return Where(collection, filters.NotNull().ToList()); - } - - public static DEnumerable Where(this DEnumerable collection, List filters) - { - LambdaExpression? where = GetPredicateExpression(collection.Context, filters); - if (where == null) - return collection; - - return new DEnumerable(Untyped.Where(collection.Collection, where.Compile()), collection.Context); - } - - static LambdaExpression? GetPredicateExpression(BuildExpressionContext context, List filters) - { - if (filters == null || filters.Count == 0) - return null; - - string str = filters - .SelectMany(f => f.GetFilterConditions()) - .Select(f => QueryUtils.CanFilter(f.Token)) - .NotNull() - .ToString("\r\n"); - - if (str == null) - throw new ApplicationException(str); - - Expression body = filters.Select(f => f.GetExpression(context)).AggregateAnd(); - - return Expression.Lambda(body, context.Parameter); - } - - #endregion - - #region OrderBy - - - - public static DQueryable OrderBy(this DQueryable query, List orders) - { - string str = orders.Select(f => QueryUtils.CanOrder(f.Token)).NotNull().ToString("\r\n"); - if (str == null) - throw new ApplicationException(str); - - var pairs = orders.Select(o => ( - lambda: QueryUtils.CreateOrderLambda(o.Token, query.Context), - orderType: o.OrderType - )).ToList(); - - return new DQueryable(Untyped.OrderBy(query.Query, pairs), query.Context); - } - - - public static DEnumerable OrderBy(this DEnumerable collection, List orders) - { - var pairs = orders.Select(o => ( - lambda: QueryUtils.CreateOrderLambda(o.Token, collection.Context), - orderType: o.OrderType - )).ToList(); - - - return new DEnumerable(Untyped.OrderBy(collection.Collection, pairs), collection.Context); - } - - public static DEnumerableCount OrderBy(this DEnumerableCount collection, List orders) - { - var pairs = orders.Select(o => ( - lambda: QueryUtils.CreateOrderLambda(o.Token, collection.Context), - orderType: o.OrderType - )).ToList(); - - return new DEnumerableCount(Untyped.OrderBy(collection.Collection, pairs), collection.Context, collection.TotalElements); - } - - #endregion - - #region Unique - - [return: MaybeNull] - public static T Unique(this IEnumerable collection, UniqueType uniqueType) - { - return uniqueType switch - { - UniqueType.First => collection.First(), - UniqueType.FirstOrDefault => collection.FirstOrDefault(), - UniqueType.Single => collection.SingleEx(), - UniqueType.SingleOrDefault => collection.SingleOrDefaultEx(), - UniqueType.Only => collection.Only(), - _ => throw new InvalidOperationException(), - }; - } - - //[return: MaybeNull] - public static Task UniqueAsync(this IQueryable collection, UniqueType uniqueType, CancellationToken token) - { - return uniqueType switch - { - UniqueType.First => collection.FirstAsync(token), - UniqueType.FirstOrDefault => collection.FirstOrDefaultAsync(token)!, - UniqueType.Single => collection.SingleAsync(token), - UniqueType.SingleOrDefault => collection.SingleOrDefaultAsync(token)!, - UniqueType.Only => collection.Take(2).ToListAsync(token).ContinueWith(l => l.Result.Only()!), - _ => throw new InvalidOperationException(), - }; - } - - #endregion - - #region TryTake - public static DQueryable TryTake(this DQueryable query, int? num) - { - if (num.HasValue) - return new DQueryable(Untyped.Take(query.Query, num.Value, query.Context.ElementType), query.Context); - return query; - } - - public static DEnumerable TryTake(this DEnumerable collection, int? num) - { - if (num.HasValue) - return new DEnumerable(Untyped.Take(collection.Collection, num.Value, collection.Context.ElementType), collection.Context); - return collection; - } - #endregion - - - #region TryPaginate - - public static async Task> TryPaginateAsync(this DQueryable query, Pagination pagination, SystemTime? systemTime, CancellationToken token) - { - if (pagination == null) - throw new ArgumentNullException(nameof(pagination)); - - var elemType = query.Context.ElementType; - - if (pagination is Pagination.All) - { - var allList = await Untyped.ToListAsync(query.Query, token, elemType); - - return new DEnumerableCount(allList, query.Context, allList.Count); - } - else if (pagination is Pagination.Firsts top) - { - var topList = await Untyped.ToListAsync(Untyped.Take(query.Query, top.TopElements, elemType), token, elemType); - - return new DEnumerableCount(topList, query.Context, null); - } - else if (pagination is Pagination.Paginate pag) - { - if (systemTime is SystemTime.Interval) //Results multipy due to Joins, not easy to change LINQ provider because joins are delayed - { - var q = Untyped.OrderAlsoByKeys(query.Query, elemType); - - var list = await Untyped.ToListAsync(query.Query /*q maybe?*/, token, elemType); - - var elements = list; - if (pag.CurrentPage != 1) - elements = Untyped.ToList(Untyped.Skip(elements, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType), elemType); - - elements = Untyped.ToList(Untyped.Take(elements, pag.ElementsPerPage, elemType), elemType); - - return new DEnumerableCount(elements, query.Context, list.Count); - } - else - { - var q = Untyped.OrderAlsoByKeys(query.Query, elemType); - - if (pag.CurrentPage != 1) - q = Untyped.Skip(q, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType); - - q = Untyped.Take(q, pag.ElementsPerPage, elemType); - - var listTask = await Untyped.ToListAsync(q, token, elemType); - var countTask = systemTime is SystemTime.Interval ? - (await Untyped.ToListAsync(query.Query, token, elemType)).Count : //Results multipy due to Joins, not easy to change LINQ provider because joins are delayed - await Untyped.CountAsync(query.Query, token, elemType); - - return new DEnumerableCount(listTask, query.Context, countTask); - } - } - - throw new InvalidOperationException("pagination type {0} not expexted".FormatWith(pagination.GetType().Name)); - } - - public static DEnumerableCount TryPaginate(this DQueryable query, Pagination pagination, SystemTime? systemTime) - { - if (pagination == null) - throw new ArgumentNullException(nameof(pagination)); - - var elemType = query.Context.ElementType; - - if (pagination is Pagination.All) - { - var allList = Untyped.ToList(query.Query, elemType); - - return new DEnumerableCount(allList, query.Context, allList.Count); - } - else if (pagination is Pagination.Firsts top) - { - var topList = Untyped.ToList(Untyped.Take(query.Query, top.TopElements, elemType), elemType); - - return new DEnumerableCount(topList, query.Context, null); - } - else if (pagination is Pagination.Paginate pag) - { - if(systemTime is SystemTime.Interval) //Results multipy due to Joins, not easy to change LINQ provider because joins are delayed - { - var q = Untyped.OrderAlsoByKeys(query.Query, elemType); - - var list = Untyped.ToList(query.Query /*q?*/, elemType); - - var elements = list; - if (pag.CurrentPage != 1) - elements = Untyped.ToList(Untyped.Skip(elements, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType), elemType); - - elements = Untyped.ToList(Untyped.Take(elements, pag.ElementsPerPage, elemType), elemType); - - return new DEnumerableCount(elements, query.Context, list.Count); - } - else - { - var q = Untyped.OrderAlsoByKeys(query.Query, elemType); - - if (pag.CurrentPage != 1) - q = Untyped.Skip(q, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType); - - q = Untyped.Take(q, pag.ElementsPerPage, elemType); - - var list = Untyped.ToList(q, elemType); - var count = list.Count < pag.ElementsPerPage ? pag.ElementsPerPage : - Untyped.Count(query.Query, elemType); - - return new DEnumerableCount(list, query.Context, count); - } - - - - } - - throw new InvalidOperationException("pagination type {0} not expexted".FormatWith(pagination.GetType().Name)); - } - - public static DEnumerableCount TryPaginate(this DEnumerable collection, Pagination pagination) - { - if (pagination == null) - throw new ArgumentNullException(nameof(pagination)); - - - var elemType = collection.Context.ElementType; - - if (pagination is Pagination.All) - { - var allList = Untyped.ToList(collection.Collection, elemType); - - return new DEnumerableCount(allList, collection.Context, allList.Count); - } - else if (pagination is Pagination.Firsts top) - { - var topList = Untyped.ToList(Untyped.Take(collection.Collection, top.TopElements, elemType), elemType); - - return new DEnumerableCount(topList, collection.Context, null); - } - else if (pagination is Pagination.Paginate pag) - { - int? totalElements = null; - - var q = collection.Collection; - if (pag.CurrentPage != 1) - q = Untyped.Skip(q, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType); - - q = Untyped.Take(q, pag.ElementsPerPage, elemType); - - var list = Untyped.ToList(q, elemType); - - if (list.Count < pag.ElementsPerPage && pag.CurrentPage == 1) - totalElements = list.Count; - - return new DEnumerableCount(list, collection.Context, totalElements ?? Untyped.Count(collection.Collection, elemType)); - } - - throw new InvalidOperationException("pagination type {0} not expexted".FormatWith(pagination.GetType().Name)); - } - - public static DEnumerableCount TryPaginate(this DEnumerableCount collection, Pagination pagination) - { - if (pagination == null) - throw new ArgumentNullException(nameof(pagination)); - - var elemType = collection.Context.ElementType; - - if (pagination is Pagination.All) - { - return new DEnumerableCount(collection.Collection, collection.Context, collection.TotalElements); - } - else if (pagination is Pagination.Firsts top) - { - var topList = Untyped.ToList(Untyped.Take(collection.Collection, top.TopElements, elemType), elemType); - - return new DEnumerableCount(topList, collection.Context, null); - } - else if (pagination is Pagination.Paginate pag) - { - var c = collection.Collection; - if (pag.CurrentPage != 1) - c = Untyped.Skip(c, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType); - - c = Untyped.Take(c, pag.ElementsPerPage, elemType); - - return new DEnumerableCount(c, collection.Context, collection.TotalElements); - } - - throw new InvalidOperationException("pagination type {0} not expexted".FormatWith(pagination.GetType().Name)); - } - - #endregion - - #region GroupBy - - static readonly GenericInvoker> giGroupByE = - new((col, ks, rs) => (IEnumerable)Enumerable.GroupBy((IEnumerable)col, (Func)ks, (Func, double>)rs)); - public static DEnumerable GroupBy(this DEnumerable collection, HashSet keyTokens, HashSet aggregateTokens) - { - var rootKeyTokens = GetRootKeyTokens(keyTokens); - - var redundantKeyTokens = keyTokens.Except(rootKeyTokens).ToHashSet(); - - var keySelector = KeySelector(collection.Context, rootKeyTokens); - - LambdaExpression resultSelector = ResultSelectSelectorAndContext(collection.Context, rootKeyTokens, redundantKeyTokens, aggregateTokens, keySelector.Body.Type, isQueryable: false, out BuildExpressionContext newContext); - - var resultCollection = giGroupByE.GetInvoker(collection.Context.ElementType, keySelector.Body.Type, resultSelector.Body.Type)(collection.Collection, keySelector.Compile(), resultSelector.Compile()); - - return new DEnumerable(resultCollection, newContext); - } - - static MethodInfo miGroupByQ = ReflectionTools.GetMethodInfo(() => Queryable.GroupBy((IQueryable)null!, (Expression>)null!, (Expression, double>>)null!)).GetGenericMethodDefinition(); - public static DQueryable GroupBy(this DQueryable query, HashSet keyTokens, HashSet aggregateTokens) - { - var rootKeyTokens = GetRootKeyTokens(keyTokens); - - var redundantKeyTokens = keyTokens.Except(rootKeyTokens).ToHashSet(); - - var keySelector = KeySelector(query.Context, rootKeyTokens); - - LambdaExpression resultSelector = ResultSelectSelectorAndContext(query.Context, rootKeyTokens, redundantKeyTokens, aggregateTokens, keySelector.Body.Type, isQueryable: true, out BuildExpressionContext newContext); - - var resultQuery = query.Query.Provider.CreateQuery(Expression.Call(null, miGroupByQ.MakeGenericMethod(query.Context.ElementType, keySelector.Body.Type, resultSelector.Body.Type), - new Expression[] { query.Query.Expression, Expression.Quote(keySelector), Expression.Quote(resultSelector) })); - - return new DQueryable(resultQuery, newContext); - } - - private static HashSet GetRootKeyTokens(HashSet keyTokens) - { - return keyTokens.Where(t => !keyTokens.Any(t2 => t2.Dominates(t))).ToHashSet(); - } - - - static MethodInfo miFirstE = ReflectionTools.GetMethodInfo(() => Enumerable.First((IEnumerable)null!)).GetGenericMethodDefinition(); - - static LambdaExpression ResultSelectSelectorAndContext(BuildExpressionContext context, HashSet rootKeyTokens, HashSet redundantKeyTokens, HashSet aggregateTokens, Type keyTupleType, bool isQueryable, out BuildExpressionContext newContext) - { - Dictionary resultExpressions = new Dictionary(); - ParameterExpression pk = Expression.Parameter(keyTupleType, "key"); - ParameterExpression pe = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(context.ElementType), "e"); - - resultExpressions.AddRange(rootKeyTokens.Select((kqt, i) => KeyValuePair.Create(kqt, TupleReflection.TupleChainProperty(pk, i)))); - - if (redundantKeyTokens.Any()) - { - if (isQueryable) - { - var tempContext = new BuildExpressionContext(keyTupleType, pk, rootKeyTokens.Select((kqt, i) => KeyValuePair.Create(kqt, new ExpressionBox(TupleReflection.TupleChainProperty(pk, i)))).ToDictionary()); - resultExpressions.AddRange(redundantKeyTokens.Select(t => KeyValuePair.Create(t, t.BuildExpression(tempContext)))); - } - else - { - var first = Expression.Call(miFirstE.MakeGenericMethod(typeof(object)), pe); - - resultExpressions.AddRange(redundantKeyTokens.Select(t => - { - var exp = t.BuildExpression(context); - var replaced = ExpressionReplacer.Replace(exp, - new Dictionary - { - { context.Parameter, first } - }); - - return KeyValuePair.Create(t, replaced); - })); - } - } - - resultExpressions.AddRange(aggregateTokens.Select(at => KeyValuePair.Create((QueryToken)at, BuildAggregateExpressionEnumerable(pe, at, context)))); - - var resultConstructor = TupleReflection.TupleChainConstructor(resultExpressions.Values); - - ParameterExpression pg = Expression.Parameter(resultConstructor.Type, "gr"); - newContext = new BuildExpressionContext(resultConstructor.Type, pg, - resultExpressions.Keys.Select((t, i) => KeyValuePair.Create(t, new ExpressionBox(TupleReflection.TupleChainProperty(pg, i)))).ToDictionary()); - - return Expression.Lambda(resultConstructor, pk, pe); - } - - static LambdaExpression KeySelector(BuildExpressionContext context, HashSet keyTokens) - { - var keySelector = Expression.Lambda( - TupleReflection.TupleChainConstructor(keyTokens.Select(t => t.BuildExpression(context)).ToList()), - context.Parameter); - return keySelector; - } - - static Expression BuildAggregateExpressionEnumerable(Expression collection, AggregateToken at, BuildExpressionContext context) - { - Type elementType = collection.Type.ElementType()!; - - if (at.AggregateFunction == AggregateFunction.Count && at.Parent == null) - return Expression.Call(typeof(Enumerable), "Count", new[] { elementType }, new[] { collection }); - - var body = at.Parent!.BuildExpression(context); - - if (at.AggregateFunction == AggregateFunction.Count) - { - if (at.FilterOperation.HasValue) - { - var condition = QueryUtils.GetCompareExpression(at.FilterOperation.Value, body.Nullify(), Expression.Constant(at.Value, body.Type.Nullify())); - - var lambda = Expression.Lambda(condition, context.Parameter); - - return Expression.Call(typeof(Enumerable), AggregateFunction.Count.ToString(), new[] { elementType }, new[] { collection, lambda }); - } - else if (at.Distinct) - { - var lambda = Expression.Lambda(body, context.Parameter); - - var select = Expression.Call(typeof(Enumerable), "Select", new[] { elementType, body.Type }, new[] { collection, lambda }); - var distinct = Expression.Call(typeof(Enumerable), "Distinct", new[] { body.Type }, new[] { select }); - var param = Expression.Parameter(lambda.Body.Type); - LambdaExpression notNull = Expression.Lambda(Expression.NotEqual(param, Expression.Constant(null, param.Type.Nullify())), param); - var count = Expression.Call(typeof(Enumerable), "Count", new[] { body.Type }, new Expression[] { distinct, notNull }); - - return count; - } - else - throw new InvalidOperationException(); - } - else - { - if (body.Type != at.Type) - body = body.TryConvert(at.Type); - - var lambda = Expression.Lambda(body, context.Parameter); - - if (at.AggregateFunction == AggregateFunction.Min || at.AggregateFunction == AggregateFunction.Max) - return Expression.Call(typeof(Enumerable), at.AggregateFunction.ToString(), new[] { elementType, lambda.Body.Type }, new[] { collection, lambda }); - - return Expression.Call(typeof(Enumerable), at.AggregateFunction.ToString(), new[] { elementType }, new[] { collection, lambda }); - } - } - - static Expression BuildAggregateExpressionQueryable(Expression collection, AggregateToken at, BuildExpressionContext context) - { - Type elementType = collection.Type.ElementType()!; - - if (at.AggregateFunction == AggregateFunction.Count) - return Expression.Call(typeof(Queryable), "Count", new[] { elementType }, new[] { collection }); - - var body = at.Parent!.BuildExpression(context); - - var type = at.Type; - - if (body.Type != type) - body = body.TryConvert(type); - - var lambda = Expression.Lambda(body, context.Parameter); - var quotedLambda = Expression.Quote(lambda); - - if (at.AggregateFunction == AggregateFunction.Min || at.AggregateFunction == AggregateFunction.Max) - return Expression.Call(typeof(Queryable), at.AggregateFunction.ToString(), new[] { elementType, lambda.Body.Type }, new[] { collection, quotedLambda }); - - return Expression.Call(typeof(Queryable), at.AggregateFunction.ToString(), new[] { elementType }, new[] { collection, quotedLambda }); - } - - static Expression BuildAggregateExpressionQueryableAsync(Expression collection, AggregateToken at, BuildExpressionContext context, CancellationToken token) - { - var tokenConstant = Expression.Constant(token); - - Type elementType = collection.Type.ElementType()!; - - if (at.AggregateFunction == AggregateFunction.Count) - return Expression.Call(typeof(QueryableAsyncExtensions), "CountAsync", new[] { elementType }, new[] { collection, tokenConstant }); - - var body = at.Parent!.BuildExpression(context); - - var type = at.AggregateFunction == AggregateFunction.Sum ? at.Type.UnNullify() : at.Type; - - if (body.Type != type) - body = body.TryConvert(type); - - var lambda = Expression.Lambda(body, context.Parameter); - var quotedLambda = Expression.Quote(lambda); - - if (at.AggregateFunction == AggregateFunction.Min || at.AggregateFunction == AggregateFunction.Max) - return Expression.Call(typeof(QueryableAsyncExtensions), at.AggregateFunction.ToString() + "Async", new[] { elementType, lambda.Body.Type }, new[] { collection, quotedLambda, tokenConstant }); - - return Expression.Call(typeof(QueryableAsyncExtensions), at.AggregateFunction.ToString() + "Async", new[] { elementType }, new[] { collection, quotedLambda, tokenConstant }); - } - - - #endregion - - #region SimpleAggregate - - public static object? SimpleAggregate(this DEnumerable collection, AggregateToken simpleAggregate) - { - var expr = BuildAggregateExpressionEnumerable(Expression.Constant(collection.Collection), simpleAggregate, collection.Context); - - return Expression.Lambda>(Expression.Convert(expr, typeof(object))).Compile()(); - } - - public static object? SimpleAggregate(this DQueryable query, AggregateToken simpleAggregate) - { - var expr = BuildAggregateExpressionQueryable(query.Query.Expression, simpleAggregate, query.Context); - - return Expression.Lambda>(Expression.Convert(expr, typeof(object))).Compile()(); - } - - public static Task SimpleAggregateAsync(this DQueryable query, AggregateToken simpleAggregate, CancellationToken token) - { - var expr = BuildAggregateExpressionQueryableAsync(query.Query.Expression, simpleAggregate, query.Context, token); - - var func = (Func)Expression.Lambda(expr).Compile(); - - var task = func(); - - return CastTask(task); - } - public static async Task CastTask(this Task task) - { - if (task == null) - throw new ArgumentNullException(nameof(task)); - - await task.ConfigureAwait(false); - - object? result = task.GetType().GetProperty(nameof(Task.Result))!.GetValue(task); - return (T)result!; - } - - #endregion - - public struct ExpandColumn : IExpandColumn - { - public QueryToken Token { get; private set; } - - public readonly Func, T> GetValue; - public ExpandColumn(QueryToken token, Func, T> getValue) - { - Token = token; - GetValue = getValue; - } - - Expression IExpandColumn.GetExpression(Expression entitySelector) - { - return Expression.Invoke(Expression.Constant(GetValue), entitySelector); - } - } - - public interface IExpandColumn - { - public QueryToken Token { get;} - Expression GetExpression(Expression entitySelector); - } - - public static DEnumerable ReplaceColumns(this DEnumerable query, params IExpandColumn[] newColumns) - { - var entity = query.Context.Replacements.Single(a => a.Key.FullKey() == "Entity").Value.GetExpression(); - var newColumnsDic = newColumns.ToDictionary(a => a.Token, a => a.GetExpression(entity)); - - List tokens = query.Context.Replacements.Keys.Union(newColumns.Select(a => a.Token)).ToList(); - List expressions = tokens.Select(t => newColumnsDic.TryGetC(t) ?? query.Context.Replacements.GetOrThrow(t).GetExpression()).ToList(); - Expression ctor = TupleReflection.TupleChainConstructor(expressions); - - var pe = Expression.Parameter(ctor.Type); - - var newContext = new BuildExpressionContext( - ctor.Type, pe, - tokens - .Select((t, i) => new { Token = t, Expr = TupleReflection.TupleChainProperty(pe, i) }) - .ToDictionary(t => t.Token!, t => new ExpressionBox(t.Expr))); - - var selector = Expression.Lambda(ctor, query.Context.Parameter); - - return new DEnumerable(Untyped.Select(query.Collection, selector.Compile()), newContext); - } - - public static ResultTable ToResultTable(this DEnumerableCount collection, QueryRequest req) - { - var isMultiKeyGrupping = req.GroupResults && req.Columns.Count(col => col.Token is not AggregateToken) >= 2; - - var columnAccesors = req.Columns.Select(c => - { - var expression = Expression.Lambda(c.Token.BuildExpression(collection.Context), collection.Context.Parameter); - - var lambda = expression.Compile(); - - var array = Untyped.ToArray(Untyped.Select(collection.Collection, lambda), expression.Body.Type); - - var rc = new ResultColumn(c, array); - - if (c.Token.Type.IsLite() || isMultiKeyGrupping && c.Token is not AggregateToken) - rc.CompressUniqueValues = true; - - return rc; - }).ToArray(); - - return new ResultTable(columnAccesors, collection.TotalElements, req.Pagination); - } -} - - -static class Untyped -{ - static MethodInfo miSelectQ = - ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Select((Expression>)null!)).GetGenericMethodDefinition(); - public static IQueryable Select(IQueryable query, LambdaExpression selector) - { - var types = selector.Type.GenericTypeArguments; - - var mi = miSelectQ.MakeGenericMethod(types); - - return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Quote(selector) })); - } - - static GenericInvoker> giSelectE = - new((q, selector) => ((IEnumerable)q).Select((Func)selector)); - public static IEnumerable Select(IEnumerable collection, Delegate selector) - { - var types = selector.GetType().GenericTypeArguments; - - return giSelectE.GetInvoker(types)(collection, selector); - } - - - static MethodInfo miWhereQ = - ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Where((Expression>)null!)).GetGenericMethodDefinition(); - public static IQueryable Where(IQueryable query, LambdaExpression predicate) - { - var types = query.GetType().GenericTypeArguments; - - var mi = miWhereQ.MakeGenericMethod(types); - - return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Quote(predicate) })); - } - - static GenericInvoker> giWhereE = - new((q, predicate) => ((IEnumerable)q).Where((Func)predicate)); - public static IEnumerable Where(IEnumerable collection, Delegate selector) - { - var types = selector.GetType().GenericTypeArguments; - - return giSelectE.GetInvoker(types)(collection, selector); - } - - static MethodInfo miDistinctQ = - ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Distinct()).GetGenericMethodDefinition(); - public static IQueryable Distinct(IQueryable query, Type elementType) - { - var mi = miDistinctQ.MakeGenericMethod(elementType); - - return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression })); - } - - static MethodInfo miOrderAlsoByKeysQ = - ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).OrderAlsoByKeys()).GetGenericMethodDefinition(); - public static IQueryable OrderAlsoByKeys(IQueryable query, Type elementType) - { - var mi = miOrderAlsoByKeysQ.MakeGenericMethod(elementType); - - return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression })); - } - - static GenericInvoker> giTakeE = - new((q, limit) => ((IEnumerable)q).Take(limit)); - public static IEnumerable Take(IEnumerable collection, int limit, Type elementType) - { - return giTakeE.GetInvoker(elementType)(collection, limit); - } - - static MethodInfo miTakeQ = - ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Take(3)).GetGenericMethodDefinition(); - public static IQueryable Take(IQueryable query, int limit, Type elementType) - { - var mi = miTakeQ.MakeGenericMethod(elementType); - - return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Constant(limit) })); - } - - static GenericInvoker> giSkipE = - new((q, limit) => ((IEnumerable)q).Skip(limit)); - public static IEnumerable Skip(IEnumerable collection, int limit, Type elementType) - { - return giSkipE.GetInvoker(elementType)(collection, limit); - } - - static MethodInfo miSkipQ = - ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Skip(3)).GetGenericMethodDefinition(); - public static IQueryable Skip(IQueryable query, int limit, Type elementType) - { - var mi = miSkipQ.MakeGenericMethod(elementType); - - return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Constant(limit) })); - } - - static GenericInvoker> giCountE = - new((q) => ((IEnumerable)q).Count()); - public static int Count(IEnumerable collection, Type elementType) - { - return giCountE.GetInvoker(elementType)(collection); - } - - - static MethodInfo miCountQ = - ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Count()).GetGenericMethodDefinition(); - public static int Count(IQueryable query, Type elementType) - { - var mi = miCountQ.MakeGenericMethod(elementType); - - return (int)query.Provider.Execute(Expression.Call(null, mi, new Expression[] { query.Expression }))!; - } - - public static async Task CountAsync(IQueryable query, CancellationToken token, Type elementType) - { - var mi = miCountQ.MakeGenericMethod(elementType); - - var result = await ((IQueryProviderAsync)query.Provider).ExecuteAsync(Expression.Call(null, mi, new Expression[] { query.Expression }), token)!; - - return (int)result!; - } - - static MethodInfo miConcatQ = - ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Concat((IQueryable)null!)).GetGenericMethodDefinition(); - public static IQueryable Concat(IQueryable query, IQueryable query2, Type elementType) - { - var mi = miConcatQ.MakeGenericMethod(elementType); - - return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, query2.Expression })); - } - - static GenericInvoker> gConcatE = - new((q, q2) => ((IEnumerable)q).Concat((IEnumerable)q2)); - - public static IEnumerable Concat(IEnumerable collection, IEnumerable collection2, Type elementType) - { - return gConcatE.GetInvoker(elementType)(collection, collection2); - } - - static GenericInvoker> gToArrayE = - new((q) => ((IEnumerable)q).ToArray()); - public static Array ToArray(IEnumerable collection, Type elementType) - { - return gToArrayE.GetInvoker(elementType)(collection); - } - - static GenericInvoker> gToListE = - new((q) => ((IEnumerable)q).ToList()); - - public static IList ToList(IEnumerable collection, Type elementType) - { - return gToListE.GetInvoker(elementType)(collection); - } - - static GenericInvoker>> gToListAsyncQ = - new((q, token) => ToIListAsync((IQueryable)q, token)); - - public static Task ToListAsync(IQueryable query, CancellationToken token, Type elementType) - { - return gToListAsyncQ.GetInvoker(elementType)(query, token); - } - - static async Task ToIListAsync(IQueryable query, CancellationToken token) - { - return await query.ToListAsync(token); - } - - static readonly GenericInvoker> giOrderByE = new((col, del) => ((IEnumerable)col).OrderBy((Func)del)); - static readonly GenericInvoker> giOrderByDescendingE = new((col, del) => ((IEnumerable)col).OrderByDescending((Func)del)); - public static IEnumerable OrderBy(IEnumerable collection, LambdaExpression lambda, OrderType orderType) - { - var mi = orderType == OrderType.Ascending ? giOrderByE : giOrderByDescendingE; - - return mi.GetInvoker(lambda.Type.GetGenericArguments())(collection, lambda.Compile()); - } - - static readonly GenericInvoker> giThenByE = new((col, del) => ((IOrderedEnumerable)col).ThenBy((Func)del)); - static readonly GenericInvoker> giThenByDescendingE = new((col, del) => ((IOrderedEnumerable)col).ThenByDescending((Func)del)); - public static IEnumerable ThenBy(IEnumerable collection, LambdaExpression lambda, OrderType orderType) - { - var mi = orderType == OrderType.Ascending ? giThenByE : giThenByDescendingE; - - return mi.GetInvoker(lambda.Type.GetGenericArguments())(collection, lambda.Compile()); - } - - public static IEnumerable OrderBy(IEnumerable collection, List<(LambdaExpression lambda, OrderType orderType)> orders) - { - if (orders == null || orders.Count == 0) - return collection; - - IEnumerable result = Untyped.OrderBy(collection, orders[0].lambda, orders[0].orderType); - - foreach (var (lambda, orderType) in orders.Skip(1)) - { - result = Untyped.ThenBy(result, lambda, orderType); - } - - return result; - } - - static MethodInfo miOrderByQ = ReflectionTools.GetMethodInfo(() => Database.Query().OrderBy(t => t.Id)).GetGenericMethodDefinition(); - static MethodInfo miOrderByDescendingQ = ReflectionTools.GetMethodInfo(() => Database.Query().OrderByDescending(t => t.Id)).GetGenericMethodDefinition(); - public static IOrderedQueryable OrderBy(IQueryable query, LambdaExpression lambda, OrderType orderType) - { - MethodInfo mi = (orderType == OrderType.Ascending ? miOrderByQ : miOrderByDescendingQ).MakeGenericMethod(lambda.Type.GetGenericArguments()); - - return (IOrderedQueryable)query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Quote(lambda) })); - } - - static MethodInfo miThenByQ = ReflectionTools.GetMethodInfo(() => Database.Query().OrderBy(t => t.Id).ThenBy(t => t.Id)).GetGenericMethodDefinition(); - static MethodInfo miThenByDescendingQ = ReflectionTools.GetMethodInfo(() => Database.Query().OrderBy(t => t.Id).ThenByDescending(t => t.Id)).GetGenericMethodDefinition(); - public static IOrderedQueryable ThenBy(IOrderedQueryable query, LambdaExpression lambda, OrderType orderType) - { - MethodInfo mi = (orderType == OrderType.Ascending ? miThenByQ : miThenByDescendingQ).MakeGenericMethod(lambda.Type.GetGenericArguments()); - - return (IOrderedQueryable)query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Quote(lambda) })); - } - - public static IQueryable OrderBy(IQueryable query, List<(LambdaExpression lambda, OrderType orderType)> orders) - { - if (orders == null || orders.Count == 0) - return query; - - IOrderedQueryable result = Untyped.OrderBy(query, orders[0].lambda, orders[0].orderType); - - foreach (var (lambda, orderType) in orders.Skip(1)) - { - result = Untyped.ThenBy(result, lambda, orderType); - } - - return result; - } -} +using Signum.Entities.DynamicQuery; +using Signum.Utilities.Reflection; +using Signum.Engine.Linq; +using Signum.Entities.Basics; +using System.Diagnostics.CodeAnalysis; +using System.Collections; + +namespace Signum.Engine.DynamicQuery; + +public interface IDynamicInfo +{ + BuildExpressionContext Context { get; } +} + + +/// Unraleted with the content, only with the original anonymous type +public class DQueryable : IDynamicInfo +{ + public DQueryable(IQueryable query, BuildExpressionContext context) + { + this.Query = query; + this.Context = context; + } + + public IQueryable Query { get; private set; } + public BuildExpressionContext Context { get; private set; } +} + +/// Unraleted with the content, only with the original anonymous type +public class DQueryableCount : DEnumerable +{ + public DQueryableCount(IQueryable query, BuildExpressionContext context, int totalElements) : + base(query, context) + { + this.TotalElements = totalElements; + } + + public int TotalElements { get; private set; } +} + +/// Unraleted with the content, only with the original anonymous type +public class DEnumerable : IDynamicInfo +{ + public DEnumerable(IEnumerable collection, BuildExpressionContext context) + { + this.Collection = collection; + this.Context = context; + } + + public IEnumerable Collection { get; private set; } + public BuildExpressionContext Context { get; private set; } +} + +/// Unraleted with the content, only with the original anonymous type +public class DEnumerableCount : DEnumerable +{ + public DEnumerableCount(IEnumerable collection, BuildExpressionContext context, int? totalElements) : + base(collection, context) + { + this.TotalElements = totalElements; + } + + public int? TotalElements {get; private set;} +} + + +public static class DQueryable +{ + #region ToDQueryable + + public static DQueryable ToDQueryable(this IQueryable query, QueryDescription description) + { + ParameterExpression pe = Expression.Parameter(typeof(T)); + + var dic = description.Columns.ToDictionary( + cd => (QueryToken)new ColumnToken(cd, description.QueryName), + cd => new ExpressionBox(Expression.PropertyOrField(pe, cd.Name).BuildLiteNullifyUnwrapPrimaryKey(cd.PropertyRoutes!))); + + return new DQueryable(query, new BuildExpressionContext(typeof(T), pe, dic)); + } + + + public static Task> AllQueryOperationsAsync(this DQueryable query, QueryRequest request, CancellationToken token) + { + return query + .SelectMany(request.Multiplications()) + .Where(request.Filters) + .OrderBy(request.Orders) + .Select(request.Columns) + .TryPaginateAsync(request.Pagination, request.SystemTime, token); + } + + public static DEnumerableCount AllQueryOperations(this DQueryable query, QueryRequest request) + { + return query + .SelectMany(request.Multiplications()) + .Where(request.Filters) + .OrderBy(request.Orders) + .Select(request.Columns) + .TryPaginate(request.Pagination, request.SystemTime); + } + + #endregion + + #region Select + + public static IEnumerable SelectOne(this DEnumerable collection, QueryToken token) + { + var exp = Expression.Lambda(Expression.Convert(token.BuildExpression(collection.Context), typeof(object)), collection.Context.Parameter); + + return (IEnumerable)Untyped.Select(collection.Collection, exp.Compile()); + } + + public static IQueryable SelectOne(this DQueryable query, QueryToken token) + { + var exp = Expression.Lambda(Expression.Convert(token.BuildExpression(query.Context), typeof(object)), query.Context.Parameter); + + return (IQueryable)Untyped.Select(query.Query, exp); + } + + public static DQueryable Select(this DQueryable query, List columns) + { + return Select(query, new HashSet(columns.Select(c => c.Token))); + } + + public static DQueryable Select(this DQueryable query, HashSet columns) + { + var selector = SelectTupleConstructor(query.Context, columns, out BuildExpressionContext newContext); + + return new DQueryable(Untyped.Select(query.Query, selector), newContext); + } + + public static DEnumerable Select(this DEnumerable collection, List columns) + { + return Select(collection, new HashSet(columns.Select(c => c.Token))); + } + + public static DEnumerable Select(this DEnumerable collection, HashSet columns) + { + var selector = SelectTupleConstructor(collection.Context, columns, out BuildExpressionContext newContext); + + return new DEnumerable(Untyped.Select(collection.Collection, selector.Compile()), newContext); + } + + + static LambdaExpression SelectTupleConstructor(BuildExpressionContext context, HashSet tokens, out BuildExpressionContext newContext) + { + string str = tokens.Select(t => QueryUtils.CanColumn(t)).NotNull().ToString("\r\n"); + if (str.HasText()) + throw new ApplicationException(str); + + List expressions = tokens.Select(t => t.BuildExpression(context)).ToList(); + Expression ctor = TupleReflection.TupleChainConstructor(expressions); + + var pe = Expression.Parameter(ctor.Type); + + newContext = new BuildExpressionContext( + ctor.Type, pe, + tokens.Select((t, i) => new + { + Token = t, + Expr = TupleReflection.TupleChainProperty(pe, i) + }).ToDictionary(t => t.Token!, t => new ExpressionBox(t.Expr))); + + return Expression.Lambda(ctor, context.Parameter); + } + + public static DQueryable SelectWithSubQueries(this DQueryable query, HashSet columns) + { + var selector = SelectTupleWithSubQueriesConstructor(query.Context, columns, out BuildExpressionContext newContext); + + return new DQueryable(Untyped.Select(query.Query, selector), newContext); + } + + static LambdaExpression SelectTupleWithSubQueriesConstructor(BuildExpressionContext context, HashSet tokens, out BuildExpressionContext newContext) + { + string str = tokens.Select(t => QueryUtils.CanColumn(t)).NotNull().ToString("\r\n"); + if (str.HasText()) + throw new ApplicationException(str); + + var tokenGroups = tokens.GroupBy(a => a.Follow(_ => _.Parent).OfType().FirstOrDefault()).ToList(); + + var tree = TreeHelper.ToTreeC(tokenGroups, gr => + { + if (gr.Key == null) + return null; + + var parentKey = gr.Key.Parent!.Follow(a => a.Parent).OfType().FirstOrDefault(); + + var grParent = tokenGroups.SingleEx(a => object.Equals(a.Key, parentKey)); + + return grParent; + }).SingleEx(); + + return SubQueryConstructor(context, tree, out newContext); + } + + public static MethodInfo miToList = ReflectionTools.GetMethodInfo(() => Enumerable.Empty().ToList()).GetGenericMethodDefinition(); + + static LambdaExpression SubQueryConstructor(BuildExpressionContext context, Node> node, out BuildExpressionContext newContext) + { + var simpleTokens = node.Value.ToList(); + + List expressions = simpleTokens.Select(t => t.BuildExpression(context)).ToList(); + + List subContext = new List(); + foreach (var child in node.Children) + { + var collectionToken = child.Value.Key!.Parent!; + var colExpre = collectionToken.BuildExpression(context); + + var elemType = colExpre.Type.ElementType()!; + + var pe2 = Expression.Parameter(elemType, elemType.CleanType().Name.Substring(0, 1).ToLower()); + + var subQueryCtx = new BuildExpressionContext(pe2.Type, pe2, new Dictionary + { + { child.Value.Key, new ExpressionBox(pe2) } + }); + + var subQueryExp = SubQueryConstructor(subQueryCtx, child, out var newSubContext); + + var tupleType = subQueryExp.Body.Type; + + var miSelect = colExpre.Type.IsInstanceOfType(typeof(IQueryable<>)) ? OverloadingSimplifier.miSelectQ : OverloadingSimplifier.miSelectE; + var select = Expression.Call(miSelect.MakeGenericMethod(pe2.Type!, tupleType), colExpre, subQueryExp); + var toList = Expression.Call(miToList.MakeGenericMethod(tupleType), select); + + expressions.Add(toList); + subContext.Add(newSubContext); + } + + Expression ctor = TupleReflection.TupleChainConstructor(expressions); + + var pe = Expression.Parameter(ctor.Type); + + var replacements = simpleTokens.Select((t, i) => new + { + Token = t, + Expr = TupleReflection.TupleChainProperty(pe, i) + }).ToDictionary(t => t.Token!, t => new ExpressionBox(t.Expr)); + + for (int i = 0; i < node.Children.Count; i++) + { + var childNode = node.Children[i]; + CollectionElementToken collectionElementToken = childNode.Value.Key!; + + var exp = TupleReflection.TupleChainProperty(pe, i + simpleTokens.Count); + replacements.Add(collectionElementToken, new ExpressionBox(exp, subQueryContext: subContext[i])); + } + + newContext = new BuildExpressionContext(ctor.Type, pe, replacements); + + return Expression.Lambda(ctor, context.Parameter); + } + + + public static DEnumerable Concat(this DEnumerable collection, DEnumerable other) + { + if (collection.Context.ElementType != other.Context.ElementType) + throw new InvalidOperationException("Enumerable's TupleType does not match Other's one.\r\n Enumerable: {0}: \r\n Other: {1}".FormatWith( + collection.Context.ElementType.TypeName(), + other.Context.ElementType.TypeName())); + + return new DEnumerable(Untyped.Concat(collection.Collection, other.Collection, collection.Context.ElementType), collection.Context); + } + + public static DEnumerableCount Concat(this DEnumerableCount collection, DEnumerableCount other) + { + if (collection.Context.ElementType != other.Context.ElementType) + throw new InvalidOperationException("Enumerable's TupleType does not match Other's one.\r\n Enumerable: {0}: \r\n Other: {1}".FormatWith( + collection.Context.ElementType.TypeName(), + other.Context.ElementType.TypeName())); + + return new DEnumerableCount(Untyped.Concat(collection.Collection,other.Collection, collection.Context.ElementType), collection.Context, collection.TotalElements + other.TotalElements); + } + #endregion + + public static DEnumerable ToDEnumerable(this DQueryable query) + { + return new DEnumerable(Untyped.ToList(query.Query, query.Context.ElementType), query.Context); + } + + public static DEnumerable ToDEnumerable(this IEnumerable query, QueryDescription description) + { + ParameterExpression pe = Expression.Parameter(typeof(T)); + + var dic = description.Columns.ToDictionary( + cd => (QueryToken)new ColumnToken(cd, description.QueryName), + cd => new ExpressionBox(Expression.PropertyOrField(pe, cd.Name).BuildLiteNullifyUnwrapPrimaryKey(cd.PropertyRoutes!))); + + return new DEnumerable(query, new BuildExpressionContext(typeof(T), pe, dic)); + } + + public static DEnumerableCount WithCount(this DEnumerable result, int? totalElements) + { + return new DEnumerableCount(result.Collection, result.Context, totalElements); + } + + public static async Task> ToDEnumerableAsync(this DQueryable query, CancellationToken token) + { + var list = await Untyped.ToListAsync(query.Query, token, query.Context.ElementType); + return new DEnumerable(list, query.Context); + } + + #region SelectMany + public static DQueryable SelectMany(this DQueryable query, List elementTokens) + { + foreach (var cet in elementTokens) + { + query = query.SelectMany(cet); + } + + return query; + } + + static MethodInfo miDefaultIfEmptyE = ReflectionTools.GetMethodInfo(() => Enumerable.Empty().DefaultIfEmpty()).GetGenericMethodDefinition(); + static MethodInfo miEmptyIfNull = ReflectionTools.GetMethodInfo(() => Enumerable.Empty().EmptyIfNull()).GetGenericMethodDefinition(); + + public static DQueryable SelectMany(this DQueryable query, CollectionElementToken cet) + { + SelectManyConstructor(query.Context, cet, + out LambdaExpression collectionSelector, + out LambdaExpression resultSelector, + out BuildExpressionContext newContext); + + var newQuery = Untyped.SelectMany(query.Query, collectionSelector, resultSelector); + + return new DQueryable(newQuery, newContext); + } + + private static void SelectManyConstructor(BuildExpressionContext context, CollectionElementToken cet, out LambdaExpression collectionSelector, out LambdaExpression resultSelector, out BuildExpressionContext newContext) + { + var eptML = MListElementPropertyToken.AsMListEntityProperty(cet.Parent!); + + Type elementType = eptML != null ? + MListElementPropertyToken.MListElementType(eptML) : + cet.Parent!.Type.ElementType()!; + + var collectionSelectorBody = Expression.Call(miDefaultIfEmptyE.MakeGenericMethod(elementType), + eptML != null ? MListElementPropertyToken.BuildMListElements(eptML, context) : + cet.Parent!.BuildExpression(context)); + + collectionSelector = Expression.Lambda( + typeof(Func<,>).MakeGenericType(context.ElementType, typeof(IEnumerable<>).MakeGenericType(elementType)), + collectionSelectorBody, + context.Parameter); + + var elementParameter = Expression.Parameter(elementType); + + var replacement = context.Replacements.ToDictionary(); //Necessary for SubQueries IEnumerable + + var properties = replacement.Values.Select(box => box.RawExpression).And(elementParameter.BuildLite().Nullify()).ToList(); + + var ctor = TupleReflection.TupleChainConstructor(properties); + + resultSelector = Expression.Lambda(ctor, context.Parameter, elementParameter); + var parameter = Expression.Parameter(ctor.Type); + + var newReplacements = replacement.Select((kvp, i) => KeyValuePair.Create(kvp.Key, + new ExpressionBox(TupleReflection.TupleChainProperty(parameter, i), + mlistElementRoute: kvp.Value.MListElementRoute) + )).ToDictionary(); + + newReplacements.Add(cet, new ExpressionBox( + TupleReflection.TupleChainProperty(parameter, replacement.Count), + mlistElementRoute: eptML != null ? cet.GetPropertyRoute() : null + )); + + newContext = new BuildExpressionContext(ctor.Type, parameter, newReplacements); + } + + public static DEnumerableCount SelectManySubQueries(this DEnumerableCount collection) + { + var subqueries = collection.Context.SubQueries(); + + if (!subqueries.Any()) + return collection; + + var nc = collection.SelectManySubQueries(subqueries); + + var count = Untyped.Count(nc.Collection, nc.Context.ElementType); + + return new DEnumerableCount(nc.Collection, nc.Context, count); + } + + static DEnumerable SelectManySubQueries(this DEnumerable collection, List elementTokens) + { + foreach (var cet in elementTokens) + { + var subQueryContext = collection.Context.Replacements[cet].SubQueryContext!; + + collection = collection.SelectManySubQuery(cet); + + var subQueries = subQueryContext.SubQueries(); + if (subQueries.Any()) + { + collection = collection.SelectManySubQueries(subQueries); + } + } + + return collection; + } + + static DEnumerable SelectManySubQuery(this DEnumerable collection, CollectionElementToken cet) + { + SelectManySubQueriesConstructor(collection.Context, cet, + out LambdaExpression collectionSelector, + out LambdaExpression resultSelector, + out BuildExpressionContext newContext); + + var newQuery = Untyped.SelectMany(collection.Collection, collectionSelector.Compile(), resultSelector.Compile()); + + return new DEnumerable(newQuery, newContext); + } + + static void SelectManySubQueriesConstructor(BuildExpressionContext context, CollectionElementToken cet, out LambdaExpression collectionSelector, out LambdaExpression resultSelector, out BuildExpressionContext newContext) + { + var subQueryContext = context.Replacements[cet].SubQueryContext!; + + var collectionSelectorBody = + Expression.Call(miDefaultIfEmptyE.MakeGenericMethod(subQueryContext.ElementType), + Expression.Call(miEmptyIfNull.MakeGenericMethod(subQueryContext.ElementType), + context.Replacements[cet].RawExpression)); + + collectionSelector = Expression.Lambda( + typeof(Func<,>).MakeGenericType(context.ElementType, typeof(IEnumerable<>).MakeGenericType(subQueryContext.ElementType)), + collectionSelectorBody, + context.Parameter); + + var replacement = context.Replacements.Where(a => a.Key != cet).ToDictionary(); + + var properties = replacement.Values.Select(box => box.RawExpression).ToList(); + + properties.AddRange(subQueryContext.Replacements.Values.Select(box => Expression.Condition( + Expression.Equal(subQueryContext.Parameter, Expression.Constant(null, subQueryContext.Parameter.Type)), + Expression.Constant(null, box.RawExpression.Type), + box.RawExpression))); + + var ctor = TupleReflection.TupleChainConstructor(properties); + + resultSelector = Expression.Lambda(ctor, context.Parameter, subQueryContext.Parameter); + var parameter = Expression.Parameter(ctor.Type); + + var newReplacements = replacement.Select((kvp, i) => KeyValuePair.Create(kvp.Key, + new ExpressionBox(TupleReflection.TupleChainProperty(parameter, i), + mlistElementRoute: kvp.Value.MListElementRoute, + subQueryContext: kvp.Value.SubQueryContext) + )).ToDictionary(); + + newReplacements.AddRange(subQueryContext.Replacements.Select((kvp, i) => KeyValuePair.Create(kvp.Key, + new ExpressionBox(TupleReflection.TupleChainProperty(parameter, i + replacement.Count), + mlistElementRoute: kvp.Value.MListElementRoute, + subQueryContext: kvp.Value.SubQueryContext) + ))); + + newContext = new BuildExpressionContext(ctor.Type, parameter, newReplacements); + } + + + #endregion + + #region Where + + public static DQueryable Where(this DQueryable query, params Filter[] filters) + { + return Where(query, filters.NotNull().ToList()); + } + + public static DQueryable Where(this DQueryable query, List filters) + { + LambdaExpression? predicate = GetPredicateExpression(query.Context, filters); + if (predicate == null) + return query; + + return new DQueryable(Untyped.Where(query.Query, predicate), query.Context); + } + + public static DQueryable Where(this DQueryable query, Expression> filter) + { + return new DQueryable(Untyped.Where(query.Query, filter), query.Context); + } + + public static DEnumerable Where(this DEnumerable collection, params Filter[] filters) + { + return Where(collection, filters.NotNull().ToList()); + } + + public static DEnumerable Where(this DEnumerable collection, List filters) + { + LambdaExpression? where = GetPredicateExpression(collection.Context, filters); + if (where == null) + return collection; + + return new DEnumerable(Untyped.Where(collection.Collection, where.Compile()), collection.Context); + } + + static LambdaExpression? GetPredicateExpression(BuildExpressionContext context, List filters) + { + if (filters == null || filters.Count == 0) + return null; + + string str = filters + .SelectMany(f => f.GetFilterConditions()) + .Select(f => QueryUtils.CanFilter(f.Token)) + .NotNull() + .ToString("\r\n"); + + if (str.HasText()) + throw new ApplicationException(str); + + Expression body = filters.Select(f => f.GetExpression(context)).AggregateAnd(); + + return Expression.Lambda(body, context.Parameter); + } + + #endregion + + #region OrderBy + + + + public static DQueryable OrderBy(this DQueryable query, List orders) + { + string str = orders.Select(f => QueryUtils.CanOrder(f.Token)).NotNull().ToString("\r\n"); + if (str.HasText()) + throw new ApplicationException(str); + + var pairs = orders.Select(o => ( + lambda: QueryUtils.CreateOrderLambda(o.Token, query.Context), + orderType: o.OrderType + )).ToList(); + + return new DQueryable(Untyped.OrderBy(query.Query, pairs), query.Context); + } + + + public static DEnumerable OrderBy(this DEnumerable collection, List orders) + { + var pairs = orders.Select(o => ( + lambda: QueryUtils.CreateOrderLambda(o.Token, collection.Context), + orderType: o.OrderType + )).ToList(); + + + return new DEnumerable(Untyped.OrderBy(collection.Collection, pairs), collection.Context); + } + + public static DEnumerableCount OrderBy(this DEnumerableCount collection, List orders) + { + var pairs = orders.Select(o => ( + lambda: QueryUtils.CreateOrderLambda(o.Token, collection.Context), + orderType: o.OrderType + )).ToList(); + + return new DEnumerableCount(Untyped.OrderBy(collection.Collection, pairs), collection.Context, collection.TotalElements); + } + + #endregion + + #region Unique + + [return: MaybeNull] + public static T Unique(this IEnumerable collection, UniqueType uniqueType) + { + return uniqueType switch + { + UniqueType.First => collection.First(), + UniqueType.FirstOrDefault => collection.FirstOrDefault(), + UniqueType.Single => collection.SingleEx(), + UniqueType.SingleOrDefault => collection.SingleOrDefaultEx(), + UniqueType.Only => collection.Only(), + _ => throw new InvalidOperationException(), + }; + } + + //[return: MaybeNull] + public static Task UniqueAsync(this IQueryable collection, UniqueType uniqueType, CancellationToken token) + { + return uniqueType switch + { + UniqueType.First => collection.FirstAsync(token), + UniqueType.FirstOrDefault => collection.FirstOrDefaultAsync(token)!, + UniqueType.Single => collection.SingleAsync(token), + UniqueType.SingleOrDefault => collection.SingleOrDefaultAsync(token)!, + UniqueType.Only => collection.Take(2).ToListAsync(token).ContinueWith(l => l.Result.Only()!), + _ => throw new InvalidOperationException(), + }; + } + + #endregion + + #region TryTake + public static DQueryable TryTake(this DQueryable query, int? num) + { + if (num.HasValue) + return new DQueryable(Untyped.Take(query.Query, num.Value, query.Context.ElementType), query.Context); + return query; + } + + public static DEnumerable TryTake(this DEnumerable collection, int? num) + { + if (num.HasValue) + return new DEnumerable(Untyped.Take(collection.Collection, num.Value, collection.Context.ElementType), collection.Context); + return collection; + } + #endregion + + + #region TryPaginate + + public static async Task> TryPaginateAsync(this DQueryable query, Pagination pagination, SystemTime? systemTime, CancellationToken token) + { + if (pagination == null) + throw new ArgumentNullException(nameof(pagination)); + + var elemType = query.Context.ElementType; + + if (pagination is Pagination.All) + { + var allList = await Untyped.ToListAsync(query.Query, token, elemType); + + return new DEnumerableCount(allList, query.Context, allList.Count); + } + else if (pagination is Pagination.Firsts top) + { + var topList = await Untyped.ToListAsync(Untyped.Take(query.Query, top.TopElements, elemType), token, elemType); + + return new DEnumerableCount(topList, query.Context, null); + } + else if (pagination is Pagination.Paginate pag) + { + if (systemTime is SystemTime.Interval) //Results multipy due to Joins, not easy to change LINQ provider because joins are delayed + { + var q = Untyped.OrderAlsoByKeys(query.Query, elemType); + + var list = await Untyped.ToListAsync(query.Query /*q maybe?*/, token, elemType); + + var elements = list; + if (pag.CurrentPage != 1) + elements = Untyped.ToList(Untyped.Skip(elements, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType), elemType); + + elements = Untyped.ToList(Untyped.Take(elements, pag.ElementsPerPage, elemType), elemType); + + return new DEnumerableCount(elements, query.Context, list.Count); + } + else + { + var q = Untyped.OrderAlsoByKeys(query.Query, elemType); + + if (pag.CurrentPage != 1) + q = Untyped.Skip(q, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType); + + q = Untyped.Take(q, pag.ElementsPerPage, elemType); + + var listTask = await Untyped.ToListAsync(q, token, elemType); + var countTask = systemTime is SystemTime.Interval ? + (await Untyped.ToListAsync(query.Query, token, elemType)).Count : //Results multipy due to Joins, not easy to change LINQ provider because joins are delayed + await Untyped.CountAsync(query.Query, token, elemType); + + return new DEnumerableCount(listTask, query.Context, countTask); + } + } + + throw new InvalidOperationException("pagination type {0} not expexted".FormatWith(pagination.GetType().Name)); + } + + public static DEnumerableCount TryPaginate(this DQueryable query, Pagination pagination, SystemTime? systemTime) + { + if (pagination == null) + throw new ArgumentNullException(nameof(pagination)); + + var elemType = query.Context.ElementType; + + if (pagination is Pagination.All) + { + var allList = Untyped.ToList(query.Query, elemType); + + return new DEnumerableCount(allList, query.Context, allList.Count); + } + else if (pagination is Pagination.Firsts top) + { + var topList = Untyped.ToList(Untyped.Take(query.Query, top.TopElements, elemType), elemType); + + return new DEnumerableCount(topList, query.Context, null); + } + else if (pagination is Pagination.Paginate pag) + { + if(systemTime is SystemTime.Interval) //Results multipy due to Joins, not easy to change LINQ provider because joins are delayed + { + var q = Untyped.OrderAlsoByKeys(query.Query, elemType); + + var list = Untyped.ToList(query.Query /*q?*/, elemType); + + var elements = list; + if (pag.CurrentPage != 1) + elements = Untyped.ToList(Untyped.Skip(elements, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType), elemType); + + elements = Untyped.ToList(Untyped.Take(elements, pag.ElementsPerPage, elemType), elemType); + + return new DEnumerableCount(elements, query.Context, list.Count); + } + else + { + var q = Untyped.OrderAlsoByKeys(query.Query, elemType); + + if (pag.CurrentPage != 1) + q = Untyped.Skip(q, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType); + + q = Untyped.Take(q, pag.ElementsPerPage, elemType); + + var list = Untyped.ToList(q, elemType); + var count = list.Count < pag.ElementsPerPage ? pag.ElementsPerPage : + Untyped.Count(query.Query, elemType); + + return new DEnumerableCount(list, query.Context, count); + } + + + + } + + throw new InvalidOperationException("pagination type {0} not expexted".FormatWith(pagination.GetType().Name)); + } + + public static DEnumerableCount TryPaginate(this DEnumerable collection, Pagination pagination) + { + if (pagination == null) + throw new ArgumentNullException(nameof(pagination)); + + + var elemType = collection.Context.ElementType; + + if (pagination is Pagination.All) + { + var allList = Untyped.ToList(collection.Collection, elemType); + + return new DEnumerableCount(allList, collection.Context, allList.Count); + } + else if (pagination is Pagination.Firsts top) + { + var topList = Untyped.ToList(Untyped.Take(collection.Collection, top.TopElements, elemType), elemType); + + return new DEnumerableCount(topList, collection.Context, null); + } + else if (pagination is Pagination.Paginate pag) + { + int? totalElements = null; + + var q = collection.Collection; + if (pag.CurrentPage != 1) + q = Untyped.Skip(q, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType); + + q = Untyped.Take(q, pag.ElementsPerPage, elemType); + + var list = Untyped.ToList(q, elemType); + + if (list.Count < pag.ElementsPerPage && pag.CurrentPage == 1) + totalElements = list.Count; + + return new DEnumerableCount(list, collection.Context, totalElements ?? Untyped.Count(collection.Collection, elemType)); + } + + throw new InvalidOperationException("pagination type {0} not expexted".FormatWith(pagination.GetType().Name)); + } + + public static DEnumerableCount TryPaginate(this DEnumerableCount collection, Pagination pagination) + { + if (pagination == null) + throw new ArgumentNullException(nameof(pagination)); + + var elemType = collection.Context.ElementType; + + if (pagination is Pagination.All) + { + return new DEnumerableCount(collection.Collection, collection.Context, collection.TotalElements); + } + else if (pagination is Pagination.Firsts top) + { + var topList = Untyped.ToList(Untyped.Take(collection.Collection, top.TopElements, elemType), elemType); + + return new DEnumerableCount(topList, collection.Context, null); + } + else if (pagination is Pagination.Paginate pag) + { + var c = collection.Collection; + if (pag.CurrentPage != 1) + c = Untyped.Skip(c, (pag.CurrentPage - 1) * pag.ElementsPerPage, elemType); + + c = Untyped.Take(c, pag.ElementsPerPage, elemType); + + return new DEnumerableCount(c, collection.Context, collection.TotalElements); + } + + throw new InvalidOperationException("pagination type {0} not expexted".FormatWith(pagination.GetType().Name)); + } + + #endregion + + #region GroupBy + + static readonly GenericInvoker> giGroupByE = + new((col, ks, rs) => (IEnumerable)Enumerable.GroupBy((IEnumerable)col, (Func)ks, (Func, double>)rs)); + public static DEnumerable GroupBy(this DEnumerable collection, HashSet keyTokens, HashSet aggregateTokens) + { + var rootKeyTokens = GetRootKeyTokens(keyTokens); + + var redundantKeyTokens = keyTokens.Except(rootKeyTokens).ToHashSet(); + + var keySelector = KeySelector(collection.Context, rootKeyTokens); + + LambdaExpression resultSelector = ResultSelectSelectorAndContext(collection.Context, rootKeyTokens, redundantKeyTokens, aggregateTokens, keySelector.Body.Type, isQueryable: false, out BuildExpressionContext newContext); + + var resultCollection = giGroupByE.GetInvoker(collection.Context.ElementType, keySelector.Body.Type, resultSelector.Body.Type)(collection.Collection, keySelector.Compile(), resultSelector.Compile()); + + return new DEnumerable(resultCollection, newContext); + } + + static MethodInfo miGroupByQ = ReflectionTools.GetMethodInfo(() => Queryable.GroupBy((IQueryable)null!, (Expression>)null!, (Expression, double>>)null!)).GetGenericMethodDefinition(); + public static DQueryable GroupBy(this DQueryable query, HashSet keyTokens, HashSet aggregateTokens) + { + var rootKeyTokens = GetRootKeyTokens(keyTokens); + + var redundantKeyTokens = keyTokens.Except(rootKeyTokens).ToHashSet(); + + var keySelector = KeySelector(query.Context, rootKeyTokens); + + LambdaExpression resultSelector = ResultSelectSelectorAndContext(query.Context, rootKeyTokens, redundantKeyTokens, aggregateTokens, keySelector.Body.Type, isQueryable: true, out BuildExpressionContext newContext); + + var resultQuery = query.Query.Provider.CreateQuery(Expression.Call(null, miGroupByQ.MakeGenericMethod(query.Context.ElementType, keySelector.Body.Type, resultSelector.Body.Type), + new Expression[] { query.Query.Expression, Expression.Quote(keySelector), Expression.Quote(resultSelector) })); + + return new DQueryable(resultQuery, newContext); + } + + private static HashSet GetRootKeyTokens(HashSet keyTokens) + { + return keyTokens.Where(t => !keyTokens.Any(t2 => t2.Dominates(t))).ToHashSet(); + } + + + static MethodInfo miFirstE = ReflectionTools.GetMethodInfo(() => Enumerable.First((IEnumerable)null!)).GetGenericMethodDefinition(); + + static LambdaExpression ResultSelectSelectorAndContext(BuildExpressionContext context, HashSet rootKeyTokens, HashSet redundantKeyTokens, HashSet aggregateTokens, Type keyTupleType, bool isQueryable, out BuildExpressionContext newContext) + { + Dictionary resultExpressions = new Dictionary(); + ParameterExpression pk = Expression.Parameter(keyTupleType, "key"); + ParameterExpression pe = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(context.ElementType), "e"); + + resultExpressions.AddRange(rootKeyTokens.Select((kqt, i) => KeyValuePair.Create(kqt, TupleReflection.TupleChainProperty(pk, i)))); + + if (redundantKeyTokens.Any()) + { + if (isQueryable) + { + var tempContext = new BuildExpressionContext(keyTupleType, pk, rootKeyTokens.Select((kqt, i) => KeyValuePair.Create(kqt, new ExpressionBox(TupleReflection.TupleChainProperty(pk, i)))).ToDictionary()); + resultExpressions.AddRange(redundantKeyTokens.Select(t => KeyValuePair.Create(t, t.BuildExpression(tempContext)))); + } + else + { + var first = Expression.Call(miFirstE.MakeGenericMethod(typeof(object)), pe); + + resultExpressions.AddRange(redundantKeyTokens.Select(t => + { + var exp = t.BuildExpression(context); + var replaced = ExpressionReplacer.Replace(exp, + new Dictionary + { + { context.Parameter, first } + }); + + return KeyValuePair.Create(t, replaced); + })); + } + } + + resultExpressions.AddRange(aggregateTokens.Select(at => KeyValuePair.Create((QueryToken)at, BuildAggregateExpressionEnumerable(pe, at, context)))); + + var resultConstructor = TupleReflection.TupleChainConstructor(resultExpressions.Values); + + ParameterExpression pg = Expression.Parameter(resultConstructor.Type, "gr"); + newContext = new BuildExpressionContext(resultConstructor.Type, pg, + resultExpressions.Keys.Select((t, i) => KeyValuePair.Create(t, new ExpressionBox(TupleReflection.TupleChainProperty(pg, i)))).ToDictionary()); + + return Expression.Lambda(resultConstructor, pk, pe); + } + + static LambdaExpression KeySelector(BuildExpressionContext context, HashSet keyTokens) + { + var keySelector = Expression.Lambda( + TupleReflection.TupleChainConstructor(keyTokens.Select(t => t.BuildExpression(context)).ToList()), + context.Parameter); + return keySelector; + } + + static Expression BuildAggregateExpressionEnumerable(Expression collection, AggregateToken at, BuildExpressionContext context) + { + Type elementType = collection.Type.ElementType()!; + + if (at.AggregateFunction == AggregateFunction.Count && at.Parent == null) + return Expression.Call(typeof(Enumerable), "Count", new[] { elementType }, new[] { collection }); + + var body = at.Parent!.BuildExpression(context); + + if (at.AggregateFunction == AggregateFunction.Count) + { + if (at.FilterOperation.HasValue) + { + var condition = QueryUtils.GetCompareExpression(at.FilterOperation.Value, body.Nullify(), Expression.Constant(at.Value, body.Type.Nullify())); + + var lambda = Expression.Lambda(condition, context.Parameter); + + return Expression.Call(typeof(Enumerable), AggregateFunction.Count.ToString(), new[] { elementType }, new[] { collection, lambda }); + } + else if (at.Distinct) + { + var lambda = Expression.Lambda(body, context.Parameter); + + var select = Expression.Call(typeof(Enumerable), "Select", new[] { elementType, body.Type }, new[] { collection, lambda }); + var distinct = Expression.Call(typeof(Enumerable), "Distinct", new[] { body.Type }, new[] { select }); + var param = Expression.Parameter(lambda.Body.Type); + LambdaExpression notNull = Expression.Lambda(Expression.NotEqual(param, Expression.Constant(null, param.Type.Nullify())), param); + var count = Expression.Call(typeof(Enumerable), "Count", new[] { body.Type }, new Expression[] { distinct, notNull }); + + return count; + } + else + throw new InvalidOperationException(); + } + else + { + if (body.Type != at.Type) + body = body.TryConvert(at.Type); + + var lambda = Expression.Lambda(body, context.Parameter); + + if (at.AggregateFunction == AggregateFunction.Min || at.AggregateFunction == AggregateFunction.Max) + return Expression.Call(typeof(Enumerable), at.AggregateFunction.ToString(), new[] { elementType, lambda.Body.Type }, new[] { collection, lambda }); + + return Expression.Call(typeof(Enumerable), at.AggregateFunction.ToString(), new[] { elementType }, new[] { collection, lambda }); + } + } + + static Expression BuildAggregateExpressionQueryable(Expression collection, AggregateToken at, BuildExpressionContext context) + { + Type elementType = collection.Type.ElementType()!; + + if (at.AggregateFunction == AggregateFunction.Count) + return Expression.Call(typeof(Queryable), "Count", new[] { elementType }, new[] { collection }); + + var body = at.Parent!.BuildExpression(context); + + var type = at.Type; + + if (body.Type != type) + body = body.TryConvert(type); + + var lambda = Expression.Lambda(body, context.Parameter); + var quotedLambda = Expression.Quote(lambda); + + if (at.AggregateFunction == AggregateFunction.Min || at.AggregateFunction == AggregateFunction.Max) + return Expression.Call(typeof(Queryable), at.AggregateFunction.ToString(), new[] { elementType, lambda.Body.Type }, new[] { collection, quotedLambda }); + + return Expression.Call(typeof(Queryable), at.AggregateFunction.ToString(), new[] { elementType }, new[] { collection, quotedLambda }); + } + + static Expression BuildAggregateExpressionQueryableAsync(Expression collection, AggregateToken at, BuildExpressionContext context, CancellationToken token) + { + var tokenConstant = Expression.Constant(token); + + Type elementType = collection.Type.ElementType()!; + + if (at.AggregateFunction == AggregateFunction.Count) + return Expression.Call(typeof(QueryableAsyncExtensions), "CountAsync", new[] { elementType }, new[] { collection, tokenConstant }); + + var body = at.Parent!.BuildExpression(context); + + var type = at.AggregateFunction == AggregateFunction.Sum ? at.Type.UnNullify() : at.Type; + + if (body.Type != type) + body = body.TryConvert(type); + + var lambda = Expression.Lambda(body, context.Parameter); + var quotedLambda = Expression.Quote(lambda); + + if (at.AggregateFunction == AggregateFunction.Min || at.AggregateFunction == AggregateFunction.Max) + return Expression.Call(typeof(QueryableAsyncExtensions), at.AggregateFunction.ToString() + "Async", new[] { elementType, lambda.Body.Type }, new[] { collection, quotedLambda, tokenConstant }); + + return Expression.Call(typeof(QueryableAsyncExtensions), at.AggregateFunction.ToString() + "Async", new[] { elementType }, new[] { collection, quotedLambda, tokenConstant }); + } + + + #endregion + + #region SimpleAggregate + + public static object? SimpleAggregate(this DEnumerable collection, AggregateToken simpleAggregate) + { + var expr = BuildAggregateExpressionEnumerable(Expression.Constant(collection.Collection), simpleAggregate, collection.Context); + + return Expression.Lambda>(Expression.Convert(expr, typeof(object))).Compile()(); + } + + public static object? SimpleAggregate(this DQueryable query, AggregateToken simpleAggregate) + { + var expr = BuildAggregateExpressionQueryable(query.Query.Expression, simpleAggregate, query.Context); + + return Expression.Lambda>(Expression.Convert(expr, typeof(object))).Compile()(); + } + + public static Task SimpleAggregateAsync(this DQueryable query, AggregateToken simpleAggregate, CancellationToken token) + { + var expr = BuildAggregateExpressionQueryableAsync(query.Query.Expression, simpleAggregate, query.Context, token); + + var func = (Func)Expression.Lambda(expr).Compile(); + + var task = func(); + + return CastTask(task); + } + public static async Task CastTask(this Task task) + { + if (task == null) + throw new ArgumentNullException(nameof(task)); + + await task.ConfigureAwait(false); + + object? result = task.GetType().GetProperty(nameof(Task.Result))!.GetValue(task); + return (T)result!; + } + + #endregion + + public struct ExpandColumn : IExpandColumn + { + public QueryToken Token { get; private set; } + + public readonly Func, T> GetValue; + public ExpandColumn(QueryToken token, Func, T> getValue) + { + Token = token; + GetValue = getValue; + } + + Expression IExpandColumn.GetExpression(Expression entitySelector) + { + return Expression.Invoke(Expression.Constant(GetValue), entitySelector); + } + } + + public interface IExpandColumn + { + public QueryToken Token { get;} + Expression GetExpression(Expression entitySelector); + } + + public static DEnumerable ReplaceColumns(this DEnumerable query, params IExpandColumn[] newColumns) + { + var entity = query.Context.Replacements.Single(a => a.Key.FullKey() == "Entity").Value.GetExpression(); + var newColumnsDic = newColumns.ToDictionary(a => a.Token, a => a.GetExpression(entity)); + + List tokens = query.Context.Replacements.Keys.Union(newColumns.Select(a => a.Token)).ToList(); + List expressions = tokens.Select(t => newColumnsDic.TryGetC(t) ?? query.Context.Replacements.GetOrThrow(t).GetExpression()).ToList(); + Expression ctor = TupleReflection.TupleChainConstructor(expressions); + + var pe = Expression.Parameter(ctor.Type); + + var newContext = new BuildExpressionContext( + ctor.Type, pe, + tokens + .Select((t, i) => new { Token = t, Expr = TupleReflection.TupleChainProperty(pe, i) }) + .ToDictionary(t => t.Token!, t => new ExpressionBox(t.Expr))); + + var selector = Expression.Lambda(ctor, query.Context.Parameter); + + return new DEnumerable(Untyped.Select(query.Collection, selector.Compile()), newContext); + } + + public static ResultTable ToResultTable(this DEnumerableCount collection, QueryRequest req) + { + var isMultiKeyGrupping = req.GroupResults && req.Columns.Count(col => col.Token is not AggregateToken) >= 2; + + var columnAccesors = req.Columns.Select(c => + { + var expression = Expression.Lambda(c.Token.BuildExpression(collection.Context), collection.Context.Parameter); + + var lambda = expression.Compile(); + + var array = Untyped.ToArray(Untyped.Select(collection.Collection, lambda), expression.Body.Type); + + var rc = new ResultColumn(c, array); + + if (c.Token.Type.IsLite() || isMultiKeyGrupping && c.Token is not AggregateToken) + rc.CompressUniqueValues = true; + + return rc; + }).ToArray(); + + return new ResultTable(columnAccesors, collection.TotalElements, req.Pagination); + } +} diff --git a/Signum.Engine/DynamicQuery/DynamicQueryContainer.cs b/Signum.Engine/DynamicQuery/DynamicQueryContainer.cs index 365538b09c..5b994cdec3 100644 --- a/Signum.Engine/DynamicQuery/DynamicQueryContainer.cs +++ b/Signum.Engine/DynamicQuery/DynamicQueryContainer.cs @@ -226,3 +226,46 @@ public List GetQueryNames() })); } } + +public class DynamicQueryBucket +{ + public ResetLazy Core { get; private set; } + + public object QueryName { get; private set; } + + public Implementations EntityImplementations { get; private set; } + + public DynamicQueryBucket(object queryName, Func lazyQueryCore, Implementations entityImplementations) + { + if (lazyQueryCore == null) + throw new ArgumentNullException(nameof(lazyQueryCore)); + + this.QueryName = queryName ?? throw new ArgumentNullException(nameof(queryName)); + this.EntityImplementations = entityImplementations; + + this.Core = new ResetLazy(() => + { + var core = lazyQueryCore(); + + core.QueryName = QueryName; + + core.StaticColumns.Where(sc => sc.IsEntity).SingleEx(() => "Entity column on {0}".FormatWith(QueryUtils.GetKey(QueryName))); + + core.EntityColumnFactory().Implementations = entityImplementations; + + var errors = core.StaticColumns.Where(sc => sc.Implementations == null && sc.Type.CleanType().IsIEntity()); + + if (errors.Any()) + throw new InvalidOperationException("Column {0} of query '{1}' do(es) not have implementations defined. Use Column extension method".FormatWith(errors.CommaAnd(a => $"'{a.Name}'"), QueryUtils.GetKey(QueryName))); + + return core; + }); + } + + + public QueryDescription GetDescription() + { + return Core.Value.GetQueryDescription(); + } +} + diff --git a/Signum.Engine/DynamicQuery/DynamicQueryCore.cs b/Signum.Engine/DynamicQuery/DynamicQueryCore.cs new file mode 100644 index 0000000000..eef80a90de --- /dev/null +++ b/Signum.Engine/DynamicQuery/DynamicQueryCore.cs @@ -0,0 +1,145 @@ +using Signum.Engine.Linq; +using Signum.Entities.Basics; +using Signum.Entities.DynamicQuery; +using Signum.Utilities.Reflection; + +namespace Signum.Engine.DynamicQuery; + +public interface IDynamicQueryCore +{ + object QueryName { get; set; } + ColumnDescriptionFactory[] StaticColumns { get; } + Expression? Expression { get; } + + ColumnDescriptionFactory EntityColumnFactory(); + QueryDescription GetQueryDescription(); + + ResultTable ExecuteQuery(QueryRequest request); + Task ExecuteQueryAsync(QueryRequest request, CancellationToken cancellationToken); + ResultTable ExecuteQueryGroup(QueryRequest request); + Task ExecuteQueryGroupAsync(QueryRequest request, CancellationToken cancellationToken); + object? ExecuteQueryValue(QueryValueRequest request); + Task ExecuteQueryValueAsync(QueryValueRequest request, CancellationToken cancellationToken); + Lite? ExecuteUniqueEntity(UniqueEntityRequest request); + Task?> ExecuteUniqueEntityAsync(UniqueEntityRequest request, CancellationToken cancellationToken); + + IQueryable> GetEntitiesLite(QueryEntitiesRequest request); + IQueryable GetEntitiesFull(QueryEntitiesRequest request); +} + + +public static class DynamicQueryCore +{ + public static AutoDynamicQueryCore Auto(IQueryable query) + { + return new AutoDynamicQueryCore(query); + } + + public static ManualDynamicQueryCore Manual(Func>> execute) + { + return new ManualDynamicQueryCore(execute); + } + + internal static IDynamicQueryCore FromSelectorUntyped(Expression> expression) + where T : Entity + { + var eType = expression.Parameters.SingleEx().Type; + var tType = expression.Body.Type; + var typedSelector = Expression.Lambda(expression.Body, expression.Parameters); + + return giAutoPrivate.GetInvoker(eType, tType)(typedSelector); + } + + static readonly GenericInvoker> giAutoPrivate = + new(lambda => FromSelector((Expression>)lambda)); + public static AutoDynamicQueryCore FromSelector(Expression> selector) + where E : Entity + { + return new AutoDynamicQueryCore(Database.Query().Select(selector)); + } + + public static Dictionary? QueryMetadata(IQueryable query) + { + return MetadataVisitor.GatherMetadata(query.Expression); + } + +} + +public abstract class DynamicQueryCore : IDynamicQueryCore +{ + public object QueryName { get; set; } = null!; + + public ColumnDescriptionFactory[] StaticColumns { get; protected set; } = null!; + + public abstract ResultTable ExecuteQuery(QueryRequest request); + public abstract Task ExecuteQueryAsync(QueryRequest request, CancellationToken cancellationToken); + + public abstract ResultTable ExecuteQueryGroup(QueryRequest request); + public abstract Task ExecuteQueryGroupAsync(QueryRequest request, CancellationToken cancellationToken); + + public abstract object? ExecuteQueryValue(QueryValueRequest request); + public abstract Task ExecuteQueryValueAsync(QueryValueRequest request, CancellationToken cancellationToken); + + public abstract Lite? ExecuteUniqueEntity(UniqueEntityRequest request); + public abstract Task?> ExecuteUniqueEntityAsync(UniqueEntityRequest request, CancellationToken cancellationToken); + + public abstract IQueryable> GetEntitiesLite(QueryEntitiesRequest request); + public abstract IQueryable GetEntitiesFull(QueryEntitiesRequest request); + + + protected virtual ColumnDescriptionFactory[] InitializeColumns() + { + var result = MemberEntryFactory.GenerateList(MemberOptions.Properties | MemberOptions.Fields) + .Select((e, i) => new ColumnDescriptionFactory(i, e.MemberInfo, null)).ToArray(); + + return result; + } + + public DynamicQueryCore ColumnDisplayName(Expression> column, Enum messageValue) + { + return this.Column(column, c => c.OverrideDisplayName = () => messageValue.NiceToString()); + } + + public DynamicQueryCore ColumnDisplayName(Expression> column, Func messageValue) + { + return this.Column(column, c => c.OverrideDisplayName = messageValue); + } + + public DynamicQueryCore ColumnProperyRoutes(Expression> column, params PropertyRoute[] routes) + { + return this.Column(column, c => c.PropertyRoutes = routes); + } + + public DynamicQueryCore Column(Expression> column, Action change) + { + MemberInfo member = ReflectionTools.GetMemberInfo(column); + ColumnDescriptionFactory col = StaticColumns.SingleEx(a => a.Name == member.Name); + change(col); + + return this; + } + + public ColumnDescriptionFactory EntityColumnFactory() + { + return StaticColumns.Where(c => c.IsEntity).SingleEx(() => "Entity column on {0}".FormatWith(QueryUtils.GetKey(QueryName))); + } + + public virtual Expression? Expression + { + get { return null; } + } + + public QueryDescription GetQueryDescription() + { + var entity = EntityColumnFactory(); + string? allowed = entity.IsAllowed(); + if (allowed != null) + throw new InvalidOperationException( + "Not authorized to see Entity column on {0} because {1}".FormatWith(QueryUtils.GetKey(QueryName), allowed)); + + var columns = StaticColumns.Where(f => f.IsAllowed() == null).Select(f => f.BuildColumnDescription()).ToList(); + + return new QueryDescription(QueryName, columns); + } +} + diff --git a/Signum.Engine/DynamicQuery/ManualDynamicQuery.cs b/Signum.Engine/DynamicQuery/ManualDynamicQueryCore.cs similarity index 97% rename from Signum.Engine/DynamicQuery/ManualDynamicQuery.cs rename to Signum.Engine/DynamicQuery/ManualDynamicQueryCore.cs index 446793f40d..ccd8e9f4de 100644 --- a/Signum.Engine/DynamicQuery/ManualDynamicQuery.cs +++ b/Signum.Engine/DynamicQuery/ManualDynamicQueryCore.cs @@ -1,138 +1,138 @@ -using Signum.Entities.DynamicQuery; -using Signum.Utilities.Reflection; - -namespace Signum.Engine.DynamicQuery; - -public class ManualDynamicQueryCore : DynamicQueryCore -{ - public Func>> Execute { get; private set; } - - - public ManualDynamicQueryCore(Func>> execute) - { - this.Execute = execute ?? throw new ArgumentNullException(nameof(execute)); - - this.StaticColumns = MemberEntryFactory.GenerateList(MemberOptions.Properties | MemberOptions.Fields) - .Select((e, i) => new ColumnDescriptionFactory(i, e.MemberInfo, null)).ToArray(); - } - - - public override ResultTable ExecuteQuery(QueryRequest request) => Task.Run(() => ExecuteQueryAsync(request, CancellationToken.None)).Result; - public override async Task ExecuteQueryAsync(QueryRequest request, CancellationToken cancellationToken) - { - request.Columns.Insert(0, new _EntityColumn(EntityColumnFactory().BuildColumnDescription(), QueryName)); - - DEnumerableCount manualResult = await Execute(request, GetQueryDescription(), cancellationToken); - - return manualResult.ToResultTable(request); - } - - public override ResultTable ExecuteQueryGroup(QueryRequest request) => Task.Run(() => ExecuteQueryGroupAsync(request, CancellationToken.None)).Result; - public override async Task ExecuteQueryGroupAsync(QueryRequest request, CancellationToken cancellationToken) - { - var simpleFilters = request.Filters.Where(f => !f.IsAggregate()).ToList(); - var aggregateFilters = request.Filters.Where(f => f.IsAggregate()).ToList(); - - var keys = request.Columns.Select(t => t.Token).Where(t => !(t is AggregateToken)).ToHashSet(); - - var allAggregates = request.AllTokens().OfType().ToHashSet(); - - var qr = new QueryRequest - { - Columns = keys.Concat(allAggregates.Select(at => at.Parent).NotNull()).Distinct().Select(t => new Column(t, t.NiceName())).ToList(), - Orders = new List(), - Filters = simpleFilters, - QueryName = request.QueryName, - Pagination = new Pagination.All(), - }; - - DEnumerableCount plainCollection = await Execute(qr, GetQueryDescription(), cancellationToken); - - var groupCollection = plainCollection - .GroupBy(keys, allAggregates) - .Where(aggregateFilters) - .OrderBy(request.Orders); - - var cols = groupCollection.TryPaginate(request.Pagination); - - return cols.ToResultTable(request); - } - - public override object? ExecuteQueryValue(QueryValueRequest request) => Task.Run(() => ExecuteQueryValueAsync(request, CancellationToken.None)); - public override async Task ExecuteQueryValueAsync(QueryValueRequest request, CancellationToken cancellationToken) - { - var req = new QueryRequest - { - QueryName = request.QueryName, - Filters = request.Filters, - Columns = new List(), - Orders = new List(), - Pagination = new Pagination.All(), - }; - - if (request.ValueToken == null || request.ValueToken is AggregateToken at && at.AggregateFunction == AggregateFunction.Count) - { - req.Pagination = new Pagination.Paginate(1, 1); - req.Columns.Add(new Column(this.EntityColumnFactory().BuildColumnDescription(), QueryName)); - var result = await Execute(req, GetQueryDescription(), cancellationToken); - return result.TotalElements!.Value; - } - - else if (request.ValueToken is AggregateToken agt) - { - var parent = request.ValueToken.Parent!; - req.Columns.Add(new Column(parent, parent.NiceName())); - var result = await Execute(req, GetQueryDescription(), cancellationToken); - return result.SimpleAggregate(agt); - } - else if(request.MultipleValues) - { - req.Columns.Add(new Column(request.ValueToken, request.ValueToken.NiceName())); - var result = await Execute(req, GetQueryDescription(), cancellationToken); - return result.SelectOne(request.ValueToken).ToList(); - } - else - { - req.Columns.Add(new Column(request.ValueToken, request.ValueToken.NiceName())); - var result = await Execute(req, GetQueryDescription(), cancellationToken); - return result.SelectOne(request.ValueToken).Unique(UniqueType.SingleOrDefault); - } - } - - - - public override Lite? ExecuteUniqueEntity(UniqueEntityRequest request) => Task.Run(() => ExecuteUniqueEntityAsync(request, CancellationToken.None)).Result; - public override async Task?> ExecuteUniqueEntityAsync(UniqueEntityRequest request, CancellationToken cancellationToken) - { - var req = new QueryRequest - { - QueryName = request.QueryName, - Filters = request.Filters, - Orders = request.Orders, - Columns = new List { new Column(this.EntityColumnFactory().BuildColumnDescription(), QueryName) }, - Pagination = new Pagination.Firsts(2), - }; - - DEnumerable mr = await Execute(req, GetQueryDescription(), cancellationToken); - - var lites = (IEnumerable?>)Untyped.Select(mr.Collection, mr.Context.GetEntitySelector().Compile()); - - return lites.Unique(request.UniqueType); - } - - static readonly Lazy>> entitySelector = new Lazy>>(() => - { - ParameterExpression pe = Expression.Parameter(typeof(object), "p"); - return Expression.Lambda>>(TupleReflection.TupleChainProperty(pe, 0), pe).Compile(); - }, true); - - public override IQueryable> GetEntitiesLite(QueryEntitiesRequest request) - { - throw new NotImplementedException(); - } - - public override IQueryable GetEntitiesFull(QueryEntitiesRequest request) - { - throw new NotImplementedException(); - } -} +using Signum.Entities.DynamicQuery; +using Signum.Utilities.Reflection; + +namespace Signum.Engine.DynamicQuery; + +public class ManualDynamicQueryCore : DynamicQueryCore +{ + public Func>> Execute { get; private set; } + + + public ManualDynamicQueryCore(Func>> execute) + { + this.Execute = execute ?? throw new ArgumentNullException(nameof(execute)); + + this.StaticColumns = MemberEntryFactory.GenerateList(MemberOptions.Properties | MemberOptions.Fields) + .Select((e, i) => new ColumnDescriptionFactory(i, e.MemberInfo, null)).ToArray(); + } + + + public override ResultTable ExecuteQuery(QueryRequest request) => Task.Run(() => ExecuteQueryAsync(request, CancellationToken.None)).Result; + public override async Task ExecuteQueryAsync(QueryRequest request, CancellationToken cancellationToken) + { + request.Columns.Insert(0, new _EntityColumn(EntityColumnFactory().BuildColumnDescription(), QueryName)); + + DEnumerableCount manualResult = await Execute(request, GetQueryDescription(), cancellationToken); + + return manualResult.ToResultTable(request); + } + + public override ResultTable ExecuteQueryGroup(QueryRequest request) => Task.Run(() => ExecuteQueryGroupAsync(request, CancellationToken.None)).Result; + public override async Task ExecuteQueryGroupAsync(QueryRequest request, CancellationToken cancellationToken) + { + var simpleFilters = request.Filters.Where(f => !f.IsAggregate()).ToList(); + var aggregateFilters = request.Filters.Where(f => f.IsAggregate()).ToList(); + + var keys = request.Columns.Select(t => t.Token).Where(t => !(t is AggregateToken)).ToHashSet(); + + var allAggregates = request.AllTokens().OfType().ToHashSet(); + + var qr = new QueryRequest + { + Columns = keys.Concat(allAggregates.Select(at => at.Parent).NotNull()).Distinct().Select(t => new Column(t, t.NiceName())).ToList(), + Orders = new List(), + Filters = simpleFilters, + QueryName = request.QueryName, + Pagination = new Pagination.All(), + }; + + DEnumerableCount plainCollection = await Execute(qr, GetQueryDescription(), cancellationToken); + + var groupCollection = plainCollection + .GroupBy(keys, allAggregates) + .Where(aggregateFilters) + .OrderBy(request.Orders); + + var cols = groupCollection.TryPaginate(request.Pagination); + + return cols.ToResultTable(request); + } + + public override object? ExecuteQueryValue(QueryValueRequest request) => Task.Run(() => ExecuteQueryValueAsync(request, CancellationToken.None)); + public override async Task ExecuteQueryValueAsync(QueryValueRequest request, CancellationToken cancellationToken) + { + var req = new QueryRequest + { + QueryName = request.QueryName, + Filters = request.Filters, + Columns = new List(), + Orders = new List(), + Pagination = new Pagination.All(), + }; + + if (request.ValueToken == null || request.ValueToken is AggregateToken at && at.AggregateFunction == AggregateFunction.Count) + { + req.Pagination = new Pagination.Paginate(1, 1); + req.Columns.Add(new Column(this.EntityColumnFactory().BuildColumnDescription(), QueryName)); + var result = await Execute(req, GetQueryDescription(), cancellationToken); + return result.TotalElements!.Value; + } + + else if (request.ValueToken is AggregateToken agt) + { + var parent = request.ValueToken.Parent!; + req.Columns.Add(new Column(parent, parent.NiceName())); + var result = await Execute(req, GetQueryDescription(), cancellationToken); + return result.SimpleAggregate(agt); + } + else if(request.MultipleValues) + { + req.Columns.Add(new Column(request.ValueToken, request.ValueToken.NiceName())); + var result = await Execute(req, GetQueryDescription(), cancellationToken); + return result.SelectOne(request.ValueToken).ToList(); + } + else + { + req.Columns.Add(new Column(request.ValueToken, request.ValueToken.NiceName())); + var result = await Execute(req, GetQueryDescription(), cancellationToken); + return result.SelectOne(request.ValueToken).Unique(UniqueType.SingleOrDefault); + } + } + + + + public override Lite? ExecuteUniqueEntity(UniqueEntityRequest request) => Task.Run(() => ExecuteUniqueEntityAsync(request, CancellationToken.None)).Result; + public override async Task?> ExecuteUniqueEntityAsync(UniqueEntityRequest request, CancellationToken cancellationToken) + { + var req = new QueryRequest + { + QueryName = request.QueryName, + Filters = request.Filters, + Orders = request.Orders, + Columns = new List { new Column(this.EntityColumnFactory().BuildColumnDescription(), QueryName) }, + Pagination = new Pagination.Firsts(2), + }; + + DEnumerable mr = await Execute(req, GetQueryDescription(), cancellationToken); + + var lites = (IEnumerable?>)Untyped.Select(mr.Collection, mr.Context.GetEntitySelector().Compile()); + + return lites.Unique(request.UniqueType); + } + + static readonly Lazy>> entitySelector = new Lazy>>(() => + { + ParameterExpression pe = Expression.Parameter(typeof(object), "p"); + return Expression.Lambda>>(TupleReflection.TupleChainProperty(pe, 0), pe).Compile(); + }, true); + + public override IQueryable> GetEntitiesLite(QueryEntitiesRequest request) + { + throw new NotImplementedException(); + } + + public override IQueryable GetEntitiesFull(QueryEntitiesRequest request) + { + throw new NotImplementedException(); + } +} diff --git a/Signum.Engine/DynamicQuery/Untyped.cs b/Signum.Engine/DynamicQuery/Untyped.cs new file mode 100644 index 0000000000..c93b21200b --- /dev/null +++ b/Signum.Engine/DynamicQuery/Untyped.cs @@ -0,0 +1,259 @@ +using Signum.Entities.DynamicQuery; +using Signum.Utilities.Reflection; +using Signum.Entities.Basics; +using System.Collections; + +namespace Signum.Engine.DynamicQuery; + +static class Untyped +{ + static MethodInfo miSelectQ = + ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Select((Expression>)null!)).GetGenericMethodDefinition(); + public static IQueryable Select(IQueryable query, LambdaExpression selector) + { + var types = selector.Type.GenericTypeArguments; + + var mi = miSelectQ.MakeGenericMethod(types); + + return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Quote(selector) })); + } + + static GenericInvoker> giSelectE = + new((q, selector) => ((IEnumerable)q).Select((Func)selector)); + public static IEnumerable Select(IEnumerable collection, Delegate selector) + { + var types = selector.GetType().GenericTypeArguments; + + return giSelectE.GetInvoker(types)(collection, selector); + } + + static MethodInfo miSelectManyQ = + ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).SelectMany((Expression>>)null!, (Expression>)null!)).GetGenericMethodDefinition(); + public static IQueryable SelectMany(IQueryable query, LambdaExpression collectionSelector, LambdaExpression resultSelector) + { + var types = resultSelector.Type.GenericTypeArguments; + + var mi = miSelectManyQ.MakeGenericMethod(types); + + return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Quote(collectionSelector), Expression.Quote(resultSelector) })); + } + + static GenericInvoker> giSelectManyE = + new((q, collectionSelector, resultSelector) => ((IEnumerable)q).SelectMany((Func>)collectionSelector, (Func)resultSelector)); + public static IEnumerable SelectMany(IEnumerable collection, Delegate collectionSelector, Delegate resultSelector) + { + var types = resultSelector.GetType().GenericTypeArguments; + + var mi = miSelectManyQ.MakeGenericMethod(types); + + return giSelectManyE.GetInvoker(types)(collection, collectionSelector, resultSelector); + } + + + static MethodInfo miWhereQ = + ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Where((Expression>)null!)).GetGenericMethodDefinition(); + public static IQueryable Where(IQueryable query, LambdaExpression predicate) + { + var types = query.GetType().GenericTypeArguments; + + var mi = miWhereQ.MakeGenericMethod(types); + + return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Quote(predicate) })); + } + + static GenericInvoker> giWhereE = + new((q, predicate) => ((IEnumerable)q).Where((Func)predicate)); + public static IEnumerable Where(IEnumerable collection, Delegate selector) + { + var types = selector.GetType().GenericTypeArguments; + + return giSelectE.GetInvoker(types)(collection, selector); + } + + static MethodInfo miDistinctQ = + ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Distinct()).GetGenericMethodDefinition(); + public static IQueryable Distinct(IQueryable query, Type elementType) + { + var mi = miDistinctQ.MakeGenericMethod(elementType); + + return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression })); + } + + static MethodInfo miOrderAlsoByKeysQ = + ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).OrderAlsoByKeys()).GetGenericMethodDefinition(); + public static IQueryable OrderAlsoByKeys(IQueryable query, Type elementType) + { + var mi = miOrderAlsoByKeysQ.MakeGenericMethod(elementType); + + return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression })); + } + + static GenericInvoker> giTakeE = + new((q, limit) => ((IEnumerable)q).Take(limit)); + public static IEnumerable Take(IEnumerable collection, int limit, Type elementType) + { + return giTakeE.GetInvoker(elementType)(collection, limit); + } + + static MethodInfo miTakeQ = + ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Take(3)).GetGenericMethodDefinition(); + public static IQueryable Take(IQueryable query, int limit, Type elementType) + { + var mi = miTakeQ.MakeGenericMethod(elementType); + + return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Constant(limit) })); + } + + static GenericInvoker> giSkipE = + new((q, limit) => ((IEnumerable)q).Skip(limit)); + public static IEnumerable Skip(IEnumerable collection, int limit, Type elementType) + { + return giSkipE.GetInvoker(elementType)(collection, limit); + } + + static MethodInfo miSkipQ = + ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Skip(3)).GetGenericMethodDefinition(); + public static IQueryable Skip(IQueryable query, int limit, Type elementType) + { + var mi = miSkipQ.MakeGenericMethod(elementType); + + return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Constant(limit) })); + } + + static GenericInvoker> giCountE = + new((q) => ((IEnumerable)q).Count()); + public static int Count(IEnumerable collection, Type elementType) + { + return giCountE.GetInvoker(elementType)(collection); + } + + + static MethodInfo miCountQ = + ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Count()).GetGenericMethodDefinition(); + public static int Count(IQueryable query, Type elementType) + { + var mi = miCountQ.MakeGenericMethod(elementType); + + return (int)query.Provider.Execute(Expression.Call(null, mi, new Expression[] { query.Expression }))!; + } + + public static async Task CountAsync(IQueryable query, CancellationToken token, Type elementType) + { + var mi = miCountQ.MakeGenericMethod(elementType); + + var result = await ((IQueryProviderAsync)query.Provider).ExecuteAsync(Expression.Call(null, mi, new Expression[] { query.Expression }), token)!; + + return (int)result!; + } + + static MethodInfo miConcatQ = + ReflectionTools.GetMethodInfo(() => ((IQueryable)null!).Concat((IQueryable)null!)).GetGenericMethodDefinition(); + public static IQueryable Concat(IQueryable query, IQueryable query2, Type elementType) + { + var mi = miConcatQ.MakeGenericMethod(elementType); + + return query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, query2.Expression })); + } + + static GenericInvoker> gConcatE = + new((q, q2) => ((IEnumerable)q).Concat((IEnumerable)q2)); + + public static IEnumerable Concat(IEnumerable collection, IEnumerable collection2, Type elementType) + { + return gConcatE.GetInvoker(elementType)(collection, collection2); + } + + static GenericInvoker> gToArrayE = + new((q) => ((IEnumerable)q).ToArray()); + public static Array ToArray(IEnumerable collection, Type elementType) + { + return gToArrayE.GetInvoker(elementType)(collection); + } + + static GenericInvoker> gToListE = + new((q) => ((IEnumerable)q).ToList()); + + public static IList ToList(IEnumerable collection, Type elementType) + { + return gToListE.GetInvoker(elementType)(collection); + } + + static GenericInvoker>> gToListAsyncQ = + new((q, token) => ToIListAsync((IQueryable)q, token)); + + public static Task ToListAsync(IQueryable query, CancellationToken token, Type elementType) + { + return gToListAsyncQ.GetInvoker(elementType)(query, token); + } + + static async Task ToIListAsync(IQueryable query, CancellationToken token) + { + return await query.ToListAsync(token); + } + + static readonly GenericInvoker> giOrderByE = new((col, del) => ((IEnumerable)col).OrderBy((Func)del)); + static readonly GenericInvoker> giOrderByDescendingE = new((col, del) => ((IEnumerable)col).OrderByDescending((Func)del)); + public static IEnumerable OrderBy(IEnumerable collection, LambdaExpression lambda, OrderType orderType) + { + var mi = orderType == OrderType.Ascending ? giOrderByE : giOrderByDescendingE; + + return mi.GetInvoker(lambda.Type.GetGenericArguments())(collection, lambda.Compile()); + } + + static readonly GenericInvoker> giThenByE = new((col, del) => ((IOrderedEnumerable)col).ThenBy((Func)del)); + static readonly GenericInvoker> giThenByDescendingE = new((col, del) => ((IOrderedEnumerable)col).ThenByDescending((Func)del)); + public static IEnumerable ThenBy(IEnumerable collection, LambdaExpression lambda, OrderType orderType) + { + var mi = orderType == OrderType.Ascending ? giThenByE : giThenByDescendingE; + + return mi.GetInvoker(lambda.Type.GetGenericArguments())(collection, lambda.Compile()); + } + + public static IEnumerable OrderBy(IEnumerable collection, List<(LambdaExpression lambda, OrderType orderType)> orders) + { + if (orders == null || orders.Count == 0) + return collection; + + IEnumerable result = Untyped.OrderBy(collection, orders[0].lambda, orders[0].orderType); + + foreach (var (lambda, orderType) in orders.Skip(1)) + { + result = Untyped.ThenBy(result, lambda, orderType); + } + + return result; + } + + static MethodInfo miOrderByQ = ReflectionTools.GetMethodInfo(() => Database.Query().OrderBy(t => t.Id)).GetGenericMethodDefinition(); + static MethodInfo miOrderByDescendingQ = ReflectionTools.GetMethodInfo(() => Database.Query().OrderByDescending(t => t.Id)).GetGenericMethodDefinition(); + public static IOrderedQueryable OrderBy(IQueryable query, LambdaExpression lambda, OrderType orderType) + { + MethodInfo mi = (orderType == OrderType.Ascending ? miOrderByQ : miOrderByDescendingQ).MakeGenericMethod(lambda.Type.GetGenericArguments()); + + return (IOrderedQueryable)query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Quote(lambda) })); + } + + static MethodInfo miThenByQ = ReflectionTools.GetMethodInfo(() => Database.Query().OrderBy(t => t.Id).ThenBy(t => t.Id)).GetGenericMethodDefinition(); + static MethodInfo miThenByDescendingQ = ReflectionTools.GetMethodInfo(() => Database.Query().OrderBy(t => t.Id).ThenByDescending(t => t.Id)).GetGenericMethodDefinition(); + public static IOrderedQueryable ThenBy(IOrderedQueryable query, LambdaExpression lambda, OrderType orderType) + { + MethodInfo mi = (orderType == OrderType.Ascending ? miThenByQ : miThenByDescendingQ).MakeGenericMethod(lambda.Type.GetGenericArguments()); + + return (IOrderedQueryable)query.Provider.CreateQuery(Expression.Call(null, mi, new Expression[] { query.Expression, Expression.Quote(lambda) })); + } + + public static IQueryable OrderBy(IQueryable query, List<(LambdaExpression lambda, OrderType orderType)> orders) + { + if (orders == null || orders.Count == 0) + return query; + + IOrderedQueryable result = Untyped.OrderBy(query, orders[0].lambda, orders[0].orderType); + + foreach (var (lambda, orderType) in orders.Skip(1)) + { + result = Untyped.ThenBy(result, lambda, orderType); + } + + return result; + } +} diff --git a/Signum.Entities/DynamicQuery/Requests.cs b/Signum.Entities/DynamicQuery/Requests.cs index 63e5904d29..1a397e3df4 100644 --- a/Signum.Entities/DynamicQuery/Requests.cs +++ b/Signum.Entities/DynamicQuery/Requests.cs @@ -30,6 +30,13 @@ public class QueryRequest : BaseQueryRequest public SystemTime? SystemTime { get; set; } + public bool MultiplicationsInSubQueries() + { + return GroupResults == false && Pagination is Pagination.All && + Orders.Select(a => a.Token).Concat(Columns.Select(a => a.Token)).Any(a => a.HasElement()) && + !Filters.SelectMany(a => a.GetFilterConditions()).Select(a => a.Token).Any(t => t.HasElement()); + } + public List Multiplications() { HashSet allTokens = new HashSet(this.AllTokens()); diff --git a/Signum.Entities/DynamicQuery/Tokens/QueryToken.cs b/Signum.Entities/DynamicQuery/Tokens/QueryToken.cs index 27e8e3921c..0c218a2eae 100644 --- a/Signum.Entities/DynamicQuery/Tokens/QueryToken.cs +++ b/Signum.Entities/DynamicQuery/Tokens/QueryToken.cs @@ -507,14 +507,22 @@ public BuildExpressionContext(Type elementType, ParameterExpression parameter, D public readonly ParameterExpression Parameter; public readonly Dictionary Replacements; - public Expression>> GetEntitySelector() + public LambdaExpression GetEntitySelector() { - return Expression.Lambda>>(Replacements.Single(a=>a.Key.FullKey() == "Entity").Value.GetExpression(), Parameter); + return Expression.Lambda(Replacements.Single(a=>a.Key.FullKey() == "Entity").Value.GetExpression(), Parameter); } - public Expression> GetEntityFullSelector() + public LambdaExpression GetEntityFullSelector() { - return Expression.Lambda>(Replacements.Single(a => a.Key.FullKey() == "Entity").Value.GetExpression().ExtractEntity(false), Parameter); + return Expression.Lambda(Replacements.Single(a => a.Key.FullKey() == "Entity").Value.GetExpression().ExtractEntity(false), Parameter); + } + + internal List SubQueries() + { + return Replacements + .Where(a => a.Value.SubQueryContext != null) + .Select(a => (CollectionElementToken)a.Key) + .ToList(); } } @@ -522,9 +530,13 @@ public struct ExpressionBox { public readonly Expression RawExpression; public readonly PropertyRoute? MListElementRoute; + public readonly BuildExpressionContext? SubQueryContext; + + public ExpressionBox(Expression rawExpression, PropertyRoute? mlistElementRoute = null, BuildExpressionContext? subQueryContext = null) { this.RawExpression = rawExpression; this.MListElementRoute = mlistElementRoute; + this.SubQueryContext = subQueryContext; } public Expression GetExpression() @@ -534,6 +546,11 @@ public Expression GetExpression() return RawExpression; } + + public override string ToString() + { + return new { RawExpression, MListElementRoute, SubQueryContext }.ToString()!; + } }