So, I know I was supposed to talk about my user stories / use cases for the
Producer/Wine model of the virtual cellar, but I decided to delay that a little
bit to talk about a few things that have been going on. I went to the dentist
today to get my teeth cleaned and it just so happens that the weather in Seattle
cleared up (stopped raining) long enough for me to ride my bike in from Bellevue
to my dentist in Seattle. Usually I listen to the radio when I ride my bike (in
one ear only), but this time I forgot to pack my cell phone, so I had no one to
bother me except myself. And during my ride, and the time in the dentist chair I
began to think...
The Master Detail Relationship
There's been a little bit of code that's been bothering me in the
VirtualCellar, and I'm not sure what I'm going to do about it. If anybody has
any ideas, please pass them along. If we look at the UML diagram from yesterday,
you'll see that the Wine class has a Varietals property that is a collection
WineVarietal classes. Indeed I have a WineVarietalCollection class that derives
from System.Collections.CollectionBase to handle the classes in a strongly typed
manner. The thing that is bothering me is how to handle the persisting of the
classes (and underlying records). Here's a simple illustration of what I'm
talking about.
We have a class Master that has an attribute Details, which is a collection
of Detail classes for the instance of Master with a particular .Id attribute
class Master
{
public int Id
{
get {return _id;}
}
public string Name
{
get {return _name;}
set {_name = value;}
}
public DetailCollection Details
{
get {return _details;}
}
private string _name;
private DetailCollection _details;
}
class DetailCollection : System.Collections.CollectionBase
{
...CollectionBase Implementation
}
class Detail
{
public Detail(int amount)
{
_amount = amount;
}
public Detail(IDataReader dr)
{
// Simplified, should really check for null
_amount = dr["Amount"];
}
public int Amount
{
get {return _amount;}
set {_amount = value;}
}
private int _amount;
}
|
To help in the creation of a Master class instance, I create a
MasterFactory class that has the following definition:
class MasterFactory
{
public static Master Create(IDataReader dr)
{
Master m = new Master();
// Simplified, should really check for null
m.Id = dr["Id"];
m.Name = dr["Name"];
// Load the Details attribute with the matching Detail classes
m.Details = new CreateDetailCollection(m.Id);
return m;
}
// Creates a DetailCollection for the given Master Id
private DetailCollection CreateDetailCollection(int masterId)
{
DetailCollection dc = new DetailCollection();
// Not shown, but GetDetailDataProvider returns a dataprovider class
// that implements the IDetailDataProvider interface (methods to retrieve information
// from the Detail dataset) for the database configured in the .config file
IDetailDataProvider ddp = new GetDetailDataProvider();
// Get the Detail records for the specified Master record
IDataReader dr = ddp.GetByMasterAsDataReader(masterId);
// Exception handling purposely left out for clarity
while(dr.Read())
{
// Add a new Detail class to the DetailCollection
dc.Add(new Detail(dr));
}
return dc;
}
}
|
I also toyed with the idea of having the DetailCollection class have a
constructor that looked like this
public DetailCollection(int masterId)
{
// Put CreateDetailCollection(int masterId) implementation here
}
|
However I decided against this because the DetailCollection (or WineVarietalCollection) class is really a part of the Master
class and the responsibility for it's creation should be handled in the
MasterFactory class. Opinions on this anybody?
Now, to get to the real meat of
the problem. How to handle changes to items in the Detail collection, either
modifications of existing Detail items, or the addition or removal of Detail
items in the collection?
To address this I'll need to introduce the MasterRepository class.
public MasterRepository : IRepository
{
public RepositoryItem Load(int id)
{
return MasterFactory.Create(id);
}
public Save(RepositoryItem ri)
{
Master m = ri as Master;
switch(ri.ItemState)
{
case RepositoryItemState.New
{
ri.Id = GetMasterDataProvider().Add(m.Name);
AddDetailCollection(m.Details);
ri.ItemState = RepositoryItemState.Clean;
break;
}
case RepositoryItemState.Dirty
{
GetMasterDataProvider().Update(m.Id, m.Name);
UpdateDetailCollection(m.Id, m.Details);
ri.ItemState = RepositoryItemState.Clean;
}
}
// Add a Detail record for each class in the collection
private void AddDetailCollection(int masterId, DetailCollection dc)
{
IDetailDataProvider ddp = GetDetailDataProvider();
// Add back all the Detail records in the passed DetailCollection
foreach(Detail d in dc)
{
ddp.Add(masterId, d.Amount);
}
}
// Update the Detail records by deleting existing records and then
// adding a Detail record for each class in the collection
private void UpdateDetailCollection(int masterId, DetailCollection dc)
{
IDetailDataProvider ddp = GetDetailDataProvider();
// Delete all the records in the Detail table for the given master
ddp.DeleteAllByMaster(masterId);
// Add the collection back
AddDetailCollection(masterId, dc);
}
}
|
So now I can do the following to Load an existing Master instance, modify the
Detail records and then save the changes...
Master m = MasterRepository.Load(1);
m.Details.Add(new Detail(21));
MasterRepository.Save(m);
|
Of course, I've left out all of the transaction code to make sure that
all the updates happen together, but I wanted to keep the problem and
solution simple. I also toyed with the idea of having a public DataSet
GetMasterAsDataSet(int id) method, and return a DataSet with two Tables
(Master, Detail) and a DataRelation setup between them. Then I could make
the Master class encapsulate the DataSet and adapt Master and Detail
operations to DataSet methods. Any opinions on that approach?
Incidentally, David Hayden, discusses
Class responsibilities with respect to contained aggregates on his blog.
 |
Alright, it's been a few days (about a month) since I've posted
last. Let me just say that I've had a great excuse. Vanessa and I took a
little vacation during my "time off". We flew to Barcelona, Spain for a
few days, and then hopped on a cruise ship that took us to Tarragona,
Gibraltar, Cadiz, Marrakech, Funchal, Lanzarote, across the Atlantic to
St Maarten and the Bahamas, finally dropping us of in Ft. Lauderdale. I
must say that this was one enjoyable trip. The picture below is from the
Rock of Gibraltar where the Apes (not monkeys) run wild. I'd like to
call my vacation a research trip since I was able to find (and drink)
many great Spanish wines from Rioja, Catalunya, Ribo Del Duero and great
sherry from Jerez. |
So, I've coded enough of my model to begin entering information into the
application. Here's what my implementation looks like at this point from the
perspective of the core business classes. I've left out the Repository classes
for the Wine and Producer classes.

This set of classes will allow me to begin entering information into the
application. To that end, I'll be focusing on creating a simple web application
that will allow me to add Producers and Wines to the database. The database will
contain all of the "known" wines. The next step is to begin modeling a
particular user's collection of know wines which will be a subset of the known
Catalog. Of course the user will store her wine in a Cellar by the Bottle.
So tomorrow I'll revisit my User Stories with respect to the above model.