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:
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:
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:
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:
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!