I’m an extremely lazy coder. If i can find a way to write a base class to do the bulk of my chores within code, by crikey I'll do it and damn the scorn from my peers.

In this case, I wanted to write fairly generic CRUD (Create, Read, Update & Delete) methods using LINQ to SQL. The rationale as to why I wanted to do this was simply due to the fact that LINQ to SQL already takes care of writing the various Entity Classes properties – so - why do I still need to tease out each field name to do an Update or Select?

The other reason I came up with this as I had some issues with UPDATE in LINQ to SQL (mainly due to having relational tables), basically i was breaking the “unit of work” pattern and blaming LINQ to SQL for not doing what I thought its job should be (I'm sure I'll bend the ear of the LINQ team on what I think the right approach should be hehe).

Here is my cheat source on how to get out of actually doing work.

The BaseDAO.

This class is where you’ll inherit all your DAO classes from here on out (should you buy into my code wisdom). I’ve kept the methods as generic as possible and the tax you pay for doing this is that the execution time will grow by a few ms (my tests found it not noticeable).

When firing your Update, Create, Delete & Read methods, you simply pass in your proposed entity to persist as well as the DataContext in which you wish to use. I’m not thrilled at passing DataContexts around in arguments but in this case I couldn’t think of an alternative approach.

  1: using System;
  2: using System.Data.Linq;
  3: using System.Data.Linq.Mapping;
  4: using System.Linq;
  5: using System.Reflection;
  6: 
  7: namespace JARVIS.Lib.DAL
  8: {
  9:     /// <summary>
 10:     /// Base Data Access Object
 11:     /// </summary>
 12:     /// <remarks>TODO:</remarks>
 13:     public class BaseDAO
 14:     {
 15:         /// <summary>
 16:         /// Update an Entity Row in the LINQ Context.
 17:         /// </summary>
 18:         public T Update<T>(T entity, DataContext db) where T : class
 19:         {
 20:             using (db)
 21:             {
 22:                 Table<T> table = db.GetTable<T>();
 23:                 T original = table.FirstOrDefault(e => e == entity);
 24:                 if (original != null)
 25:                 {
 26:                     var model = new AttributeMappingSource().GetModel(db.GetType());
 27:                     MetaTable tbl = model.GetTable(typeof(T));
 28:                     foreach (var dm in tbl.RowType.DataMembers)
 29:                     {
 30:                         PropertyInfo p1 = original.GetType().GetProperty(dm.MappedName);
 31:                         PropertyInfo p2 = entity.GetType().GetProperty(dm.MappedName);
 32:                         try
 33:                         {
 34:                             if (p1.PropertyType.ToString() != "System.DateTime")
 35:                             {
 36:                                 p1.SetValue(original, p2.GetValue(entity, null), null);
 37:                             }
 38:                             else
 39:                             {
 40:                                 // *** WARNING: Cheesy Hack : WARNING ***
 41:                                 //                                
 42:                                 // Just in case you have a row that doesn't have DateTime Set..
 43:                                 // This will hack your way past an exception. Highly recommend
 44:                                 // you ensure your data going into the DB is validated against
 45:                                 // DB rules.
 46:                                 //
 47:                                 // No, i'm not proud of this but meh.. great code happens when you ship v3 :D
 48:                                 //
 49:                                 string dtStr = p2.GetValue(entity, null).ToString();
 50:                                 DateTime dt = Convert.ToDateTime(dtStr);
 51:                                 System.Diagnostics.Trace.WriteLine(dt.Year);
 52:                                 if (dt.Year > 1)
 53:                                 {
 54:                                     p1.SetValue(original, p2.GetValue(entity, null), null);
 55:                                 }
 56:                             }
 57:                         }
 58:                         catch
 59:                         {
 60:                             System.Diagnostics.Trace.WriteLine(dm.MappedName);
 61:                         }
 62:                     }
 63:                 }
 64:                 db.SubmitChanges();
 65:                 db.Dispose();
 66:                 return original;
 67:             }
 68:         }
 69: 
 70:         /// <summary>
 71:         /// Create an Entity Row into the LINQ Context
 72:         /// </summary>
 73:         public T Create<T>(T entity, DataContext db) where T : class
 74:         {
 75:             using (db)
 76:             {
 77:                 Table<T> table = db.GetTable<T>();
 78:                 table.InsertOnSubmit(entity);
 79:                 db.SubmitChanges();
 80:             }
 81:             db.Dispose();
 82:             return entity;
 83:         }
 84: 
 85:         /// <summary>
 86:         /// Read an Entity Row from the LINQ context back (based of Primary Key setting)
 87:         /// </summary>
 88:         public T Read<T>(T entity, DataContext db) where T : class
 89:         {
 90:             using (db)
 91:             {
 92:                 Table<T> table = db.GetTable<T>();
 93:                 T original = table.FirstOrDefault(e => e == entity);
 94:                 db.Dispose();
 95:                 return original;
 96:             }
 97:         }
 98: 
 99:         /// <summary>
100:         /// Hard Delete a Row from the LINQ Context. Note, this will DELETE from the database, its recommended you Retire records not Delete them.
101:         /// </summary>
102:         public void HardDelete<T>(T entity, DataContext db) where T : class
103:         {
104:             using (db)
105:             {
106:                 Table<T> table = db.GetTable<T>();
107:                 T original = table.FirstOrDefault(e => e == entity);
108:                 if (original != null)
109:                 {
110:                     table.DeleteOnSubmit(original);
111:                     db.SubmitChanges();
112:                 }
113:             }
114:             db.Dispose();
115:         }
116:     }
117: }
118: 

The UserDAO.

  1: using System;
  2: using System.Data.Linq;
  3: using System.Linq;
  4: using JARVIS.Models.Security.TO;
  5: using System.Reflection;
  6: using System.Data.Linq.Mapping;
  7: using JARVIS.Lib.DAL;
  8: namespace JARVIS.Models.Security.DAO
  9: {
 10:     /// <summary>
 11:     /// JARVIS.User Data Access Object
 12:     /// </summary>
 13:     /// <remarks>
 14:     /// TODO: 
 15:     /// - Add Triggers for Realtional Data (ie Users Roles etc) for Delete  Method
 16:     /// </remarks>
 17:     public class UserDAO : BaseDAO
 18:     {
 19:         /// <summary>
 20:         /// Update a UserTO from the Persistance Layer based off the UserId
 21:         /// </summary>
 22:         public T Update<T>(T entityTO) where T : UserTO
 23:         {
 24: 
 25:             var db = new JARVISDBDataContext();
 26:             JARVIS_User entity = entityTO.ConvertToLINQ(entityTO);
 27:             entity.ModifiedDate = DateTime.Now;
 28:             JARVIS_User updated = base.Update(entity, db) as JARVIS_User;
 29:             T result = entityTO.ConvertToTO(updated) as T;
 30:             return result;
 31:         }
 32: 
 33:         /// <summary>
 34:         /// Retire a UserTO from the Persistance Layer based off the UserId
 35:         /// </summary>
 36:         public T Delete<T>(T entityTO) where T : UserTO
 37:         {
 38:             var db = new JARVISDBDataContext();
 39:             JARVIS_User entity = entityTO.ConvertToLINQ(entityTO);
 40: 
 41:             // TimeStamp the UserTO Modification Dates and Mark for Retirement.
 42:             entity.ModifiedDate = DateTime.Now;
 43:             entity.IsRetired = true;
 44: 
 45:             // Return a Modified UserTO
 46:             JARVIS_User updated = base.Update(entity, db) as JARVIS_User;
 47:             T result = entityTO.ConvertToTO(updated) as T;
 48:             return result;
 49:         }
 50: 
 51:         /// <summary>
 52:         /// Create a UserTO into the Persistance Layer
 53:         /// </summary>
 54:         /// <returns>An updated UserTO</returns>
 55:         public T Create<T>(T entityTO) where T : UserTO
 56:         {
 57: 
 58:             var db = new JARVISDBDataContext();
 59:             JARVIS_User entity = entityTO.ConvertToLINQ(entityTO);
 60: 
 61:             // Modify the Timestamps.
 62:             entity.CreatedDate = DateTime.Now;
 63:             entity.ModifiedDate = DateTime.Now;
 64:             base.Create(entity, db);
 65:             T result = entityTO as T;
 66:             return result;
 67:         }
 68: 
 69:         /// <summary>
 70:         /// Return a UserTO from the Persistance Layer based off the UserId
 71:         /// </summary>
 72:         public T Read<T>(T entityTO) where T : UserTO
 73:         {
 74:             var db = new JARVISDBDataContext();
 75:             JARVIS_User entity = entityTO.ConvertToLINQ(entityTO);
 76:             JARVIS_User updated = base.Read(entity, db);
 77:             T result = entityTO.ConvertToTO(updated) as T;
 78:             return result;
 79:         }
 80:     }
 81: }
 82: 

As you can see, fairly simplified approach from here on out, essentially what I’ll do here is simply map the T to the appropriate Type and ensure I go between my Transfer Object type to the LINQ type and back again, always ensuring the code beyond UserDAO really has no clue as to what flavor of LINQ I'm using.

The UserTO.

I’m a bit of a fan of Transfer Objects, but again, I’m lazy and well the correct thing to do here would be to NOT inherit the LINQ table JARVIS_User, but since the fields are already there and I hate typing, its “meh” to any whom object.

  1: using System.Data.Linq.Mapping;
  2: using System.Reflection;
  3: namespace JARVIS.Models.Security.TO
  4: {
  5:     public class UserTO : JARVIS_User
  6:     {
  7: 
  8: 
  9:         public UserTO ConvertToTO(JARVIS_User userTO)
 10:         {
 11:             var context = new JARVISDBDataContext();
 12:             var model = new AttributeMappingSource().GetModel(context.GetType());
 13:             UserTO instance = new UserTO();
 14:             MetaTable tbl = model.GetTable(typeof(JARVIS_User));
 15:             foreach (var dm in tbl.RowType.DataMembers)
 16:             {
 17:                 try
 18:                 {
 19:                     PropertyInfo p1 = instance.GetType().GetProperty(dm.MappedName);
 20:                     PropertyInfo p2 = userTO.GetType().GetProperty(dm.MappedName);
 21:                     p1.SetValue(instance, p2.GetValue(userTO, null), null);
 22:                 }
 23:                 catch
 24:                 {
 25:                 }
 26:             }
 27:             context.Dispose();
 28:             return instance;
 29:         }
 30: 
 31: 
 32:         public JARVIS_User ConvertToLINQ(UserTO userTO)
 33:         {
 34:             var context = new JARVISDBDataContext();
 35:             var model = new AttributeMappingSource().GetModel(context.GetType());
 36:             JARVIS_User instance = new JARVIS_User();
 37:             MetaTable tbl = model.GetTable(typeof(JARVIS_User));
 38:             foreach (var dm in tbl.RowType.DataMembers)
 39:             {
 40:                 try
 41:                 {
 42:                     PropertyInfo p1 = instance.GetType().GetProperty(dm.MappedName);
 43:                     PropertyInfo p2 = userTO.GetType().GetProperty(dm.MappedName);
 44:                     p1.SetValue(instance, p2.GetValue(userTO, null), null);
 45:                     //System.Diagnostics.Trace.WriteLine(p1.GetValue(instance,null));
 46:                 }
 47:                 catch
 48:                 {
 49:                 }
 50:             }
 51:             context.Dispose();
 52:             return instance;
 53:         }
 54:     }
 55: 
 56: }
 57: 

The Test.

In this Test, It simply shows the various routines being fired within context to all of the above.

  1: using System;
  2: using JARVIS.Models.Security.DAO;
  3: using JARVIS.Models.Security.TO;
  4: using Microsoft.VisualStudio.TestTools.UnitTesting;
  5: 
  6: namespace JARVIS.Tests.Controllers
  7: {
  8:     /// <summary>
  9:     /// Summary description for SecurityController
 10:     /// </summary>
 11:     [TestClass]
 12:     public class SecurityController
 13:     {
 14:         [TestMethod]
 15:         public void UserAccountTest()
 16:         {
 17: 
 18:             // DAO
 19:             UserDAO dao = new UserDAO();
 20: 
 21:             // User Entity.            
 22:             UserTO userTO = new UserTO();
 23:             //userTO.UserId = new Guid("e47dcd57-4c91-4118-b9c5-027de0270620");
 24:             userTO.UserId = Guid.NewGuid();
 25:             userTO.Username = "scbarnes";
 26:             userTO.Password = "itsasecret";
 27:             userTO.DisplayName = "None";
 28:             userTO.Email = "";
 29:             userTO.IsActive = true;
 30:             userTO.CreatedBy = userTO.UserId;
 31:             userTO.ModifiedBy = userTO.UserId;
 32: 
 33:             // CREATE
 34:             UserTO o1 = dao.Create(userTO);
 35: 
 36:             // UPDATE            
 37:             userTO.Username = "UsernameUpdated";
 38:             UserTO o2 = dao.Update(userTO);
 39: 
 40:             // DELETE
 41:             dao.Delete(userTO);
 42: 
 43:             // READ
 44:             UserTO o3 = new UserTO();
 45:             o3.UserId = o2.UserId;
 46:             UserTO o4 = dao.Read(o3);
 47: 
 48:         }
 49: 
 50:     }
 51: }
 52: 

The Versions Used..

I’m currently using the ASP.NET MVC Beta which was released on 10/15/2008.  (.NET 3.5).

The End.

Look, I’m not saying its “champagne C#” and I’m sure it will ruffle a few feathers of some dire hard pattern fans, but at the end of the day, I’ve put in enough layers of abstraction here to allow for trouble to be stomped out. In the event the above becomes to taxing on the server loads, it’s easily swapped in / out from the DAO level down, and that in itself is the true power of object orientated programming.

Will it win me the Martin Fowler award of excellence, probably not, but hey, I’m shipping :)

Scott Out.