Given all latest changes to SDKs and tools, I think a significant update is needed for my old blog post on creating a data access layer for Azure Table Storage in Silverlight (Porting Silverlight RIA to Windows Azure - Part 1). It is now a part of whitepaper I'm working on, soon to be released on MSDN.
Note: Steps 1-3 represent one server-side model class, step 4 represents server-side data service class and steps 5-7 represent third, client-side view model class.
Without EF-generated entities, one can create data model manually with help of plain old CLR objects (POCO) that look like this:
public class Report : TableServiceEntity
{
public Report()
}
public Report(string title, string description, string initiatorId, string solverId, string attachments)
: base(title, title)
Title = title;
Description = description;
InitiatorId = initiatorId;
SolverId = solverId;
Attachments = attachments;
DateCreated = DateTime.UtcNow;
ReportId = Guid.NewGuid();
public string Title { get; set; }
public string Description { get; set; }
public string Solution { get; set; }
public string InitiatorId { get; set; }
public string SolverId { get; set; }
public string Attachments { get; set; }
public DateTime DateCreated { get; set; }
public Guid ReportId { get; set; }
Please note the use of TableServiceEntity as a base class – it is a class from built-in Windows Azure StorageClient library representing an entity in ATS. Among other things it adds ATS-specific attributes such as definitions of RowKey and PartitionKey to these entity classes.
Now, we need to wrap our entities into a model class that will serve as a representation of our entities visible to all clients. This not only allows us to separate the database representation of the entities from what we want to expose to the clients but also allows us to take advantage of StorageClient’s TableServiceContext class that will be used later by WCF DS.
public partial class AzureModel : TableServiceContext
private const string ReportsTableName = "Reports";
public AzureModel(string baseAddress, StorageCredentials credentials) : base(baseAddress, credentials)
public IQueryable<Report> Reports
get { return CreateQuery<Report>(ReportsTableName); }
...
The reason you need IUpdatable implementation is because ADO.NET uses it to update the storage. The ADO.NET Entity Framework's ObjectContext does not implement IUpdatable, but its ObjectContextServiceProvider does, so building a Data Service with an ObjectContext gives you an update support for free; using LINQ to SQL with a DataContext results in using the ReflectionServiceProvider that has no built in support for IUpdatable operations and relies on the data service context to implement it; same applies to any other DataServiceContext-based context classes such as StorageClient’s TableServiceContext used here. Therefore, we need to create our own IUpdatable implementation. After that is done, you need to do is to inherit your model class from it.
public partial class AzureModel : IUpdatable
public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
AddLink(targetResource, propertyName, resourceToBeAdded);
public void ClearChanges()
// clear out links
foreach (LinkDescriptor link in Links)
DetachLink(link.Source, link.SourceProperty, link.Target);
// clear out entities
foreach (EntityDescriptor entity in Entities)
Detach(entity.Entity);
public object CreateResource(string containerName, string fullTypeName)
object obj = Activator.CreateInstance(GetType().Assembly.GetType(fullTypeName));
AddObject(containerName, obj);
return obj;
public void DeleteResource(object targetResource)
DeleteObject(targetResource);
public object GetResource(IQueryable query, string fullTypeName)
object resource;
// fullTypeName can be null for deletes
if (fullTypeName == null)
resource = query.Cast<BaseEntity>().SingleOrDefault();
else
// Check for types that will be updated or deleted
if (fullTypeName == typeof(Report).FullName)
resource = query.Cast<Report>().SingleOrDefault();
…
UpdateObject(resource);
return resource;
public object GetValue(object targetResource, string propertyName)
Type type = targetResource.GetType();
PropertyInfo propInfo = type.GetProperty(propertyName);
if (propInfo == null)
throw new Exception(string.Format("Can't find given property '{0}'.", propertyName ?? "NULL"));
object val = propInfo.GetValue(targetResource, null);
return val;
public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
DeleteLink(targetResource, propertyName, resourceToBeRemoved);
public object ResetResource(object resource)
Detach(resource);
public object ResolveResource(object resource)
// Already gave object in CreateResource
void IUpdatable.SaveChanges()
try
SaveChanges();
catch (KeyNotFoundException e)
var exception = e;
public void SetReference(object targetResource, string propertyName, object propertyValue)
SetLink(targetResource, propertyName, propertyValue);
public void SetValue(object targetResource, string propertyName, object propertyValue)
PropertyInfo propInfo = targetResource.GetType().GetProperty(propertyName);
throw new Exception(string.Format("Can't find property '{0}'", propertyName ?? String.Empty));
propInfo.SetValue(targetResource, propertyValue, null);
The overall flow of data is shown in Figure 1. Please note that we are calling DataServiceContext.UpdateObject() when we need to “synchronize” two contexts – client and server – as they don’t automatically update each other.
Figure 1. Accessing data in ATS via WCF DS
Please note that we are updating the object when it is being requested to accommodate all changes that occured on the client to this object as they don’t automatically propagate to the other client context we use to access data in ATS.
Add new ADO.NET Data Service item to your Azure web role project. You’ll have to reference your DAL project to have access to the model and then you can base your data service on it:
public class AzureDS : DataService<AzureModel>
public static void InitializeService(IDataServiceConfiguration config)
Logger.Info("InitializeService");
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
catch (Exception e)
Logger.Error(e.Message);
protected override AzureModel CreateDataSource()
var cloudStorageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
string baseAddress = cloudStorageAccount.TableEndpoint.ToString();
StorageCredentials credentials = cloudStorageAccount.Credentials;
var azureModel = new AzureModel(baseAddress, credentials);
return azureModel;
Now, add a service reference to the data service in your Silverlight project as you would do with any other web service:
Figure 2. Adding a service reference to WCF Data Service
In view model class, you need to initialize data context with service URI. It’s a good idea to abstract the data service context with help of object property:
protected AzureModel Context
get
if (_model == null)
_model = new AzureModel(new Uri("AzureDS.svc", UriKind.Relative))
MergeOption = MergeOption. PreserveChanges
};
return _model;
Please note - you might want to switch between NoTracking and PreserveChanges merge options here to achieve maximum performance of the queries as NoTracking is faster but can be used safely only when there are no changes being made in the client context. You can find more about various optimizations in the next chapter.
Modify all your Silverlight code so that it uses LINQ to SQL to query objects and uses DataServiceContext’s BeginSaveChanges to make updates.
public void CreateReport(string liveUserId, string title, string description, string categoryId)
//Associate with current user
var query = (DataServiceQuery<User>)(from c in Context.Users
where c.RowKey == liveUserId
select c);
// Execute the query
query.BeginExecute(a =>
_matchingUsers = query.EndExecute(a).Select(user => (IUserElement)new UserElement(user));
IUserElement userElement = _matchingUsers.FirstOrDefault();
var report = new Report
Title = title,
Description = description,
InitiatorId = userElement.LiveUserId,
DateCreated = GetServerDateTime(),
CategoryId = categoryId,
Solution = String.Empty,
SolverId = String.Empty,
Attachments = String.Empty
report.RowKey = string.Format("{0:D19}", DateTime.MaxValue.Ticks - DateTime.UtcNow.Ticks);
report.PartitionKey = report.DateCreated.ToString("yyyy-MM");
report.ReportId = Guid.NewGuid();
Context.AddToReports(report);
Context.BeginSaveChanges(CreateReport_EndSaveChanges, report);
catch (Exception ex)
RaiseErrorHandler(new ErrorEventArgs(ex));
},
null);
private void CreateReport_EndSaveChanges(IAsyncResult result)
Context.EndSaveChanges(result);
CreateReportCompleted(this, new CreateReportEventArgs(((Report)result.AsyncState)));
catch (DataServiceRequestException ex)
HtmlPage.Window.Alert("Error occurred while creating report: " + ex.Response);
HtmlPage.Window.Alert("Error occurred while creating report: " + ex.Message);