Implementing a data repository class with a _true_ business object all with TDD – level 300

I’ve revised my previous sample that contained a Category class.  This
Category has an id, a name and description.  There is a Category table to
persist the object. I pulled everything data out of my business object and
created a CategoryData object that will know the DAL and know how to persist and
hydrate the Category object.  This approach greatly simplifies the business
object and decouples persisting the object to the database.  In the future, if I
changed the persistence method, I don’t have to change the business object, just
the data object.  Here are my sample classes:

Category:  note how simple my business object is now:

    7 public class Category {
    8 
    9    
private int
_id = 0;
   10     private string _name
= string.Empty;
   11     private
string _description = string.Empty;
   12 
   13    
public int ID
{
   14         get { return this._id; }
   15         set {
this._id = value; }
   16     }
   17 
   18    
public string
Name {
   19         get { return this._name; }
   20         set {
this._name = value; }
   21     }
   22 
   23    
public string
Description {
   24         get { return this._description; }
   25         set {
this._description = value; }
   26     }
   27 }

And my unit test class for this:

   12 [TestFixture]
   13 public class CategoryTest
   14 {
   15    
private Category _category = null;
   16 
   17    
public CategoryTest() {
   18         Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
   19     }
   20 
   21    
[SetUp]
   22     public void
GetACategory(){
   23         this._category = new
Category();
   24     }
   25 
   26    
[Test]
   27     public void
CreateCategory() {
   28        
Assert.IsNotNull(this._category);
   29     }
   30 
   31    
[Test]
   32     public void
CategoryID() {
   33         this._category.ID = 2;
   34         Assert.AreEqual(2, this._category.ID);
   35     }
   36 
   37    
[Test]
   38     public void
CategoryName() {
   39         this._category.Name = “Beverages”;
   40         Assert.AreEqual(“Beverages”,
_category.Name);
   41     }
   42 
   43    
[Test]
   44     public void
CategoryDescription() {
   45         this._category.Description =
“MyDescription”;
   46        
Assert.AreEqual(“MyDescription”, _category.Description);
   47     }
   48 }

Then I have my CategoryData object that knows how to persist using the
DAAB:

    7 public class CategoryData
    8 {
    9    
private Database _database = null;
   10 
   11    
public Database Database {
   12         get {

   13             if(this._database ==
null)
   14                 this._database =
DatabaseFactory.CreateDatabase(“DEV”);
   15             return this._database; }
   16         set {
this._database = value; }
   17     }
   18 
   19    
public Category GetCategory(int categoryID) {
   20         Category cat = null;
   21    
    using(IDataReader reader = this._database.ExecuteReader(“GetCategoryByID”,
categoryID)) {           
   22 
   23             if(reader.Read()){
   24                 cat = new Category();
   25                 cat.ID =
reader.GetInt32(reader.GetOrdinal(“CategoryID”));
   26                 cat.Name =
reader.GetString(reader.GetOrdinal(“CategoryName”));
   27                 cat.Description =
reader.GetString(reader.GetOrdinal(“Description”));
   28                 return cat;
   29             }else {
   30    
            return null;   
   31             }
   32         }
   33     }
   34 
   35    
public void
Insert(Category newCategory) {
   36        
newCategory.ID = (int)this._database.ExecuteScalar(“InsertCategory”,
newCategory.Name, newCategory.Description);
   37     }
   38 }

And the unit tests with stubs for the CategoryData class:

   12 [TestFixture]
   13 public class CategoryDataTest
   14 {
   15    
private CategoryData _categoryData = null;
   16 
   17    
[SetUp]
   18     public void
GetACategory(){
   19         this._categoryData = new CategoryData();
   20         this._categoryData.Database = new DatabaseStub();
   21     }
   22 
   23    
[Test]
   24     public void
GetCategory() {
   25         Category cat =
this._categoryData.GetCategory(1);
   26         Assert.IsNotNull(cat);
   27         Assert.AreEqual(1, cat.ID);
   28         Assert.AreEqual(“Beverages”,
cat.Name);
   29        
Assert.AreEqual(“MyDescription”, cat.Description);
   30     }
   31 
   32    
[Test]
   33     public void
GetCategoryThatDoesntExist() {
   34        
Category cat = this._categoryData.GetCategory(500);
   35         Assert.IsNull(cat);
   36     }
   37 
   38    
[Test]
   39     public void
InsertCategory() {
   40         Category
cat = new Category();
   41         cat.Name = “Jeffrey”;
   42         cat.Description = “Hello
category”;
   43         this._categoryData.Insert(cat);
   44         Assert.IsTrue(cat.ID == 400);
   45     }
   46 
   47    
internal class
DatabaseStub : SqlDatabase {
   48        
public override object
ExecuteScalar(string storedProcedureName, params object[]
parameterValues) {
   49             switch(storedProcedureName) {
   50                 case “InsertCategory”:
   51                     return 400;
   52                     break;
   53    
        }
   54 
   55             return null;
   56    
    }
   57 
   58 
   59    
    public override IDataReader ExecuteReader(string storedProcedureName, params object[]
parameterValues) {
   60 
   61             switch(storedProcedureName) {
   62                 case “GetCategoryByID”:
   63                     if(parameterValues.Length == 1 && (int)parameterValues[0] == 1) {
   64                         return new
DataReaderForCategory1Stub();
   65        
            }
   66                    
break;
   67             }
   68 
   69    
        return new EmptyDataReaderStub();
   70         }
   71     }
   72 
   73    
internal class
DataReaderForCategory1Stub : IDataReader  {
   74 
   75    
    public bool Read() {
   76             Trace.WriteLine(“Returning true
from Read()”, this.GetType().Name);
   77             return true;
   78    
    }
   79 
   80         public
string GetString(int index) {
   81             string retValue = string.Empty;
   82             switch(index) {
   83                 case 1:
   84    
                retValue = “Beverages”;
   85                     break;
   86    
            case 56:
   87                     retValue =
“MyDescription”;
   88                    
break;
   89             }
   90 
   91    
        return retValue;
   92         }
   93 
   94    
    public int
GetInt32(int index) {
   95             int retValue = int.MinValue;
   96             switch(index) {
   97                 case 0:
   98    
                retValue = 1;
   99        
            break;
  100             }
  101 
  102    
        return retValue;
  103         }
  104 
  105    
    public int
GetOrdinal(string name) {
  106             int retValue = 0;
  107             switch(name) {
  108                 case “CategoryName”:
  109                     retValue = 1;
  110                     break;
  111    
            case “CategoryID”:
  112                     retValue = 0;
  113                     break;
  114    
            case “Description”:
  115                     retValue = 56;
  116                     break;
  117    
        }
  118 
  119             return retValue;
  120         }
  121 
  122        
#region
IDataReader Members
  123 
  124         // extra members omitted
  279        
#endregion

  280     }
  281 
  282    
internal class
EmptyDataReaderStub : IDataReader {
  283        
#region
IDataReader Members
  284 
  285         // implementation omitted 
  468        
#endregion

  469 
  470     }
  471 }

This makes my model so much easier because now my business rules don’t have
to have anything to do with the database, and if we change databases later, my
business object doesn’t have to change.  I’ve been talking with Scott Bellware a lot lately
about Domain-driven design, and it’s some pretty deep stuff.  One thing we agree
on:  RAD sucks!