public ITransaction Setup(IEnumerable <object> items) { var txnEntries = items.Select(x => { var type = x.GetType().Name; var id = _idAccessor.GetId(x)?.ToString(); var rev = _revisionAccessor.GetRevision(x)?.ToString(); var txn = new TransactionEntry() { DateTime = DateTime.UtcNow, Id = $"{id}-{type}", ActualId = id, ActualRev = rev, Type = type }; return(txn); }); var transaction = new Transaction(_couchDb, _idAccessor, _revisionAccessor); try { transaction.Init(txnEntries); } catch (Exception e) { transaction.Rollback(e); throw; } return(transaction); }
public Task Execute(IEnumerable <CommitContext> items, Next <IEnumerable <CommitContext> > next) { items = items.ToList(); //ensure 1 iteration over list. (tasks to run once) //do not call the db if we have no items to transact on. if (!items.Any()) { return(Task.CompletedTask); } //setup the items for the database commit. var entityUpdates = new Dictionary <string, object>(); var docRequests = new List <BulkDocRequest>(); foreach (var bulkContext in items) { var entry = new BulkDocRequest { Content = bulkContext.Entity, Id = bulkContext.Key.CouchDbId }; switch (bulkContext.ActionType) { case ActionType.Add: break; case ActionType.Update: break; case ActionType.Delete: entry.Rev = (string)_revisionAccessor.GetRevision(bulkContext.Entity); entry.Delete = true; break; default: throw new ArgumentOutOfRangeException(); } docRequests.Add(entry); entityUpdates.Add(entry.Id, bulkContext.Entity); } //apply the commit var request = new BulkDocsRequest { Docs = docRequests }; var updates = _couchDb.BulkApplyChanges(request); //update any revisions! foreach (var update in updates) { var entity = entityUpdates[update.Id]; _revisionAccessor.SetRevision(entity, update.Rev); } return(Task.CompletedTask); }
public void Init(IEnumerable <TransactionEntry> txnEntries) { _txnEntries = new Dictionary <string, TransactionEntry>(); foreach (var txnEntry in txnEntries) { txnEntry.TransactionId = _id; _txnEntries.Add(txnEntry.ActualId, txnEntry); } var now = DateTime.UtcNow; bool HasExpired(DateTime time) => new TimeSpan(now.Ticks - time.Ticks).TotalSeconds > 5; var existingTransactionEntries = _couchDb .LoadAllEntities( new AllDocsRequest() { Keys = _txnEntries.Values.Select(x => x.Id) //checking against transaction key for documents }) .Rows .Select(x => x.Doc) .Cast <TransactionEntry>() .ToList(); var hasTransactionLockConflict = existingTransactionEntries.Any(x => HasExpired(x.DateTime)); if (hasTransactionLockConflict) { throw new TransactionException(existingTransactionEntries.Select(x => x.Id).ToList()); } var payload = new BulkDocsRequest() { Docs = _txnEntries.Values.Select( txn => new BulkDocRequest() { Id = txn.Id, Content = txn }) }; _results = _couchDb.BulkApplyChanges(payload).ToList(); //now we have a txn (lock) we confirm the target docs are still on the same rev var conflictingDocs = _couchDb.LoadAllEntities( new AllDocsRequest() { Keys = _txnEntries.Values.Select(x => x.ActualId) }) .Rows .Select(x => x.Doc) .Select(x => new { Id = _idAccessor.GetId(x).ToString(), Rev = _revisionAccessor.GetRevision(x)?.ToString() }) .Where(x => { if (!_txnEntries.TryGetValue(x.Id, out var entry)) { return(false); } return(entry.ActualRev != x.Rev); }) .ToList(); if (conflictingDocs.Any()) { throw new AggregateException(conflictingDocs.Select(x => new ConflictException(x.Id, x.Rev, "conflict", "stale reference"))); } }