Applying UML and Patterns - An Introduction to Object-Oriented Analysis and Design and Iterative Development by Craig Larman has a short chapter that discusses the creation of a persistence framework with design patterns. Most of it has to deal with mapping objects to database tables and the design patterns one might use in his/her home grown O/R Mapper. Honestly, it is more on an introduction to design patterns and I am not recommending you use any of the ideas to build your own O/R Mapper. Building an O/R Mapper / Persistence Framework is no easy task.
The book doesn't go into the persistence framework in a lot of detail, so I though I would expand upon the ideas and share it here in the blog.
To start, below is an NUnit test that show the basic idea. An address is fetched from the repository (database, xml file, etc.) that has a primary key of “1“. I pass the primary key to the repository as well as the class type.
1 using System;
2
3 using MyProject.Domain;
4 using MyProject.Interfaces;
5 using MyProject.Data;
6
7 using NUnit.Framework;
8
9 namespace MyProject.Tests
10 {
11 /// <summary>
12 /// Summary description for AddressTests.
13 /// </summary>
14 [TestFixture]
15 public class AddressTests
16 {
17 [Test]
18 public void GetAddress()
19 {
20 Address address = (Address)Repository.GetInstance().Get(1, typeof(Address));
21 Assert.IsTrue(address.ID == 1);
22 Assert.IsTrue(address.Address1.Equals("1234 Fake Street"));
23 Assert.IsTrue(address.Address2.Equals(string.Empty));
24 }
25 }
26 }
The object in question, Address, is shown here for completeness.
1 using MyProject.Interfaces;
2
3 namespace MyProject.Domain
4 {
5 /// <summary>
6 /// Summary description for Address.
7 /// </summary>
8 public class Address : IAddress
9 {
10 private int _id;
11 private string _address1;
12 private string _address2;
13 private string _city;
14 private string _state;
15 private string _zipCode;
16 private string _country;
17
18 #region IAddress Members
19
20 public int ID
21 {
22 get
23 {
24 return _id;
25 }
26 }
27
28 public string State
29 {
30 get
31 {
32 return _state;
33 }
34 set
35 {
36 _state = value;
37 }
38 }
39
40 public string City
41 {
42 get
43 {
44 return _city;
45 }
46 set
47 {
48 _city = value;
49 }
50 }
51
52 public string ZipCode
53 {
54 get
55 {
56 return _zipCode;
57 }
58 set
59 {
60 _zipCode = value;
61 }
62 }
63
64 public string Address1
65 {
66 get
67 {
68 return _address1;
69 }
70 set
71 {
72 _address1 = value;
73 }
74 }
75
76 public string Country
77 {
78 get
79 {
80 return _country;
81 }
82 set
83 {
84 _country = value;
85 }
86 }
87
88 public string Address2
89 {
90 get
91 {
92 return _address2;
93 }
94 set
95 {
96 _address2 = value;
97 }
98 }
99
100 #endregion
101 }
102 }
Here we have the repository with a single Get method for object retrieval. The repository has to find an appropriate mapper (AddressMapper) for the particular class and passes the primary key and class type to the mapper. In the example in the book, only the primary key was passed to the mapper. However, in the book, the persistence framework instantiated the class directly, which meant I needed to have a reference to my domain objects. In my case, I instantiate the class using reflection with Activator.CreateInstance since I am passing along the class type anyway.
1 using System;
2
3 namespace MyProject.Data
4 {
5 /// <summary>
6 /// Summary description for IRepository.
7 /// </summary>
8 public interface IRepository
9 {
10 object Get(object key, Type classType);
11 }
12 }
1 using System;
2
3 using MyProject.Interfaces;
4
5 namespace MyProject.Data
6 {
7 /// <summary>
8 /// Summary description for Repository.
9 /// </summary>
10 public class Repository : IRepository
11 {
12 public static Repository GetInstance()
13 {
14 return new Repository();
15 }
16
17 #region IRepository Members
18
19 public object Get(object key, Type classType)
20 {
21 IMapper mapper = GetMapper(classType);
22 return mapper.Get(key, classType);
23 }
24
25 #endregion
26
27 private IMapper GetMapper(Type classType)
28 {
29 if (classType.Name.Equals("Address"))
30 return new AddressMapper();
31
32 throw new ArgumentException("No mapper for class name = " + classType.FullName);
33 }
34 }
35 }
Here is the AddressMapper that takes care of fetching an address. I just populated the address manually, but obviously you would grab the object data from storage. Here we use reflection to instantiate the object and set the private members. Yes, I know, I could refactor it, but I just created it quickly.
1 using System;
2
3 namespace MyProject.Data
4 {
5 /// <summary>
6 /// Summary description for IMapper.
7 /// </summary>
8 public interface IMapper
9 {
10 object Get(object key, Type classType);
11 }
12 }
1 using System;
2 using System.Reflection;
3
4 namespace MyProject.Data
5 {
6 /// <summary>
7 /// Summary description for AddressMapper.
8 /// </summary>
9 public class AddressMapper : IMapper
10 {
11 #region IMapper Members
12
13 public object Get(object key, Type classType)
14 {
15 object address = Activator.CreateInstance(classType);
16
17 FieldInfo id = address.GetType().GetField("_id", BindingFlags.Instance | BindingFlags.NonPublic);
18 id.SetValue(address,1);
19
20 FieldInfo address1 = address.GetType().GetField("_address1", BindingFlags.Instance | BindingFlags.NonPublic);
21 address1.SetValue(address, "1234 Fake Street");
22
23 FieldInfo address2 = address.GetType().GetField("_address2", BindingFlags.Instance | BindingFlags.NonPublic);
24 address2.SetValue(address, string.Empty);
25
26 FieldInfo city = address.GetType().GetField("_city", BindingFlags.Instance | BindingFlags.NonPublic);
27 city.SetValue(address, "Longboat Key");
28
29 FieldInfo state = address.GetType().GetField("_state", BindingFlags.Instance | BindingFlags.NonPublic);
30 state.SetValue(address, "Florida");
31
32 FieldInfo zipCode = address.GetType().GetField("_zipCode", BindingFlags.Instance | BindingFlags.NonPublic);
33 zipCode.SetValue(address, "34228");
34
35 FieldInfo country = address.GetType().GetField("_country", BindingFlags.Instance | BindingFlags.NonPublic);
36 country.SetValue(address, "USA");
37
38 return address;
39 }
40
41 #endregion
42
43 }
44 }
And there you have it, a little more meat given to the example in the book.
I also have another version that uses a Virtual Proxy for Lazy Loading. This is also discussed in the book very briefly. I will put it up at some point during the week.