/// <summary>
        /// Detaches all entities matching the passed <paramref name="predicate"/> from the current object context
        /// </summary>
        /// <param name="unchangedEntitiesOnly">When <c>true</c>, only entities in unchanged state will be detached.</param>
        /// <param name="deep">Whether to scan all navigation properties and detach them recursively also.</param>
        /// <returns>The count of detached entities</returns>
        public static int DetachEntities(this HookingDbContext ctx, Func <BaseEntity, bool> predicate, bool unchangedEntitiesOnly = true, bool deep = false)
        {
            Guard.NotNull(predicate, nameof(predicate));

            var numDetached = 0;

            using (new DbContextScope(ctx, autoDetectChanges: false, lazyLoading: false))
            {
                var entries = ctx.ChangeTracker.Entries <BaseEntity>().Where(Match).ToList();

                HashSet <BaseEntity> objSet = deep ? new HashSet <BaseEntity>() : null;

                foreach (var entry in entries)
                {
                    numDetached += ctx.DetachInternal(entry, objSet, deep);
                }

                return(numDetached);
            }

            bool Match(EntityEntry <BaseEntity> entry)
            {
                if (entry.State > EfState.Detached && predicate(entry.Entity))
                {
                    return(unchangedEntitiesOnly
                        ? entry.State == EfState.Unchanged
                        : true);
                }

                return(false);
            }
        }
        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));
        }
        private static int DetachInternal(this HookingDbContext ctx, EntityEntry <BaseEntity> entry, ISet <BaseEntity> objSet, bool deep)
        {
            var obj         = entry.Entity;
            int numDetached = 0;

            if (deep)
            {
                // This is to prevent an infinite recursion when the child object has a navigation property
                // that points back to the parent
                if (objSet != null && !objSet.Add(obj))
                {
                    return(0);
                }

                // Recursively detach all navigation properties
                foreach (var prop in FastProperty.GetProperties(obj.GetType()).Values)
                {
                    if (typeof(BaseEntity).IsAssignableFrom(prop.Property.PropertyType))
                    {
                        numDetached += ctx.DetachInternal(prop.GetValue(obj) as BaseEntity, objSet, deep);
                    }
                    else if (typeof(IEnumerable <BaseEntity>).IsAssignableFrom(prop.Property.PropertyType))
                    {
                        var val = prop.GetValue(obj);
                        if (val is IEnumerable <BaseEntity> list)
                        {
                            foreach (var item in list.ToList())
                            {
                                numDetached += ctx.DetachInternal(item, objSet, deep);
                            }
                        }
                    }
                }
            }

            entry.State = EfState.Detached;
            numDetached++;

            return(numDetached);
        }
 public static int DetachEntity <TEntity>(this HookingDbContext ctx, TEntity entity, bool deep = false) where TEntity : BaseEntity
 {
     return(ctx.DetachInternal(entity, deep ? new HashSet <BaseEntity>() : null, deep));
 }