Пример #1
0
        /// <summary>
        /// Applies a <see cref="ExpressionOrderBy"/> to set the order of the result, it is used in
        /// conjunction with <see cref="Top(int)"/> and <see cref="Skip(int)"/> to implement paging
        /// </summary>
        public Query <T> OrderBy(ExpressionOrderBy orderby)
        {
            var clone = Clone();

            clone._orderby = orderby;
            return(clone);
        }
Пример #2
0
        /// <summary>
        /// Backbone for <see cref="CountAsync(CancellationToken, int)"/>, <see cref="ToListAsync(CancellationToken)"/> and <see cref="ToListAndCountAsync(CancellationToken)"/>
        /// </summary>
        private async Task <(List <T> result, int count)> ToListAndCountInnerAsync(bool includeCount, int maxCount, CancellationToken cancellation)
        {
            var args = await _factory(cancellation);

            var conn      = args.Connection;
            var sources   = args.Sources;
            var userId    = args.UserId;
            var userToday = args.UserToday;
            var localizer = args.Localizer;
            var i         = args.Instrumentation;
            var logger    = args.Logger;

            using var _ = i.Block("Query.ToListAndCountInnerAsync");
            IDisposable block;

            block = i.Block("Get Descriptor");
            var resultDesc = TypeDescriptor.Get <T>();

            block.Dispose();
            block = i.Block("Validate Paths and Props");

            _orderby ??= (IsEntityWithKey() ? ExpressionOrderBy.Parse("Id desc") :
                          throw new InvalidOperationException($"Query<{resultDesc.Type.Name}> was executed without an orderby clause"));

            // Prepare all the query parameters
            ExpressionSelect  selectExp  = _select;
            ExpressionExpand  expandExp  = _expand;
            ExpressionOrderBy orderbyExp = _orderby;
            ExpressionFilter  filterExp  = _filterConditions?.Aggregate(
                (e1, e2) => ExpressionFilter.Conjunction(e1, e2));

            // To prevent SQL injection
            ValidatePathsAndProperties(selectExp, expandExp, filterExp, orderbyExp, resultDesc, localizer);

            // ------------------------ Step #1

            block.Dispose();
            block = i.Block("Prepare QueryInternals");

            // Segment the paths of select and expand along the one-to-many relationships, each one-to-many relationship will
            // result in a new internal query for the child collection with the original query as its principal query
            var segments = new Dictionary <ArraySegment <string>, QueryInternal>(new PathEqualityComparer());

            // Helper method for creating a an internal query, will be used later in both the select and the expand loops
            QueryInternal MakeFlatQuery(ArraySegment <string> previousFullPath, ArraySegment <string> subPath, TypeDescriptor desc)
            {
                QueryInternal         principalQuery = previousFullPath == null ? null : segments[previousFullPath];
                ArraySegment <string> pathToCollectionPropertyInPrincipal = previousFullPath == null ? null : subPath;

                if (principalQuery != null && desc.KeyType == KeyType.None)
                {
                    // Programmer mistake
                    throw new InvalidOperationException($"[Bug] Type {desc.Name} has no Id property, yet it is used as a navigation collection on another entity");
                }

                string foreignKeyToPrincipalQuery = null;
                bool   isAncestorExpand           = false;

                if (principalQuery != null)
                {
                    // This loop retrieves the entity descriptor that has the collection property
                    TypeDescriptor collectionPropertyEntity = principalQuery.ResultDescriptor;
                    int            i = 0;
                    for (; i < pathToCollectionPropertyInPrincipal.Count - 1; i++)
                    {
                        var step = pathToCollectionPropertyInPrincipal[i];
                        collectionPropertyEntity = collectionPropertyEntity.NavigationProperty(step).TypeDescriptor;
                    }

                    // Get the collection/Parent property
                    string propertyName = pathToCollectionPropertyInPrincipal[i];
                    var    property     = collectionPropertyEntity.Property(propertyName);

                    if (property is NavigationPropertyDescriptor navProperty && navProperty.IsParent)
                    {
                        foreignKeyToPrincipalQuery = "ParentId";
                        isAncestorExpand           = true;
                    }
                    else if (property is CollectionPropertyDescriptor collProperty)
                    {
                        // Must be a collection then
                        foreignKeyToPrincipalQuery = collProperty.ForeignKeyName;
                    }
                    else
                    {
                        throw new InvalidOperationException($"Bug: Segment along a property {property.Name} on type {collectionPropertyEntity.Name} That is neither a collection nor a parent");
                    }
                }

                if (isAncestorExpand)
                {
                    // the path to parent entity is the path above minus the "Parent"
                    var pathToParentEntity = new ArraySegment <string>(
                        array: pathToCollectionPropertyInPrincipal.Array,
                        offset: 0,
                        count: pathToCollectionPropertyInPrincipal.Count - 1);

                    // Adding this causes the principal query to always include ParentId in the select clause
                    principalQuery.PathsToParentEntitiesWithExpandedAncestors.Add(pathToParentEntity);
                }

                // This is the orderby of related queries, and the default orderby of the root query
                var defaultOrderBy = ExpressionOrderBy.Parse(
                    desc.HasProperty("Index") ? "Index" :
                    desc.HasProperty("SortKey") ? "SortKey" : "Id");

                // Prepare the flat query and return it
                var flatQuery = new QueryInternal
                {
                    PrincipalQuery   = principalQuery,
                    IsAncestorExpand = isAncestorExpand,
                    PathToCollectionPropertyInPrincipal = pathToCollectionPropertyInPrincipal,
                    ForeignKeyToPrincipalQuery          = foreignKeyToPrincipalQuery,
                    ResultDescriptor = desc,
                    OrderBy          = defaultOrderBy
                };

                return(flatQuery);
            }
Пример #3
0
 /// <summary>
 /// A version of <see cref="OrderBy(ExpressionOrderBy)" that accepts a string
 /// </summary>
 public Query <T> OrderBy(string orderBy)
 {
     return(OrderBy(ExpressionOrderBy.Parse(orderBy)));
 }