Beispiel #1
0
        public static bool IsReferenceLoaded <TEntity, TProperty>(
            this HookingDbContext ctx,
            TEntity entity,
            Expression <Func <TEntity, TProperty> > navigationProperty,
            out ReferenceEntry <TEntity, TProperty> referenceEntry)
            where TEntity : BaseEntity
            where TProperty : BaseEntity
        {
            Guard.NotNull(entity, nameof(entity));
            Guard.NotNull(navigationProperty, nameof(navigationProperty));

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

            var entry = ctx.Entry(entity);

            referenceEntry = entry.Reference(navigationProperty);

            // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property...
            if (entry.State == EfState.Detached || entry.State == EfState.Added)
            {
                return(referenceEntry.CurrentValue != null);
            }

            return(referenceEntry.IsLoaded);
        }
        private static int DetachInternal(this HookingDbContext ctx, BaseEntity obj, ISet <BaseEntity> objSet, bool deep)
        {
            if (obj == null)
            {
                return(0);
            }

            return(ctx.DetachInternal(ctx.Entry(obj), objSet, deep));
        }
Beispiel #3
0
        public static bool IsReferenceLoaded <TEntity, TProperty>(
            this HookingDbContext ctx,
            TEntity entity,
            Expression <Func <TEntity, TProperty> > navigationProperty,
            out ReferenceEntry <TEntity, TProperty> referenceEntry)
            where TEntity : BaseEntity
            where TProperty : BaseEntity
        {
            Guard.NotNull(entity, nameof(entity));
            Guard.NotNull(navigationProperty, nameof(navigationProperty));

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

            var entry = ctx.Entry(entity);

            referenceEntry = entry.Reference(navigationProperty);
            var isLoaded = referenceEntry.CurrentValue != null || referenceEntry.IsLoaded;

            // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property...
            if (!isLoaded && (entry.State == EfState.Detached))
            {
                // 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)
                {
                    referenceEntry = ctx.Entry(entity).Reference(navigationProperty);
                }
                else
                {
                    ctx.Attach(entity);
                }
            }

            return(isLoaded);
        }
        /// <summary>
        /// Sets the state of an entity to <see cref="EfState.Modified"/> if it is detached.
        /// </summary>
        /// <typeparam name="TEntity">Type of entity</typeparam>
        /// <param name="entity">The entity instance</param>
        /// <returns><c>true</c> if the state has been changed, <c>false</c> if entity is attached already.</returns>
        public static bool TryUpdate <TEntity>(this HookingDbContext ctx, TEntity entity) where TEntity : BaseEntity
        {
            var detectChanges = ctx.ChangeTracker.AutoDetectChangesEnabled;

            ctx.ChangeTracker.AutoDetectChangesEnabled = false;

            using (new ActionDisposable(() => ctx.ChangeTracker.AutoDetectChangesEnabled = detectChanges))
            {
                // (perf) turning off autoDetectChanges prevents that ctx.Entry() performs change detection internally.
                var entry = ctx.Entry(entity);
                return(entry.TryUpdate());
            }
        }
Beispiel #5
0
        /// <summary>
        /// Change the state of an entity object
        /// </summary>
        /// <typeparam name="TEntity">Type of entity</typeparam>
        /// <param name="entity">The entity instance</param>
        /// <param name="requestedState">The requested new state</param>
        public static void ChangeState <TEntity>(this HookingDbContext ctx, TEntity entity, EfState requestedState) where TEntity : BaseEntity
        {
            //Console.WriteLine("ChangeState ORIGINAL");
            var entry = ctx.Entry(entity);

            if (entry.State != requestedState)
            {
                // Only change state when requested state differs,
                // because EF internally sets all properties to modified
                // if necessary, even when requested state equals current state.
                entry.State = requestedState;
            }
        }
        /// <summary>
        /// Determines whether an entity property has changed since it was attached.
        /// </summary>
        /// <param name="entity">Entity</param>
        /// <param name="propertyName">The property name to check</param>
        /// <param name="originalValue">The previous/original property value if change was detected</param>
        /// <returns><c>true</c> if property has changed, <c>false</c> otherwise</returns>
        public static bool TryGetModifiedProperty(this HookingDbContext ctx, BaseEntity entity, string propertyName, out object originalValue)
        {
            Guard.NotNull(entity, nameof(entity));

            if (entity.IsTransientRecord())
            {
                originalValue = null;
                return(false);
            }

            var entry = ctx.Entry((object)entity);

            return(entry.TryGetModifiedProperty(propertyName, out originalValue));
        }
        /// <summary>
        /// Loads an entity referenced by a 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 event if loaded already.</param>
        /// <param name="queryModifier">Modifier for the query that is about to be executed against the database.</param>
        public static async Task LoadReferenceAsync <TEntity, TProperty>(
            this HookingDbContext ctx,
            TEntity entity,
            Expression <Func <TEntity, TProperty> > navigationProperty,
            bool force = false,
            Func <IQueryable <TProperty>, IQueryable <TProperty> > queryModifier = null)
            where TEntity : BaseEntity
            where TProperty : BaseEntity
        {
            Guard.NotNull(entity, nameof(entity));
            Guard.NotNull(navigationProperty, nameof(navigationProperty));

            var entry     = ctx.Entry(entity);
            var reference = entry.Reference(navigationProperty);

            // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property...
            if (entry.State == EfState.Detached)
            {
                try
                {
                    ctx.Attach(entity);
                }
                catch
                {
                    // Attach may throw!
                }
            }

            if (force)
            {
                reference.IsLoaded = false;
            }

            if (!reference.IsLoaded)
            {
                if (queryModifier != null)
                {
                    var query = queryModifier(reference.Query());
                    reference.CurrentValue = await query.FirstOrDefaultAsync();
                }
                else
                {
                    await reference.LoadAsync();
                }

                reference.IsLoaded = true;
            }
        }
        /// <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 event if loaded already.</param>
        /// <param name="queryModifier">Modifier for the query that is about to be executed against the database.</param>
        public static async Task LoadCollectionAsync <TEntity, TCollection>(
            this HookingDbContext ctx,
            TEntity entity,
            Expression <Func <TEntity, IEnumerable <TCollection> > > navigationProperty,
            bool force = false,
            Func <IQueryable <TCollection>, IQueryable <TCollection> > queryModifier = null)
            where TEntity : BaseEntity
            where TCollection : BaseEntity
        {
            Guard.NotNull(entity, nameof(entity));
            Guard.NotNull(navigationProperty, nameof(navigationProperty));

            var entry      = ctx.Entry(entity);
            var collection = entry.Collection(navigationProperty);

            // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property...
            if (entry.State == EfState.Detached)
            {
                try
                {
                    ctx.Attach(entity);
                }
                catch
                {
                    // Attach may throw!
                }
            }

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

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

                collection.IsLoaded = true;
            }
        }
Beispiel #9
0
        public static bool IsCollectionLoaded <TEntity, TCollection>(
            this HookingDbContext ctx,
            TEntity entity,
            Expression <Func <TEntity, IEnumerable <TCollection> > > navigationProperty)
            where TEntity : BaseEntity
            where TCollection : BaseEntity
        {
            Guard.NotNull(entity, nameof(entity));
            Guard.NotNull(navigationProperty, nameof(navigationProperty));

            var entry      = ctx.Entry(entity);
            var collection = entry.Collection(navigationProperty);

            // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property...
            if (entry.State == EfState.Detached)
            {
                return(false);
            }

            return(collection.IsLoaded);
        }
 /// <summary>
 /// Reloads the entity from the database overwriting any property values with values from the database.
 /// The entity will be in the Unchanged state after calling this method.
 /// </summary>
 /// <typeparam name="TEntity">Type of entity</typeparam>
 /// <param name="entity">The entity instance</param>
 public static Task ReloadEntityAsync <TEntity>(this HookingDbContext ctx, TEntity entity, CancellationToken cancelToken = default) where TEntity : BaseEntity
 {
     return(ctx.Entry((object)entity).ReloadEntityAsync(cancelToken));
 }
 /// <summary>
 /// Reloads the entity from the database overwriting any property values with values from the database.
 /// The entity will be in the Unchanged state after calling this method.
 /// </summary>
 /// <typeparam name="TEntity">Type of entity</typeparam>
 /// <param name="entity">The entity instance</param>
 public static void ReloadEntity <TEntity>(this HookingDbContext ctx, TEntity entity) where TEntity : BaseEntity
 {
     ctx.Entry((object)entity).ReloadEntity();
 }
 /// <summary>
 /// Gets a list of modified properties for the specified entity
 /// </summary>
 /// <param name="entity">The entity instance for which to get modified properties for</param>
 /// <returns>
 /// A dictionary, where the key is the name of the modified property
 /// and the value is its ORIGINAL value (which was tracked when the entity
 /// was attached to the context the first time)
 /// Returns an empty dictionary if no modification could be detected.
 /// </returns>
 public static IDictionary <string, object> GetModifiedProperties(this HookingDbContext ctx, BaseEntity entity)
 {
     return(ctx.Entry((object)entity).GetModifiedProperties());
 }
Beispiel #13
0
        /// <summary>
        /// Loads an entity referenced by a 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 event if loaded already.</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 <ReferenceEntry <TEntity, TProperty> > LoadReferenceAsync <TEntity, TProperty>(
            this HookingDbContext ctx,
            TEntity entity,
            Expression <Func <TEntity, TProperty> > navigationProperty,
            bool force = false,
            Func <IQueryable <TProperty>, IQueryable <TProperty> > queryModifier = null,
            CancellationToken cancelToken = default)
            where TEntity : BaseEntity
            where TProperty : 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 reference = entry.Reference(navigationProperty);
            var isLoaded  = reference.CurrentValue != null || reference.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 reference property of attached entity
                        // and copy the result to this detached entity. This way we don't need to attach the source entity.
                        var otherReference = await ctx.LoadReferenceAsync(other, navigationProperty, force, queryModifier, cancelToken : cancelToken);

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

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

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

                reference.IsLoaded = true;
            }

            return(reference);
        }
Beispiel #14
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);
        }