Example #1
0
        async Task DoSave(IEntity entity, SaveBehaviour behaviour)
        {
            var mode = entity.IsNew ? SaveMode.Insert : SaveMode.Update;

            var asEntity = entity as Entity;

            if (mode == SaveMode.Update && (asEntity._ClonedFrom?.IsStale == true) && AnyOpenTransaction())
            {
                throw new InvalidOperationException("This " + entity.GetType().Name + " instance in memory is out-of-date. " +
                                                    "A clone of it is already updated in the transaction. It is not allowed to update the same instance multiple times in a transaction, because then the earlier updates would be overwriten by the older state of the instance in memory. \r\n\r\n" +
                                                    @"BAD: 
Database.Update(myObject, x=> x.P1 = ...); // Note: this could also be nested inside another method that's called here instead.
Database.Update(myObject, x=> x.P2 = ...);

GOOD: 
Database.Update(myObject, x=> x.P1 = ...);
myObject = Database.Reload(myObject);
Database.Update(myObject, x=> x.P2 = ...);");
            }

            if (Entity.Services.IsImmutable(entity))
            {
                throw new ArgumentException("An immutable record must be cloned before any modifications can be applied on it. " +
                                            $"Type={entity.GetType().FullName}, Id={entity.GetId()}.");
            }

            var dataProvider = GetProvider(entity);

            if (!IsSet(behaviour, SaveBehaviour.BypassValidation))
            {
                await Entity.Services.RaiseOnValidating(entity as Entity, EventArgs.Empty);

                await entity.Validate();
            }
            else if (!dataProvider.SupportValidationBypassing())
            {
                throw new ArgumentException(dataProvider.GetType().Name + " does not support bypassing validation.");
            }

            #region Raise saving event

            if (!IsSet(behaviour, SaveBehaviour.BypassSaving))
            {
                var savingArgs = new System.ComponentModel.CancelEventArgs();
                await Entity.Services.RaiseOnSaving(entity, savingArgs);

                if (savingArgs.Cancel)
                {
                    Cache.Remove(entity);
                    return;
                }
            }

            #endregion

            if (!IsSet(behaviour, SaveBehaviour.BypassLogging))
            {
                if (mode == SaveMode.Insert)
                {
                    await Audit.LogInsert(entity);
                }
                else
                {
                    await Audit.LogUpdate(entity);
                }
            }

            await dataProvider.Save(entity);

            Cache.UpdateRowVersion(entity);

            if (mode == SaveMode.Update && asEntity?._ClonedFrom != null && AnyOpenTransaction())
            {
                asEntity._ClonedFrom.IsStale = true;
                asEntity.IsStale             = false;
            }

            if (mode == SaveMode.Insert)
            {
                Entity.Services.SetSaved(entity);
            }

            Cache.Remove(entity);

            if (Transaction.Current != null)
            {
                Transaction.Current.TransactionCompleted += (s, e) => { Cache.Remove(entity); }
            }
            ;

            DbTransactionScope.Root?.OnTransactionCompleted(() => Cache.Remove(entity));

            await Updated.Raise(entity);

            if (!IsSet(behaviour, SaveBehaviour.BypassSaved))
            {
                await Entity.Services.RaiseOnSaved(entity, new SaveEventArgs(mode));
            }

            // OnSaved event handler might have read the object again and put it in the cache, which would
            // create invalid CachedReference objects.
            Cache.Remove(entity);
        }