/// <summary> /// Submits one or more commands to the database reflecting the changes made to the retreived entities. /// If a transaction is not already specified one will be created for the duration of this operation. /// If a change conflict is encountered a ChangeConflictException will be thrown. /// You can override this method to implement common conflict resolution behaviors. /// </summary> /// <param name="failureMode">Determines how SubmitChanges handles conflicts.</param> public virtual void SubmitChanges(ConflictMode failureMode, IMeasureProvider measureProvider = null) { CheckDispose(); CheckNotInSubmitChanges(); VerifyTrackingEnabled(); conflicts.Clear(); try { isInSubmitChanges = true; if (Transactions.Transaction.Current == null && provider.Transaction == null) { var openedConnection = false; DbTransaction transaction = null; try { if (provider.Connection.State == ConnectionState.Open) { provider.ClearConnection(); } if (provider.Connection.State == ConnectionState.Closed) { provider.Connection.Open(); openedConnection = true; } transaction = provider.Connection.BeginTransaction(IsolationLevel.ReadCommitted); provider.Transaction = transaction; new ChangeProcessor(services, this).SubmitChanges(failureMode, measureProvider); AcceptChanges(); // to commit a transaction, there can be no open readers // on the connection. provider.ClearConnection(); transaction.Commit(); } catch { if (transaction != null) { transaction.Rollback(); } throw; } finally { provider.Transaction = null; if (openedConnection) { provider.Connection.Close(); } } } else { new ChangeProcessor(services, this).SubmitChanges(failureMode, measureProvider); AcceptChanges(); } } finally { isInSubmitChanges = false; } }
internal void SubmitChanges(ConflictMode failureMode, IMeasureProvider measureProvider = null) { this.TrackUntrackedObjects(); // Must apply inferred deletions only after any untracked objects // are tracked this.ApplyInferredDeletions(); this.BuildEdgeMaps(); var list = this.GetOrderedList(); ValidateAll(list); int numUpdatesAttempted = 0; ChangeConflictSession conflictSession = new ChangeConflictSession(this.context); List <ObjectChangeConflict> conflicts = new List <ObjectChangeConflict>(); List <TrackedObject> deletedItems = new List <TrackedObject>(); List <TrackedObject> insertedItems = new List <TrackedObject>(); List <TrackedObject> syncDependentItems = new List <TrackedObject>(); foreach (TrackedObject item in list) { try { var tableName = item.Type.Table.TableName; if (item.IsNew) { if (item.SynchDependentData()) { syncDependentItems.Add(item); } using (measureProvider?.Measure($"Insert {tableName}")) { changeDirector.Insert(item); }; // store all inserted items for post processing insertedItems.Add(item); } else if (item.IsDeleted) { // Delete returns 1 if the delete was successfull, 0 if the row exists // but wasn't deleted due to an OC conflict, or -1 if the row was // deleted by another context (no OC conflict in this case) numUpdatesAttempted++; int ret; using (measureProvider?.Measure($"Delete {tableName}")) { ret = changeDirector.Delete(item); } if (ret == 0) { conflicts.Add(new ObjectChangeConflict(conflictSession, item, false)); } else { // store all deleted items for post processing deletedItems.Add(item); } } else if (item.IsPossiblyModified) { if (item.SynchDependentData()) { syncDependentItems.Add(item); } if (item.IsModified) { CheckForInvalidChanges(item); numUpdatesAttempted++; int ret; using (measureProvider?.Measure($"Update {tableName}")) { ret = changeDirector.Update(item); } if (ret <= 0) { conflicts.Add(new ObjectChangeConflict(conflictSession, item)); } } } } catch (ChangeConflictException) { conflicts.Add(new ObjectChangeConflict(conflictSession, item)); } if (conflicts.Count > 0 && failureMode == ConflictMode.FailOnFirstConflict) { break; } } // if we have accumulated any failed updates, throw the exception now if (conflicts.Count > 0) { // First we need to rollback any value that have already been auto-[....]'d, since the values are no longer valid on the server changeDirector.RollbackAutoSync(); // Also rollback any dependent items that were [....]'d, since their parent values may have been rolled back foreach (TrackedObject syncDependentItem in syncDependentItems) { Debug.Assert(syncDependentItem.IsNew || syncDependentItem.IsPossiblyModified, "SynchDependent data should only be rolled back for new and modified objects."); syncDependentItem.SynchDependentData(); } this.context.ChangeConflicts.Fill(conflicts); throw CreateChangeConflictException(numUpdatesAttempted, conflicts.Count); } else { // No conflicts occurred, so we don't need to save the rollback values anymore changeDirector.ClearAutoSyncRollback(); } // Only after all updates have been sucessfully processed do we want to make // post processing modifications to the objects and/or cache state. PostProcessUpdates(insertedItems, deletedItems); }
/// <summary> /// Submits one or more commands to the database reflecting the changes made to the retreived entities. /// If a transaction is not already specified one will be created for the duration of this operation. /// If a change conflict is encountered a ChangeConflictException will be thrown. /// </summary> public void SubmitChanges(IMeasureProvider measureProvider = null) { CheckDispose(); SubmitChanges(ConflictMode.FailOnFirstConflict, measureProvider); }