private object DoSave( object theObj, Key key, IClassPersister persister, bool replicate, bool useIdentityColumn, Cascades.CascadingAction cascadeAction, object anything ) { if( persister.ImplementsValidatable ) { ( ( IValidatable ) theObj ).Validate(); } object id; if( useIdentityColumn ) { id = null; ExecuteInserts(); } else { id = key.Identifier; } // Put a placeholder in entries, so we don't recurse back to try and Save() the // same object again. QUESTION: Should this be done before OnSave() is called? // likewise, should it be done before OnUpdate()? AddEntry( theObj, Status.Saving, null, id, null, LockMode.Write, useIdentityColumn, persister, false ); // okay if id is null here // cascade-save to many-to-one BEFORE the parent is saved cascading++; try { Cascades.Cascade( this, persister, theObj, cascadeAction, CascadePoint.CascadeBeforeInsertAfterDelete, anything ); } finally { cascading--; } object[ ] values = persister.GetPropertyValues( theObj ); IType[ ] types = persister.PropertyTypes; bool substitute = false; if( !replicate ) { substitute = interceptor.OnSave( theObj, id, values, persister.PropertyNames, types ); // Keep the existing version number in the case of replicate! if( persister.IsVersioned ) { // IsUnsavedVersion bit below is NHibernate-specific substitute = Versioning.SeedVersion( values, persister.VersionProperty, persister.VersionType, persister.IsUnsavedVersion( values ) ) || substitute; } } if( persister.HasCollections ) { // TODO: make OnReplicateVisitor extend WrapVisitor if( replicate ) { OnReplicateVisitor visitor = new OnReplicateVisitor( this, id ); visitor.ProcessValues( values, types ); } WrapVisitor visitor2 = new WrapVisitor( this ); // substitutes into values by side-effect visitor2.ProcessValues( values, types ); substitute = substitute || visitor2.IsSubstitutionRequired; } if( substitute ) { persister.SetPropertyValues( theObj, values ); } TypeFactory.DeepCopy( values, types, persister.PropertyUpdateability, values ); NullifyTransientReferences( values, types, useIdentityColumn, theObj ); CheckNullability( values, persister, false ); if( useIdentityColumn ) { ScheduledIdentityInsertion insert = new ScheduledIdentityInsertion( values, theObj, persister, this ); Execute( insert ); id = insert.GeneratedId; persister.SetIdentifier( theObj, id ); key = new Key( id, persister ); CheckUniqueness( key, theObj ); } object version = Versioning.GetVersion( values, persister ); AddEntity( key, theObj ); AddEntry( theObj, Status.Loaded, values, id, version, LockMode.Write, useIdentityColumn, persister, replicate ); nonExists.Remove( key ); if( !useIdentityColumn ) { insertions.Add( new ScheduledInsertion( id, values, theObj, version, persister, this ) ); } // cascade-save to collections AFTER the collection owner was saved cascading++; try { Cascades.Cascade( this, persister, theObj, cascadeAction, CascadePoint.CascadeAfterInsertBeforeDelete, anything ); } finally { cascading--; } return id; }
private void FlushEntity( object obj, EntityEntry entry ) { IClassPersister persister = entry.Persister; Status status = entry.Status; CheckId( obj, persister, entry.Id ); object[ ] values; if( status == Status.Deleted ) { //grab its state saved at deletion values = entry.DeletedState; } else { //grab its current state values = persister.GetPropertyValues( obj ); } IType[ ] types = persister.PropertyTypes; bool substitute = false; if( persister.HasCollections ) { // wrap up any new collections directly referenced by the object // or its components // NOTE: we need to do the wrap here even if its not "dirty", // because nested collections need wrapping but changes to // _them_ don't dirty the container. Also, for versioned // data, we need to wrap before calling searchForDirtyCollections WrapVisitor visitor = new WrapVisitor( this ); // substitutes into values by side-effect visitor.ProcessValues( values, types ); substitute = visitor.IsSubstitutionRequired; } bool cannotDirtyCheck; bool interceptorHandledDirtyCheck; bool dirtyCheckDoneBySelect = false; object[ ] currentPersistentState = null; int[ ] dirtyProperties = interceptor.FindDirty( obj, entry.Id, values, entry.LoadedState, persister.PropertyNames, types ); if( dirtyProperties == null ) { // interceptor returned null, so do the dirtycheck ourself, if possible interceptorHandledDirtyCheck = false; cannotDirtyCheck = entry.LoadedState == null; // object loaded by update() if( !cannotDirtyCheck ) { dirtyProperties = persister.FindDirty( values, entry.LoadedState, obj, this ); } else { currentPersistentState = persister.GetCurrentPersistentState( entry.Id, entry.Version, this ); if( currentPersistentState != null ) { dirtyProperties = persister.FindModified( currentPersistentState, values, obj, this ); cannotDirtyCheck = false; dirtyCheckDoneBySelect = true; } } } else { // the interceptor handled the dirty checking cannotDirtyCheck = false; interceptorHandledDirtyCheck = true; } // compare to cached state (ignoring nested collections) if( IsUpdateNecessary( persister, cannotDirtyCheck, status, dirtyProperties, values, types ) ) { // its dirty! if( log.IsDebugEnabled ) { if( status == Status.Deleted ) { log.Debug( "Updating deleted entity: " + MessageHelper.InfoString( persister, entry.Id ) ); } else { log.Debug( "Updating entity: " + MessageHelper.InfoString( persister, entry.Id ) ); } } if( !entry.IsBeingReplicated ) { // give the Interceptor a chance to modify property values bool intercepted = interceptor.OnFlushDirty( obj, entry.Id, values, entry.LoadedState, persister.PropertyNames, types ); //now we might need to recalculate the dirtyProperties array if( intercepted && !cannotDirtyCheck && !interceptorHandledDirtyCheck ) { if( dirtyCheckDoneBySelect ) { dirtyProperties = persister.FindModified( currentPersistentState, values, obj, this ); } else { dirtyProperties = persister.FindDirty( values, entry.LoadedState, obj, this ); } } // if the properties were modified by the Interceptor, we need to set them back to the object substitute = substitute || intercepted; } // validate() instances of Validatable if( status == Status.Loaded && persister.ImplementsValidatable ) { ( ( IValidatable ) obj ).Validate(); } //increment the version number (if necessary) object nextVersion = GetNextVersion( persister, values, entry ); // get the updated snapshot by cloning current state object[ ] updatedState = null; if( status == Status.Loaded ) { updatedState = new object[values.Length]; TypeFactory.DeepCopy( values, types, persister.PropertyUpdateability, updatedState ); } // if it was dirtied by a collection only if( !cannotDirtyCheck && dirtyProperties == null ) { dirtyProperties = ArrayHelper.EmptyIntArray; } CheckNullability( values, persister, true ); //note that we intentionally did _not_ pass in currentPersistentState! updates.Add( new ScheduledUpdate( entry.Id, values, dirtyProperties, entry.LoadedState, entry.Version, nextVersion, obj, updatedState, persister, this ) ); } if( status == Status.Deleted ) { //entry.status = Status.Gone; } else { // now update the object... has to be outside the main if block above (because of collections) if( substitute ) { persister.SetPropertyValues( obj, values ); } // search for collections by reachability, updating their role. // we don't want to touch collections reachable from a deleted object. if( persister.HasCollections ) { new FlushVisitor( this, obj ).ProcessValues( values, types ); } } }