コード例 #1
0
 public Task <DbSaveChangesResult> SavedChangesAsync(
     IEnumerable <IHookedEntity> entries,
     HookImportance minHookImportance = HookImportance.Normal,
     CancellationToken cancelToken    = default)
 {
     return(Task.FromResult(DbSaveChangesResult.Empty));
 }
コード例 #2
0
        public static HookMetadata Create <THook, TContext>(Type hookedType, HookImportance importance = HookImportance.Normal)
            where THook : IDbSaveHook
            where TContext : DbContext
        {
            Guard.NotNull(hookedType, nameof(hookedType));

            return(new HookMetadata
            {
                ImplType = typeof(THook),
                DbContextType = typeof(TContext),
                HookedType = hookedType,
                Importance = importance
            });
        }
コード例 #3
0
        public async Task <DbSavingChangesResult> SavingChangesAsync(
            IEnumerable <IHookedEntity> entries,
            HookImportance minHookImportance = HookImportance.Normal,
            CancellationToken cancelToken    = default)
        {
            Guard.NotNull(entries, nameof(entries));

            var anyStateChanged = false;

            if (!entries.Any() || !_saveHooks.Any())
            {
                return(DbSavingChangesResult.Empty);
            }

            var processedHooks = new Multimap <IDbSaveHook, IHookedEntity>();

            foreach (var entry in entries)
            {
                var e = entry; // Prevents access to modified closure

                if (cancelToken.IsCancellationRequested)
                {
                    continue;
                }

                if (HandledAlready(e, HookStage.PreSave))
                {
                    // Prevent repetitive hooking of the same entity/state/pre combination within a single request
                    continue;
                }

                var hooks = GetSaveHookInstancesFor(e, HookStage.PreSave, minHookImportance);

                foreach (var hook in hooks)
                {
                    // call hook
                    try
                    {
                        Logger.Debug("PRE save hook: {0}, State: {1}, Entity: {2}", hook.GetType().Name, e.InitialState, e.Entity.GetType().Name);
                        var result = await hook.OnBeforeSaveAsync(e, cancelToken);

                        if (result == HookResult.Ok)
                        {
                            processedHooks.Add(hook, e);
                        }
                        else if (result == HookResult.Void)
                        {
                            RegisterVoidHook(hook, e, HookStage.PreSave);
                        }
                    }
                    catch (Exception ex) when(ex is NotImplementedException || ex is NotSupportedException)
                    {
                        RegisterVoidHook(hook, e, HookStage.PreSave);
                    }
                    catch (Exception ex)
                    {
                        Logger.Error(ex, "PreSaveHook exception ({0})", hook.GetType().FullName);
                    }

                    // change state if applicable
                    if (e.HasStateChanged)
                    {
                        e.InitialState  = e.State;
                        anyStateChanged = true;
                    }
                }
            }

            foreach (var hook in processedHooks)
            {
                await hook.Key.OnBeforeSaveCompletedAsync(hook.Value, cancelToken);
            }

            return(new DbSavingChangesResult(processedHooks.Keys, anyStateChanged)
            {
                Entries = entries
            });
        }
コード例 #4
0
        private IEnumerable <IDbSaveHook> GetSaveHookInstancesFor(IHookedEntity entry, HookStage stage, HookImportance minHookImportance)
        {
            if (entry.EntityType == null)
            {
                return(Enumerable.Empty <IDbSaveHook>());
            }

            IEnumerable <IDbSaveHook> hooks;

            // For request cache lookup
            var requestKey = new RequestHookKey(entry, stage, minHookImportance);

            if (_hooksRequestCache.ContainsKey(requestKey))
            {
                hooks = _hooksRequestCache[requestKey];
            }
            else
            {
                hooks = _saveHooks
                        // Reduce by data context types
                        .Where(x => x.Metadata.DbContextType.IsAssignableFrom(entry.DbContext.GetType()))
                        // Reduce by entity types which can be processed by this hook
                        .Where(x => x.Metadata.HookedType.IsAssignableFrom(entry.EntityType))
                        // Only include hook types with Importance >= minHookImportance
                        .Where(x => x.Metadata.Importance >= minHookImportance)
                        // Exclude void hooks (hooks known to be useless for the current EntityType/State/Stage combination)
                        .Where(x => !_voidHooks.Contains(new HookKey(x.Metadata.ImplType, entry, stage)))
                        // Apply sort
                        .OrderBy(x => x.Metadata.Order)
                        // Get the hook instance
                        .Select(x => x.Value)
                        // Make array
                        .ToArray();

                _hooksRequestCache.AddRange(requestKey, hooks);
            }

            return(hooks);
        }
コード例 #5
0
 public ImportantAttribute(HookImportance importance)
 {
     Importance = importance;
 }
コード例 #6
0
        /// <summary>
        /// Creates a scope in which a DbContext instance behaves differently.
        /// The behaviour is resetted on disposal of the scope to what it was before.
        /// </summary>
        /// <param name="ctx">The context instance to change behavior for.</param>
        /// <param name="deferCommit">
        /// Suppresses the execution of <see cref="DbContext.SaveChanges()"/> / <see cref="DbContext.SaveChangesAsync(CancellationToken)"/>
        /// until this instance is disposed or <see cref="Commit()"/> / <see cref="CommitAsync(CancellationToken)"/> is called explicitly.
        /// </param>
        /// <param name="retainConnection">
        /// Opens connection and retains it until disposal. May increase load/save performance in large scopes.
        /// </param>
        public DbContextScope(HookingDbContext ctx,
                              bool?autoDetectChanges            = null,
                              bool?lazyLoading                  = null,
                              bool?forceNoTracking              = null,
                              bool?deferCommit                  = false,
                              bool retainConnection             = false,
                              HookImportance?minHookImportance  = null,
                              CascadeTiming?cascadeDeleteTiming = null,
                              CascadeTiming?deleteOrphansTiming = null,
                              bool?autoTransactions             = null)
        {
            Guard.NotNull(ctx, nameof(ctx));

            var changeTracker = ctx.ChangeTracker;

            _ctx = ctx;
            _autoDetectChangesEnabled = changeTracker.AutoDetectChangesEnabled;
            _minHookImportance        = ctx.MinHookImportance;
            _suppressCommit           = ctx.SuppressCommit;
            _lazyLoadingEnabled       = changeTracker.LazyLoadingEnabled;
            _queryTrackingBehavior    = changeTracker.QueryTrackingBehavior;
            _cascadeDeleteTiming      = changeTracker.CascadeDeleteTiming;
            _deleteOrphansTiming      = changeTracker.DeleteOrphansTiming;
            _autoTransactionEnabled   = ctx.Database.AutoTransactionsEnabled;
            _retainConnection         = retainConnection;

            if (autoDetectChanges.HasValue)
            {
                changeTracker.AutoDetectChangesEnabled = autoDetectChanges.Value;
            }

            if (minHookImportance.HasValue)
            {
                ctx.MinHookImportance = minHookImportance.Value;
            }

            if (lazyLoading.HasValue)
            {
                changeTracker.LazyLoadingEnabled = lazyLoading.Value;
            }

            if (forceNoTracking == true)
            {
                changeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
            }

            if (deferCommit.HasValue)
            {
                ctx.SuppressCommit = deferCommit.Value;
            }

            if (cascadeDeleteTiming.HasValue)
            {
                changeTracker.CascadeDeleteTiming = cascadeDeleteTiming.Value;
            }

            if (deleteOrphansTiming.HasValue)
            {
                changeTracker.DeleteOrphansTiming = deleteOrphansTiming.Value;
            }

            if (autoTransactions.HasValue)
            {
                ctx.Database.AutoTransactionsEnabled = autoTransactions.Value;
            }

            if (retainConnection)
            {
                ctx.Database.OpenConnection();
            }
        }