예제 #1
0
        protected ISaveResult <TChangeSet> saveChanges(TPrincipal principal, ITransactionProvider transactionProvider)
        {
            if (!Enabled)
            {
                return(new SaveResult <TChangeSet, TPrincipal>(context.SaveAndAcceptChanges()));
            }

            var result = new SaveResult <TChangeSet, TPrincipal>();

            // We want to split saving and logging into two steps, so that when we
            // generate the log objects the database has already assigned IDs to new
            // objects. Then we can log about them meaningfully. So we wrap it in a
            // transaction so that even though there are two saves, the change is still
            // atomic.
            transactionProvider.InTransaction(() =>
            {
                var logger = new ChangeLogger <TChangeSet, TPrincipal>(context, factory, filter, serializer);
                var oven   = (IOven <TChangeSet, TPrincipal>)null;

                // First we detect all the changes, but we do not save or accept the changes
                // (i.e. we keep our record of them).
                context.DetectChanges();

                // Then we save and accept the changes, which invokes the standard EntityFramework
                // DbContext.SaveChanges(), including any custom user logic the end-user has defined.
                // Eventually, DbContext.InternalContext.ObjectContext.SaveChanges() will be invoked
                // and then the delegate below is called back to prepare the log objects/changes.
                result.AffectedObjectCount = context.SaveAndAcceptChanges((sender, args) =>
                {
                    // This is invoked just moments before EntityFramework accepts the original changes.
                    // Now is our best oppertunity to create the log objects, which will not yet be attached
                    // to the context. They are unattached so that the context change tracker won't noticed
                    // them when accepting the original changes.
                    oven = logger.Log(context.ObjectStateManager);
                });

                // If the oven is not set here, then DbContext.SaveChanges() did not call our delegate back
                // when accepting the original changes. Without the oven, we cannot bake the logged changes.
                if (oven == null)
                {
                    throw new ChangesNotDetectedException();
                }

                // Finally, we attach the previously prepared log objects to the context (and save/accept them)
                if (oven.HasChangeSet)
                {
                    // First do any deferred log value calculations.
                    // (see PropertyChange.Bake for more information)
                    // Then detect all the log changes that were previously deferred
                    result.ChangeSet = oven.Bake(DateTime.Now, principal);
                    context.AddChangeSet(result.ChangeSet);
                    context.DetectChanges();

                    // Then we save and accept the changes that result from creating the log objects
                    // NOTE: We do not use SaveAndAcceptChanges() here because we are not interested in going
                    //       through DbContext.SaveChanges() and invoking end-users custom logic.
                    context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
                }
            });
            return(result);
        }