Beispiel #1
0
        /// <summary>
        /// Loads entities referenced by a collection navigation property from database, unless data is already loaded.
        /// </summary>
        /// <param name="entity">Entity instance to load data for.</param>
        /// <param name="navigationProperty">The navigation property expression.</param>
        /// <param name="force"><c>false:</c> do nothing if data is already loaded. <c>true:</c> reload data even if already loaded.</param>
        /// <param name="queryModifier">Modifier for the query that is about to be executed against the database.</param>
        /// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
        public static async Task <CollectionEntry <TEntity, TCollection> > LoadCollectionAsync <TEntity, TCollection>(
            this HookingDbContext ctx,
            TEntity entity,
            Expression <Func <TEntity, IEnumerable <TCollection> > > navigationProperty,
            bool force = false,
            Func <IQueryable <TCollection>, IQueryable <TCollection> > queryModifier = null,
            CancellationToken cancelToken = default)
            where TEntity : BaseEntity
            where TCollection : BaseEntity
        {
            Guard.NotNull(entity, nameof(entity));
            Guard.NotNull(navigationProperty, nameof(navigationProperty));

            if (entity.Id == 0)
            {
                return(null);
            }

            var entry = ctx.Entry(entity);

            if (entry.State == EfState.Deleted)
            {
                return(null);
            }

            var collection = entry.Collection(navigationProperty);
            // TODO: (Core) Entities with hashSets as collections always return true here, as they are never null (for example: Product.ProductVariantAttributes).
            var isLoaded = collection.CurrentValue != null || collection.IsLoaded;

            if (!isLoaded && entry.State == EfState.Detached)
            {
                try
                {
                    // Attaching an entity while another instance with same primary key is attached will throw.
                    // First we gonna try to locate an already attached entity.
                    var other = ctx.FindTracked <TEntity>(entity.Id);
                    if (other != null)
                    {
                        // An entity with same key is attached already. So we gonna load the navigation property of attached entity
                        // and copy the result to this detached entity. This way we don't need to attach the source entity.
                        var otherCollection = await ctx.LoadCollectionAsync(other, navigationProperty, force, queryModifier, cancelToken : cancelToken);

                        // Copy collection over to detached entity.
                        collection.CurrentValue = otherCollection.CurrentValue;
                        collection.IsLoaded     = true;
                        isLoaded = true;
                        force    = false;
                    }
                    else
                    {
                        ctx.Attach(entity);
                    }
                }
                catch
                {
                    // Attach may throw!
                }
            }

            if (force)
            {
                collection.IsLoaded = false;
                isLoaded            = false;
            }

            if (!isLoaded)
            {
                if (queryModifier != null)
                {
                    var query = queryModifier(collection.Query());
                    collection.CurrentValue = await query.ToListAsync(cancellationToken : cancelToken);
                }
                else
                {
                    await collection.LoadAsync(cancelToken);
                }

                collection.IsLoaded = true;
            }

            return(collection);
        }