using System; using System.Collections.Generic; using System.Data; using System.Linq; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence { internal class LockingRepository where TRepository : IDisposable, IRepository { private readonly IScopeUnitOfWorkProvider _uowProvider; private readonly Func _repositoryFactory; private readonly int[] _readLockIds, _writeLockIds; public LockingRepository(IScopeUnitOfWorkProvider uowProvider, Func repositoryFactory, IEnumerable readLockIds, IEnumerable writeLockIds) { Mandate.ParameterNotNull(uowProvider, "uowProvider"); Mandate.ParameterNotNull(repositoryFactory, "repositoryFactory"); _uowProvider = uowProvider; _repositoryFactory = repositoryFactory; _readLockIds = readLockIds == null ? new int[0] : readLockIds.ToArray(); _writeLockIds = writeLockIds == null ? new int[0] : writeLockIds.ToArray(); } public void WithReadLocked(Action> action, bool autoCommit = true) { using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) { // getting the database creates a scope and a transaction // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) // and will throw if outer scope (if any) has a lower isolation level foreach (var lockId in _readLockIds) uow.Database.AcquireLockNodeReadLock(lockId); using (var repository = _repositoryFactory(uow)) { action(new LockedRepository(uow, repository)); if (autoCommit == false) return; uow.Commit(); } // dispose repository => dispose uow => complete (or not) scope } // dispose uow again => nothing } public TResult WithReadLocked(Func, TResult> func, bool autoCommit = true) { using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) { // getting the database creates a scope and a transaction // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) // and will throw if outer scope (if any) has a lower isolation level foreach (var lockId in _readLockIds) uow.Database.AcquireLockNodeReadLock(lockId); using (var repository = _repositoryFactory(uow)) { var ret = func(new LockedRepository(uow, repository)); if (autoCommit == false) return ret; uow.Commit(); return ret; } // dispose repository => dispose uow => complete (or not) scope } // dispose uow again => nothing } public void WithWriteLocked(Action> action, bool autoCommit = true) { using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) { // getting the database creates a scope and a transaction // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) // and will throw if outer scope (if any) has a lower isolation level foreach (var lockId in _writeLockIds) uow.Database.AcquireLockNodeWriteLock(lockId); using (var repository = _repositoryFactory(uow)) { action(new LockedRepository(uow, repository)); if (autoCommit == false) return; uow.Commit(); } // dispose repository => dispose uow => complete (or not) scope } // dispose uow again => nothing } public TResult WithWriteLocked(Func, TResult> func, bool autoCommit = true) { using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) { // getting the database creates a scope and a transaction // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) // and will throw if outer scope (if any) has a lower isolation level foreach (var lockId in _writeLockIds) uow.Database.AcquireLockNodeReadLock(lockId); using (var repository = _repositoryFactory(uow)) { var ret = func(new LockedRepository(uow, repository)); if (autoCommit == false) return ret; uow.Commit(); return ret; } // dispose repository => dispose uow => complete (or not) scope } // dispose uow again => nothing } } }