예제 #1
0
        /// <summary>
        /// Applies an <see cref="ExpressionExpand"/> on the <see cref="EntityQuery{T}"/> to determine what related tables to
        /// include in the result, any tables touched by the <see cref="ExpressionSelect"/> will have it overriding the
        /// <see cref="ExpressionExpand"/>.
        /// </summary>
        public EntityQuery <T> Expand(ExpressionExpand expand)
        {
            var clone = Clone();

            clone._expand = expand;
            return(clone);
        }
예제 #2
0
        /// <summary>
        /// Backbone for <see cref="CountAsync(int, QueryContext, CancellationToken)"/>,
        /// <see cref="ToListAsync(QueryContext, CancellationToken)"/> and
        /// <see cref="ToListAndCountAsync(int, QueryContext, CancellationToken)"/>.
        /// </summary>
        private async Task <EntityOutput <T> > ToListAndCountInnerAsync(bool includeResult, bool includeCount, int maxCount, QueryContext ctx, CancellationToken cancellation)
        {
            var queryArgs = await _factory(cancellation);

            var connString = queryArgs.ConnectionString;
            var sources    = queryArgs.Sources;
            var loader     = queryArgs.Loader;

            var userId    = ctx.UserId;
            var userToday = ctx.UserToday;

            var resultDesc = TypeDescriptor.Get <T>();

            _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  = _filter;

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

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

            // 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>, EntityQueryInternal>(new PathEqualityComparer());

            // Helper method for creating a an internal query, will be used later in both the select and the expand loops
            EntityQueryInternal MakeQueryInternal(ArraySegment <string> previousFullPath, ArraySegment <string> subPath, TypeDescriptor desc)
            {
                EntityQueryInternal   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 EntityQueryInternal
                {
                    PrincipalQuery   = principalQuery,
                    IsAncestorExpand = isAncestorExpand,
                    PathToCollectionPropertyInPrincipal = pathToCollectionPropertyInPrincipal,
                    ForeignKeyToPrincipalQuery          = foreignKeyToPrincipalQuery,
                    ResultDescriptor = desc,
                    OrderBy          = defaultOrderBy
                };

                return(flatQuery);
            }
예제 #3
0
 /// <summary>
 /// A version of <see cref="Expand(ExpressionExpand)"/> that accepts a string.
 /// </summary>
 public EntityQuery <T> Expand(string expand)
 {
     return(Expand(ExpressionExpand.Parse(expand)));
 }