-
Notifications
You must be signed in to change notification settings - Fork 1
Exceptions and exception handling
To help the user to figure out what exactly went wrong and to help in taking corrective actions the Exception model tries to throw meaningful and detailed exceptions. Since there are multiple different batching modes and therefore different behavior and different ways for the operations to fail, there are multiple different Exception types. In addition to those, a few general Exception types exist.
This description tries to explain when to expect each type of exception and how to get information out of them.
This exception will be thrown from the constructor if some required parameter is null.
This exception is thrown from the constructor if something in the constructor initialization phase fails.
This exception is thrown when entity validation fails in insert, replace or merge operations, batched and non-batched, when UseClientSideValidation
is set to true.
The EntityValidationErrors
property is a Dictionary<TData, List<string>>
that has each entity with validation issues as key and the list of validation issues as value. So from there you can pick the ones that caused the issues.
Example:
var entity = new MyEntity()
{
PartitionKey = "Secret Agents",
RowKey = "James Bond\t007", // contains an illegal char, \t
};
store.UseClientSideValidation = true;
try
{
await store.InsertAsync(BatchingMode.None, entity);
}
catch(AzureTableDataStoreEntityValidationException<MyEntity> e)
{
List<string> validationErrors = e.EntityValidationErrors[entity];
// validationErrors[0] is "Row key contains illegal characters"
// Do something...
}
A generic exception, when no specific exception type fits the context. These are thrown for example in situations like failing to initialize a table or a blob container, failing to translate a LINQ query expression to a table query, invalid parameters, and generally from all methods before any table or blob operations have occurred.
The InnerException usually contains the actual source of the issue.
Represents a failure of a single operation. This is thrown from insert, replace, merge and delete operations when there's only one entity to process.
The exception contains Entity
property of type TData
that holds the reference to the failing entity, and a list of blob operation exceptions, BlobOperationExceptions
with type List<AzureTableDataStoreBlobOperationException<TData>>
that holds any blob operation errors if there were any when processing this insert/merge/replace/delete. If the error wasn't a blob operation error (the table operation itself failed for example), the InnerException usually holds the actual exception that caused the failure.
Example:
// Setting up a failure.
var explodingStream = new Mock<Stream>();
mockStream.Setup(s => s.CanRead).Returns(true);
mockStream.Setup(s => s.Seek(It.IsAny<long>(), It.IsAny<SeekOrigin>()))
.Throws(new Exception("BOOM!")); // this will get called and throws
mockStream.Setup(s => s.Length).Returns(100);
var entity = new MyEntity()
{
PartitionKey = "Secret Agents",
RowKey = "James Bond 007",
MyBlob = new LargeBlob("image.png", explodingStream.Object, "image/png")
};
try
{
await store.InsertAsync(BatchingMode.None, entity);
}
catch(AzureTableDataStoreSingleOperationException<MyEntity> e)
{
var entityWithIssues = e.Entity; // The entity we were inserting
var innerException = e.InnerException; // The exception that blew things up
if(e.BlobOperationExceptions.Any()) // this will be true
{
LargeBlob blob = e.BlobOperationExceptions[0].SourceBlob; // blob that caused the issue
MyEntity ent = e.BlobOperationExceptions[0].SourceEntity; // the parent entity
Exception ex = e.BlobOperationExceptions[0].InnerException; // Exception "BOOM!"
}
}
Represents a failure of one or more operations in a set of multiple non-batched operations. This is thrown from insert, replace, merge and delete operations when multiple entities are given to the method to handle with BatchingMode.None
.
The exception is effectively a holder for multiple AzureTableDataStoreSingleOperationException<TData>
exceptions.
The SingleOperationExceptions
property holds a list of them.
Example:
MyEntity[] entities = { ... };
try
{
await store.InsertAsync(BatchingMode.None, entities);
}
catch(AzureTableDataStoreMultiOperationException<MyEntity> e)
{
foreach(AzureTableDataStoreSingleOperationException<MyEntity> ex in e.SingleOperationExceptions)
{
// Handle each exception.
}
}
Represents a failure within a batched operation. Thrown when there's an error stemming from table operations or blob operations or data serialization while processing batch/sub-batches of entities. Thrown when selected batching mode is BatchingMode.Strict
, BatchingMode.Strong
and BatchingMode.Loose
.
The exception is an aggregate of multiple exceptions. The BatchExceptionContexts
property contains a list of structures of type BatchExceptionContext<TData>
that contain details of each sub-batch failures (for clarification: for example, a batch request of 150 entities gets split to 2 sub-batches because a table API batch call may only contain max 100 entities, so there is one BatchExceptionContext per sub-batch making it a total of 2 in this case).
Each BatchExceptionContext instance holds the list of entities that were contained in that batch in the property BatchEntities
, then also the table operation exception if there was one in the TableOperationException
property, a list of raised AzureTableDataStoreBlobOperationException<TData>
exceptions in the property BlobOperationExceptions
and finally in the case of serialization issues, the failing entity in the CurrentEntity
property.
Example:
// Let's imagine that we have 150 entities here, of which many
// will have issues and both sub-batches will fail.
MyEntity[] entities = { ... };
try
{
await store.InsertAsync(BatchingMode.Strong, entities);
}
catch(AzureTableDataStoreBatchedOperationException<MyEntity> e)
{
// Top level failure information
_logger.Log(e.Message);
_logger.Log(e.InnerException);
// Contexts hold sub-batch level information
foreach(BatchExceptionContext<MyEntity> batchContext in e.BatchExceptionContexts)
{
_logger.Log("This batch of entities had insert issues: " +
string.Join(",", batchContext.BatchEntities.Select(x => x.Id));
if(batchContext.TableOperationException != null)
_logger.Log("The batch insertion table operation failed: " +
batchContext.TableOperationException.Message;
if(batchContext.BlobOperationExceptions.Any())
{
var errorMessages = batchContext.BlobOperationExceptions.Select(x =>
$"Entity id {x.SourceEntity.Id} blob operation for {x.SourceBlob.Filename} had an exception: " +
x.InnerException.Message);
_logger.Log(string.Join(";", errorMessages));
}
}
}
This exception should never be thrown directly; instead other exceptions may have collections of these inside them. These are thrown within TableDataStore when a blob storage operation fails.
The exception holds a reference to the LargeBlob
instance in the SourceBlob
property that was the subject of the operation, as well as the entity itself in the SourceEntity
property that the LargeBlob instance belongs to.
The InnerException holds the exception that caused the failure.
This exception represents a failure while executing or preparing to execute a query. The InnerException property holds the real reason for the failure. Issues like failing to translate the LINQ query expression to a table filter can cause this, as well as any issues with the table storage itself.