private async ValueTask <object> LoadNavigationSingleton(object principalInstance, INavigation navigation, byte cascadeLevel)
        {
            byte levelToLookFor = _whatDoing == CascadeSoftDelWhatDoing.SoftDelete
                ? (byte)0                     //if soft deleting then look for un-deleted entries
                : (byte)(cascadeLevel + 1);   //otherwise look for next level up

            //for everything else we need to load the singleton with a IgnoreQueryFilters method
            var navValueType      = navigation.PropertyInfo.PropertyType;
            var genericHelperType =
                typeof(GenericSingletonLoader <>).MakeGenericType(typeof(TInterface), navValueType);

            dynamic loader = Activator.CreateInstance(genericHelperType, _context, _config, _isAsync,
                                                      principalInstance, navigation.PropertyInfo, levelToLookFor);

            var navValueTask = loader.GetFilteredSingleton();

            if (_isAsync)
            {
                return(await navValueTask);
            }

            return(ValueTaskSyncCheckers.CheckSyncValueTaskWorkedDynamicAndReturnResult <object>(navValueTask));
        }
        private async ValueTask <IEnumerable> LoadNavigationCollection(object principalInstance, INavigation navigation,
                                                                       byte cascadeLevel)
        {
            byte levelToLookFor = _whatDoing == CascadeSoftDelWhatDoing.SoftDelete
                ? (byte)0                     //if soft deleting then look for un-deleted entries
                : (byte)(cascadeLevel + 1);   //otherwise look for next level up

            var navValueType      = navigation.PropertyInfo.PropertyType;
            var innerType         = navValueType.GetGenericArguments().Single();
            var genericHelperType =
                typeof(GenericCollectionLoader <>).MakeGenericType(typeof(TInterface), innerType);

            dynamic loader = Activator.CreateInstance(genericHelperType, _context, _config, _isAsync,
                                                      principalInstance, navigation.PropertyInfo, levelToLookFor);
            var navValueTask = loader.GetFilteredEntities();

            if (_isAsync)
            {
                return(await navValueTask);
            }

            return(ValueTaskSyncCheckers.CheckSyncValueTaskWorkedDynamicAndReturnResult <IEnumerable>(navValueTask));
        }
        public async ValueTask WalkEntitiesSoftDelete(object principalInstance, byte cascadeLevel)
        {
            if (!(principalInstance is TInterface castToCascadeSoftDelete && principalInstance.GetType().IsClass) || _stopCircularLook.Contains(principalInstance))
            {
                return;                               //isn't something we need to consider, or we saw it before, so it returns
            }
            _stopCircularLook.Add(principalInstance); //we keep a reference to this to stop the method going in a circular loop

            if (ApplyChangeIfAppropriate(castToCascadeSoftDelete, cascadeLevel))
            {
                //If the entity shouldn't be changed then we leave this entity and any of it children
                return;
            }

            var principalNavs = _context.Entry(principalInstance)
                                .Metadata.GetNavigations()
                                .Where(x => !x.IsOnDependent && //navigational link goes to dependent entity(s)
                                                                //The developer has whatDoing a Cascade delete behaviour (two options) on this link
                                       (x.ForeignKey.DeleteBehavior == DeleteBehavior.ClientCascade || x.ForeignKey.DeleteBehavior == DeleteBehavior.Cascade))
                                .ToList();

            foreach (var navigation in principalNavs)
            {
                if (navigation.PropertyInfo == null)
                {
                    //This could be changed by enhancing the navigation.PropertyInfo.GetValue(principalInstance);
                    throw new NotImplementedException("Currently only works with navigation links that are properties");
                }

                //It loads the current navigational value so that we can limit the number of database selects if the data is already loaded
                var navValue = navigation.PropertyInfo.GetValue(principalInstance);
                if (navigation.IsCollection)
                {
                    if (_readEveryTime || navValue == null)
                    {
                        var navValueTask = LoadNavigationCollection(principalInstance, navigation, cascadeLevel);
                        if (_isAsync)
                        {
                            navValue = await navValueTask;
                        }
                        else
                        {
                            navValue = ValueTaskSyncCheckers.CheckSyncValueTaskWorkedDynamicAndReturnResult <IEnumerable>(navValueTask);
                        }
                    }
                    if (navValue == null)
                    {
                        return; //no relationship
                    }
                    foreach (var entity in navValue as IEnumerable)
                    {
                        var walkValueTask = WalkEntitiesSoftDelete(entity, (byte)(cascadeLevel + 1));
                        if (_isAsync)
                        {
                            await walkValueTask;
                        }
                        else
                        {
                            walkValueTask.CheckSyncValueTaskWorked();
                        }
                    }
                }
                else
                {
                    if (_readEveryTime || navValue == null)
                    {
                        var navValueTask = LoadNavigationSingleton(principalInstance, navigation, cascadeLevel);
                        if (_isAsync)
                        {
                            navValue = await navValueTask;
                        }
                        else
                        {
                            navValue = navValueTask.CheckSyncValueTaskWorkedAndReturnResult();
                        }
                    }
                    if (navValue == null)
                    {
                        return; //no relationship
                    }
                    var walkValueTask = WalkEntitiesSoftDelete(navValue, (byte)(cascadeLevel + 1));
                    if (_isAsync)
                    {
                        await walkValueTask;
                    }
                    else
                    {
                        walkValueTask.CheckSyncValueTaskWorked();
                    }
                }
            }
        }