static void 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 (EntityManager.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)) { EntityManager.RaiseOnValidating(entity as Entity, EventArgs.Empty); 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(); EntityManager.RaiseOnSaving(entity, savingArgs); if (savingArgs.Cancel) { Cache.Current.Remove(entity); return; } } #endregion if (!IsSet(behaviour, SaveBehaviour.BypassLogging) && !(entity is IApplicationEvent) && Config.Get <bool>("Log.Record.Application.Events", defaultValue: true)) { ApplicationEventManager.RecordSave(entity, mode); } dataProvider.Save(entity); if (mode == SaveMode.Update && asEntity?._ClonedFrom != null && AnyOpenTransaction()) { asEntity._ClonedFrom.IsStale = true; asEntity.IsStale = false; } if (mode == SaveMode.Insert) { EntityManager.SetSaved(entity); } Cache.Current.Remove(entity); if (Transaction.Current != null) { Transaction.Current.TransactionCompleted += (s, e) => { Cache.Current.Remove(entity); } } ; if (DbTransactionScope.Root != null) { DbTransactionScope.Root.OnTransactionCompleted(() => Cache.Current.Remove(entity)); } if (!(entity is IApplicationEvent)) { OnUpdated(new EventArgs <IEntity>(entity)); } if (!IsSet(behaviour, SaveBehaviour.BypassSaved)) { EntityManager.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.Current.Remove(entity); }