In the post below on NHibernate 101 I said not to plumb data access code in the Operation Contract implementation. Now I'd like to refactor and get that out of there. Let's think about it a bit first. Do I really want to create a data layer class called "InstrumentService" with the CRUD in there? I could. But since this is a concert music application, I know I'm going to have several lookup tables in the system. In addition to musical instruments there will be musical genres, catalogs, recording labels, ensemble types, and so on and on.
So we want to make sure all of these data access classes implement an interface with the standard read, insert, update, and delete behavior. Let's do it. But let's be smart about it and take advantage of generics:
1: public interface IDataService<T>
2: {
3: IList<T> GetAll(string queryString);
4: T GetItem(int id);
5: T Save(T item);
6: bool Delete(int id, string tableName);
7: bool Delete(T item);
8: }
Having done that we can code a single DataService that implements the interface:
1: /// <summary>
2: /// Data service to fetch and save common lookup table items.
3: /// </summary>
4: public class DataService<T> : IDataService<T>
5: {
6: public DataService() { }
7:
8: public IList<T> GetAll(string queryString)
9: {
10: IList<T> list;
11: using (ISession session = NHibernateHelper.OpenSession())
12: {
13: IQuery query = session.CreateQuery(queryString);
14: list = query.List<T>();
15: }
16: return list;
17: }
18:
19:
20: public T GetItem(int id)
21: {
22: T item;
23: using (ISession session = NHibernateHelper.OpenSession())
24: {
25: item = session.Get<T>(id);
26: }
27: return item;
28: }
29:
30:
31: public T Save(T item)
32: {
33: using (ISession session = NHibernateHelper.OpenSession())
34: {
35: using (ITransaction transaction = session.BeginTransaction())
36: {
37: session.SaveOrUpdate(item);
38: transaction.Commit();
39: }
40: }
41: return item;
42: }
43:
44:
45: public bool Delete(int id, string tableName)
46: {
47: bool deleted = false;
48: using (ISession session = NHibernateHelper.OpenSession())
49: {
50: using (ITransaction transaction = session.BeginTransaction())
51: {
52: string hqlString = string.Format("from {0} where ID = :ID", tableName);
53: session.Delete(hqlString, id, NHibernateUtil.Int32);
54: transaction.Commit();
55: deleted = true;
56: }
57: }
58: return deleted;
59: }
60:
61: public bool Delete(T item)
62: {
63: bool deleted = false;
64: using (ISession session = NHibernateHelper.OpenSession())
65: {
66: using (ITransaction transaction = session.BeginTransaction())
67: {
68: session.Delete(item);
69: transaction.Commit();
70: deleted = true;
71: }
72: }
73: return deleted;
74: }
75: }
Note that this implementation assumes that all of my lookup tables will have a surrogate primary key. There's just one last detail and that's creating a factory for these DataService instances. It's not really necessary. But I'm obsessed with web service methods that do as little as possible so I'm going to make their job as easy as possible. So here's the simple factory:
1: public class DataServiceFactory<T> where T : new()
2: {
3: public static DataService<T> Create()
4: {
5: return new DataService<T>();
6: }
7: }
That takes care of the NHiberate plumbing for all lookup tables in my system. Now if I want to fetch all musical instruments, or genres, or record labels from the system it's pretty easy:
1: public IList<Instrument> GetInstruments()
2: {
3: IDataService<Instrument> svc = DataServiceFactory<Instrument>.Create();
4: return svc.GetAll("FROM Instrument");
5: }
6:
7: public IList<Genre> GetGenres()
8: {
9: IDataService<Genre> svc = DataServiceFactory<Genre>.Create();
10: return svc.GetAll("FROM Genre");
11: }
12:
13: public IList<Catalog> GetCatalogs()
14: {
15: IDataService<Catalog> svc = DataServiceFactory<Catalog>.Create();
16: return svc.GetAll("FROM Catalog");
17: }
The one thing that still bugs me is the hard-coded "FROM [TableName]" that gets passed in. That could get refactored and put into the DataService base class. However, doing so would make an assumption that I'm not ready to make, namely, that my class name equals the table name. There is probably a way to decorate the POCO classes with attributes but then we're creeping back into domain entites that are not persistent ignorant. So it's a small price to pay.