Showing posts with label Azure. Show all posts
Showing posts with label Azure. Show all posts

Wednesday, October 13, 2010

Azure Tables Error Messages

Azure Table Storage (ATS) is a REST-basead data access mechanism, and REST is based on HTTP. So, makes sense that ATS data access errors are returned as HTTP errors. But some translation is needed to understand those errors.

1. DataServiceRequestException and DataServiceClientException exceptions

When a data access error occurs in ATS, the exception returned usually is from DataServiceRequestException class, being the error message “An error ocurred while processing this request”. This exception encapsulates a more specific DataServiceClientException exception, that contains a XML document on the Message property describing the error:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
    <error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <code>EntityAlreadyExists</code>
    <message xml:lang="pt-BR">The specified entity already exists.</message>
</error>

The value on the <code> element needs to be interpreted considering what the application was doing when the error ocurred. For example, the code InvalidInput is generated if you are trying to save an entity that has a property of an unsupported data type; OR if you are trying to update an entity that has no concurrency control information. Almost the same, right? ;-)

2. Error codes an possible causes

Following are the HTTP error code that we’ve bumped into, and what the application was trying do do when the error ocurred.

Error Code
(value on <code> element)
Possible Causes
ConditionNotMet - Concurrency error. The app read some entity; when trying to modify or delete the entity, it had already been modified by a command from another data context.
EntityAlreadyExists - Primary key violation. The app is trying to insert an entity, but there’s an entity on the table with the same values for PartitionKey and RowKey properties on the entity being inserted.
InternalError - Use of unsupported Linq operators on ATS. (We found this one trying to recover rows that had a string property starting with some value. There’s no string support for like, >, <, and neither string.StartsWith() – the one that generated the error. You have to use string.CompareTo() – :-P)
InvalidInput - Storing an entity that has properties of types not supported by ATS.
- Updating an entity that was attached to the context (TableServiceContext.AttachTo()), but has null on it’s
ETag property.
- Linq queries on a table with no structure defined. We received this error when trying to query a table that had just been created; if you insert some entities, even if you erase all the entities on table later, it seems that the table holds the entity structure and you can use it on Linq queries.
- Linq queries using
anonymous types. The workaround was to make the query return the “whole” entity, and build a second query using anonmymous types on the data returned by ATS.
- Batch save operation (SaveChanges(SaveOptions.Batch)) with 2 or more entities having the same Partion and Row Keys.
ResourceNotFound - Linq query that returns no entitites using equality conditions on PartitionKey and RowKey (where entity.PartitionKey == “…” && entity.RowKey == “…”). (Queries with conditions using other attributes return a 0-sized list)
- Concurrency error. The app read some entity; when trying to modify or delete the entity, it had already been deleted by a command from another data context.
- Linq query on a table that does not exist on ATS.
TableAlreadyExists Found on calls to CloudTableClient.CreateTablesFromModel(). It seems that concurrent calls to this method may end up generating this exception. We placed a call to CreateTablesFromModel() on the data access context static constructor. When we raised the number of role instances to 2, one of them started ok, and the other one generated the exception.
TableBeingDeleted Same as TableAlreadyExists above.
OutOfRangeInput Generated when saving an entity with an uninitialized DateTime property. The minimum allowed value for a DateTime property on ATS is Jan 01, 1601, and an uninitialized DateTime in C# holds Jan 01, year 1. We didn’t detected it on tests against DevStorage because it changes dates before Jan 01, 1753 to Jan 01, 1753, but ATS does not.

3. How to extract the HTTP error code from DataServiceClientException objects

The code is based on an enum that has members named exactly as the error codes returned by ATS:


public enum ATSError
{
    EntityAlreadyExists,
    ConditionNotMet,
    InvalidInput,
    ResourceNotFound, 
    Unknown  // This one is the "generic" error code 
}

The following method extracts the error code from the XML returned by ATS, and converts it on the correspondent ATSError enum value:

/// <summary>
/// Translates an ATS exception to one of the ATSErrors values.
/// </summary>
/// <param name="error">Exception returned by ATS.</param>
/// <returns></returns>
public static ATSError ExtractATSError(Exception e)
{
    // Searches for a DataServiceClientException exception
    while (!(e is System.Data.Services.Client.DataServiceClientException) && (e != null)) 
        e = e.InnerException;
    if (e == null) return ATSError.Unknown;

    // At this point "e" contains a DataServiceClientException object,  
    // and the Message property contains a valid XML document describing the error
    ATSError code;
    try
    {
        XmlDocument xml = new XmlDocument();
        xml.LoadXml(e.Message);
        // The XML document uses namespace http://schemas.microsoft.com/ado/2007/08/dataservices/metadata, so 
        // we have to use a XmlNamespaceManager to represent this namespace
        XmlNamespaceManager namespaceManager = new XmlNamespaceManager(xml.NameTable);
        namespaceManager.AddNamespace("n", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
        // Retrieves the "<code>" element 
        string s = xml.SelectSingleNode("/n:error/n:code", namespaceManager).InnerText;
        // Converts the element value to the corresponding ATSError value
        code = (ATSError)Enum.Parse(typeof(ATSError), s);
    }
    catch
    { 
        code = ATSError.Unknown;
    }
    return code;
}

And that’s it. If you bump into another error code, just add the corresponding member on the ATSError enum, so that the ExtractATSError() method will recognize it.

Tuesday, August 24, 2010

Error on Azure Development Storage startup

Right after you install Azure Tools for Visual Studio, if you try to start the Development Storage service, a command prompt window pops up, then shows a error message in red, then closes so fast you can't even start to read the error message. I've tried to start the Development Storage from a command prompt to see what was the error. But the .lnk file to Dev Storage does not show what is the command to start it.
Tip 1: To start Azure Development Storage from a command prompt, open an Azure Command Prompt (Start > All Programs > Windows Azure SDK > Windows Azure SDK Command Prompt), and then run csrun /devstore.
Ok. Then I could see the error message:
Encountered an unexpected error from the devstore: Unable to start Development Storage.
Error Details: Failed to start Development Storage: the SQL Server instance 'localhost\SQLExpress' could not be found. Please configure the SQL Server instance for Development Storage using the 'DSInit' utility in the Windows Azure SDK.
Nothing like being able to read the error message when you're trying to fix some error. That leads to
Tip 2: After you install Azure Tools for Visual Studio, run dsinit /sqlinstance:sql_instance_name to create the database needed to run Development Storage.
Since I already have a non-named default SQL Server instance, I ran dsinit /sqlinstance:. (dsinit /sqlinstance "colon" "dot"), as "dot" is the name for a default SQL Server instance. Then Dev Storage started up ok.

Monday, November 9, 2009

Visual Studio: how to target different .NET Framework versions

We are creating a WPF application on Visual Studio 2010 (beta 1), but Blend 3 doesn’t load .NET Framework 4.0 assemblies. So on the designer’s machine we had to compile the solution targeting .NET Framework version 3.5.

(quick thought: our development team is made up of one designer, one product manager, and one project manager. So, WHO IS CODING???)

We found an article on MSBuild – Microsoft’s build engine – about the /toolsversion option, that targets specific .NET Framework versions. We built a .bat file that recompiles all projects in the solution. Following is some of it:

msbuild StorageClient\StorageClient.csproj /toolsversion:3.5 /verbosity:quiet
if ERRORLEVEL 1 goto erro

MSBuild recognizes Visual Studio project files, and activates the corresponding compiler. The /toolsversion option selects the target .NET Framework version (allowed values are 2.0, 3.0, 3.5 and 4.0), and overrides the selected version on the project file. The following “if” checks if MSBuild returned 1, which signals an error, so you can stop the build job (using good(?) old goto).

A lot less painful than I thought. The same batch will be used to generate a 3.5 version of the solution when building the tables on Azure Development Storage, because DevTableGen – the utility that builds the tables – also does not support 4.0 .NET Framework assemblies.

MSBuild location is not included on the standard command prompt path, so use Visual Studio 2010 command prompt (VS 2010 program group > Visual Studio 2010 Tools).

Friday, October 2, 2009

400 Bad Request accessing Azure Blob Storage

This one was kind of dumb. I'm posting it in hope someone doesn't loose as much time as I've lost. When I was trying to retrive a Blob Container object from Azure Blog Storage, I kept getting a "400 Bad Request" exception. After 3 hours I gave up and asked a friend to look at my code. In 3 minutes he spot the bug: I was using the Table Storage credentials on the acces to the Blob Storage.

Wrong code:
BlobStorage blobs = BlobStorage.Create(
StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration());
Right code:
BlobStorage blobs = BlobStorage.Create(
StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration());
The StorageAccountInfo class has 3 methods to retrieve default credentials: GetDefaultTableStorageAccountFromConfiguration(), GetDefaultBlobStorageAccountFromConfiguration(), and GetDefaultQueueStorageAccountFromConfiguration(). One for each storage Azure has. Big names with little differences between them...

Wednesday, September 30, 2009

404 Resource Not Found For Segment accessing Azure Table Storage

On Windows Azure Table Storage, when you are searching for items (or entities, as the Azure documation calls them) using LINQ on a table, under some circumstances you may get a 404 - Resource Not Found error. That's because Azure Table Storage is REST-based, and REST is about servers serving "resources" to clients. For example, HTTP is REST-based, and when you ask for a file that doesn't exist on a server, you get a "404 Page Not Found" error.

What is happening is pretty much the same. According to a comment by Andrew Conrad, from Microsoft LINQ To REST project team, if you search a list by its keys, you want a specific resource, and if the resource is not found, the 404 error is generated. This may make sense from a REST point of view, but if I'm accessing a table, if I don't find what I'm looking for, I expect a empty resultset, and not an exception. By Andrew's comment above, the LINQ to REST team is aware of this peculiarity on Azure, and plans on doing something more intuitive in the future.

For now you will have to use a ugly try/catch block around your code:
try
{
var element =
(for item in azureContext.MyTable
where item.PartitionKey = '...' and item.RowKey = '...'
select item).FirstOrDefault();
}
catch (DataServiceQueryException ex)
{
if(ex.Response.StatusCode == 404)
// element not found
}