My application using Azure SQL and Azure Blob Storage for some business requirements, most of the case need to support Atomic Transaction for both DB & Blob, if DB entry fails should rollback Blob as well(go all or no go), for DB side can use TransactionScope but Blob don't have any direct options, so decided to go 2 phase commit with help of IEnlistmentNotification interface, it working as expected but am trying to created common class/implementation to support all operations or at least few most used operations in Blob storage(upload, delete, SetMetadata ...), I don't get any idea about how to create some implementation, is this possible and any code samples available will help me lot.
Resource Manager
public class AzureBlobStorageResourceManager : IEnlistmentNotification, IDisposable
{
private List<AzureBlobStore> _operations;
private bool _disposedValue;
public void EnlistOperation(AzureBlobStore operation)
{
if (_operations is null)
{
var currentTransaction = Transaction.Current;
currentTransaction?.EnlistVolatile(this, EnlistmentOptions.None);
_operations = new List<AzureBlobStore>();
}
_operations.Add(operation);
}
public void Commit(Enlistment enlistment)
{
foreach (var blobOperation in _operations)
{
blobOperation.Dispose();
}
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
foreach (var blobOperation in _operations)
{
blobOperation.RollBack().ConfigureAwait(false);
}
enlistment.Done();
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
try
{
foreach (var blobOperation in _operations)
{
blobOperation.DoWork().ConfigureAwait(false);
}
preparingEnlistment.Prepared();
}
catch
{
preparingEnlistment.ForceRollback();
}
}
public void Rollback(Enlistment enlistment)
{
foreach (var blobOperation in _operations)
{
blobOperation.RollBack().ConfigureAwait(false);
}
enlistment.Done();
}
public void Dispose() => Dispose(true);
protected virtual void Dispose(bool disposing)
{
if (_disposedValue) return;
if (disposing)
{
foreach (var operation in _operations)
operation.Dispose();
}
_disposedValue = true;
}
~AzureBlobStorageResourceManager() => Dispose(false);
}
Actual Blob Operation
public class AzureBlobStore : IDisposable
{
private string _backupPath;
private readonly string _blobName;
private Stream _content;
private bool _disposedValue;
private BlobClient _blobClient;
public AzureBlobStore(BlobContainerClient containerClient, string blobName, Stream content)
{
(_blobName, _content, _blobClient) = (blobName, content, containerClient.GetBlobClient(blobName));
}
public async Task DoWork()
{
_content.Position = 0;
await _blobClient.UploadAsync(_content).ConfigureAwait(false);
/*
await _blobClient.DeleteAsync(Azure.Storage.Blobs.Models.DeleteSnapshotsOption.IncludeSnapshots).ConfigureAwait(false);
*/
}
public async Task RollBack()
{
// Compensation logic for Upload
await _blobClient.DeleteIfExistsAsync(Azure.Storage.Blobs.Models.DeleteSnapshotsOption.IncludeSnapshots).ConfigureAwait(false);
// Compensation logic for Delete
/* await _blobClient.UploadAsync(_backupPath); */
}
public void Dispose() => Dispose(true);
protected virtual void Dispose(bool disposing)
{
if (_disposedValue) return;
if (disposing)
{
_blobClient.DeleteIfExistsAsync(Azure.Storage.Blobs.Models.DeleteSnapshotsOption.IncludeSnapshots);
}
_disposedValue = true;
}
~AzureBlobStore() => Dispose(false);
}
Code inside /* */ is another one Blob operation, am looking for common way to solve this.