Designing a Persistence Framework With Patterns - Mapping Objects - Database Mapper - O/R Mapper

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.

posted on Sunday, November 21, 2004 4:21 PM

Main

David Hayden Google +

David Hayden Twitter

Health & Fitness

JavaScript Patterns Book Review

HTML 5 and CSS3 - Develop with Tomorrow's Standards Today

Professional ASP.NET Design Patterns Book Review

Beginning Mac Programming Book Review

C# in Depth Book Review

ASP.NET MVC

Orchard CMS

Categories