1. Duplicating
data instead of joining
Because the shirt data
isn’t static data but is infrequently changing data, you need to find a
different way of associating that data with the dynamic shopping cart
item. For this example, you can duplicate the shirt data within the
shopping cart item, as shown here:
public class ShoppingCartItem : TableServiceEntity
{
public string ShirtName { get; set; }
public string ShirtDescription { get; set; }
public int Price { get; set; }
public Material Material { get; set; }
public SizeType Size { get; set; }
}
The preceding code stores a
complete copy of the selected shirt as part of the shopping cart item’s
entity.
Note
This approach will mean that
we won’t need to perform a join with the Shirts table, but it does mean
that our table will be much larger than a traditional relational table,
meaning higher storage costs.
Now that you have your
shopping cart item, you need to correctly display your StrongShoppingCartItem object. The following code shows how you could
modify the earlier query to do this.
select new StrongShoppingCartItem
{
SelectedShirt = new Shirt {PartitionKey="Shirts",
RowKey=shoppingCartItem.ShirtName,
Description=shoppingCartItem.ShirtDescription,
Price=shoppingCartItem.Price},
Material = material,
Size = sizeType
};
As
you can see from the preceding code, you can project the duplicate data
into the StrongShoppingCartItem object by instantiating a new Shirt
object with the data from the ShoppingCartItem.
Apart from it taking up more
space, another issue with duplicating data is data synchronization.
Although the shirt data is
infrequently changed, when a change does occur (such as the price), all
items that are present in a customer’s shopping basket won’t be
automatically updated with the new price. If your business model allows
you to have stale data, this is obviously not a problem. But if your
pesky customers want the correct price to be reflected in the shopping
basket, you’ll need some method of synchronizing the master table to all
the duplicates.
A simple method of keeping
the data synchronized is to publish a message to a queue, stating that
an item has changed. Then you can have a worker role pick up that
message and update all items in the table with the correct data.
|
2. Client-side
joining of uncached data
If data synchronization is a big
concern and your dynamic data is a very small set of data, you could
take the hit of performing a client-side join. To do that, you’d need to
modify the ShoppingCartItem to support the join:
public class ShoppingCartItem : TableServiceEntity
{
public Shirt Shirt {get;set;}
public Material Material { get; set; }
public SizeType Size { get; set; }
}
In the preceding code,
the duplicate shirt properties have been replaced with a reference to
the shirt.
Now that the entity stores a
reference, you need to modify your query to join the data together. The
following code shows how you could do this:
var materials = (IEnumerable<Material>)Cache["materials"];
var sizeTypes = (IEnumerable<SizeType>)Cache["sizeTypes"];
var shoppingCartItemContext = new ShoppingCartItemContext ();
var shoppingCartItems =
shoppingCartItemContext.ShoppingCartItem.ToList();
var shirtsContext = new ShirtContext();
var q = from shoppingCartItem in shoppingCartItems
join sizeType in sizeTypes
on shoppingCartItem.Size.RowKey equals sizeType.RowKey
join material in materials
on shoppingCartItem.Material.RowKey equals material.RowKey
select new ShoppingCartItem
{
SelectedShirt = (from shirt in shirtsContext.ShirtTable
where shirt.PartitionKey=="Shirts" &&
shirt.RowKey ==
shoppingCartItem
.SelectedShirt.RowKey
select shirt).First(),
Material = material,
Size = sizeType
};
The key thing to note about the preceding example
is that the shirt query isn’t cached, and it will invoke a call to the
Table service for each item returned. The following extract shows where
this is performed:
SelectedShirt = (from shirt in shirtsContext.ShirtTable
where shirt.PartitionKey=="Shirts" &&
shirt.RowKey == shoppingCartItem.SelectedShirt.RowKey
select shirt).First(),
Because the data returned
from the shopping cart is small, this is a pretty useful technique. If
you were dealing with a much larger set of shopping cart data (such as
hundreds of items), this would start to perform badly.
Tip
If you’re working
with infrequently changing data (such as the shirt data), you may wish
to consider using SQLite to host a local cached version of your data.
This would allow you to perform SQL queries on local cached data without
calling out to the Table service or SQL Azure.
Not having the ability to join
data across tables does present challenges, but not always impossible
ones. For the most part, you can use different techniques to get around
those limitations. But in some circumstances it’s going to be impossible
to use the Table service. If you have lots of dynamic data and need to
perform live queries across various table joins, you’re not going to be
able to represent that easily in the Table service. In these instances,
SQL Azure is the most appropriate choice. Similarly, if you need to
perform transactions across various tables, SQL Azure is again the right
choice.
If you’re storing your data in
SQL Azure or the Table service and you need to perform text searches,
you can export your data out of these databases and perform searches
with Lucene.NET: http://lucene.apache.org/lucene.net/.
|