/// <summary> /// Create new instance and start ambient KTM transaction if necessary /// </summary> /// <param name="repository"> /// Repository manager /// </param> /// <param name="disposeIfSlave"> /// Dispose transaction created in scope when disposing the scope even if it is part of the ambient managed transaction. /// Has no effect if <paramref name="joinAmbientManaged"/> is <see langword="false"/>. /// </param> /// <param name="okToStartOwnTransaction"> /// Whether to allow staring own storage transaction (slave transaction is not owned). /// </param> /// <remarks> /// DOCO: transaction settings /// When wrapping existing transaction a priority is always given to storage transaction scope. /// Settings allow prohibiting the use of any external transaction (AlwaysStartNew) or only managed (DisallowJoiningAmbientManaged). /// You cannot prohibit usage of external storage transactions but allow that of external managed transactions. /// Hence if AlwaysStartNew is OFF and there's an ambient storage transaction when the scope is created it will wrap around that /// existing storage transaction. /// </remarks> protected StorageTransactionScope(IRepository repository, bool disposeIfSlave, bool okToStartOwnTransaction) { IStorageTransactionScope scopeToWrap; IFileSystemProvider fsProvider = repository.ObjectFactory.FileSystemProvider; StorageTransactionSettings settings = repository.Settings.StorageTransactionSettings; bool noTransactionOption = (settings & StorageTransactionSettings.NoTransactions) == StorageTransactionSettings.NoTransactions; bool joinAmbientManaged = (settings & StorageTransactionSettings.DisallowJoiningAmbientManaged) != StorageTransactionSettings.DisallowJoiningAmbientManaged; if (!noTransactionOption) { if ((settings & StorageTransactionSettings.RequireTransactions) == StorageTransactionSettings.RequireTransactions && !fsProvider.SupportsTransactions) { // transactions required but not supported throw new InvalidOperationException(StorageResources.TransactionsNotSupported); } // done: start new must only apply to top level transaction started in repository because if subsequent // nested methods create their scopes they would start their own transactions and would not see parent FS objects bool alwaysStartNewOption = IsAlwaysNewTransactionOptionOn(repository: repository); // note: "start new" should not create multiple transactions in nested calls INSIDE repository; only one at the top level // is all I need. bool startNewIfAlreadyActive = alwaysStartNewOption && !IsWithinStorageTransactionScope; // startNewIfAlreadyActive == true && joinAmbientManaged == true makes sense only when storage (KTM) transaction is already // active AND managed external transaction is also active but the KTM transaction is not part of the current Transaction.Current // because if it is then the result will be exactly the same as when just using already active KTM transaction if (!startNewIfAlreadyActive && fsProvider.IsStorageAmbientTransactionActive) { // case for reusage of already existing ambient storage transaction; if there's ambient storage transaction and OK to reuse it // (by not being required to start new one) then okToStartOwnTransaction is not relevant as I am not going to start own transaction // anyway; // "always start new" should prevent usage of external storage transactions; therefore when it is ON and not ok to start own tr-n // I have to create null scope _log.Debug("Ambient transaction active, returning preservation scope"); scopeToWrap = fsProvider.CreateTransactionScope(transactionToUse: fsProvider.AmbientTransaction, dispose: false); Check.Assert(!scopeToWrap.ToBeDisposed && !scopeToWrap.HasChangedContext && fsProvider.AmbientTransaction == scopeToWrap.UnderlyingTransaction); } else { if (joinAmbientManaged && IsManagedAmbientActive) { // slave transaction is not regarded as "own"; the scope does not control it. scopeToWrap = fsProvider.CreateSlaveTransactionScope(dispose: disposeIfSlave); } else { if (okToStartOwnTransaction) { // default // Creates new scope, starts new independent storage transaction regardless of current storage and managed context. scopeToWrap = fsProvider.CreateStandaloneTransactionScope(dispose: true); } else { // NULL scope needed - the scope which will not create any transaction and will clear storage transaction context // instead; this may happen when when there's storage ambient transaction but "always start new" option is ON. scopeToWrap = fsProvider.CreateTransactionScope(transactionToUse: null, dispose: false); Check.Assert(scopeToWrap.UnderlyingTransaction == null); Check.Assert(!fsProvider.IsStorageAmbientTransactionActive); } } } } else { // creating scope which will clear transactional context scopeToWrap = fsProvider.CreateTransactionScope(transactionToUse: null, dispose: false); } Check.Assert(scopeToWrap != null); Initialize(scopeToWrap, repository); }