Database application block 5 gives us automatic data mapping with new extension which is called Accessors. I have discussed more about Database Application block 5 features in previous blog post http://ciintelligence.blogspot.com/2011/01/retrieving-data-as-objects-using.html . Let see how application block 5 provides auto data mapping.
1: public IEnumerableGetCompanies()
2: {
3: return _database.CreateSqlStringAccessor(GetCompaniesSQL());
4: }
Now lets give another layer of abstraction from this data mapping and query using Repository pattern.
According to Martin Fowler :
A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction.
An object can be added and removed from repository as we do in C# List collection with Add(), Remove() functions.
The structure I have defined for repository is :
1: namespace DG.Common
2: {
3: public interface IRepository
4: {
5: ListGetAllBySortingAndPaged(SortDefinitions sortDefinitions, int startIndex, int pageSize);
6: ListGetAllBySorting(SortDefinitions sortDefinitions);
7: ListGetAll();
8: ListGetAllById(long id);
9: long Insert(T item);
10: void Update(T item);
11: void Delete(T item);
12: }
13: }
In IRepository
For generating automatic C# code for DAO, DTO and Repository Object definitions I have used T4 template. Generating code using T4 template is very easy with few syntax and Visual Studio has nice support for T4 template.
T4 templates are composed of three parts: Directives, Text blocks and Control blocks. This is almost same as we define ASP.NET Control block and Text block in markup page.
In MSDN http://msdn.microsoft.com/en-us/library/bb126478.aspx you will find syntax for writing T4 templates.
For generating DTO and DAO I will be needed to access Database Schema information for getting database Tables, Columns of Tables. Primary Keys, Auto Increment columns. I do not like reinventing wheel where from SubSonic T4 Template I get more functionality like plural and singular naming conversions. I have used SubSonic http://subsonicproject.com . I have used only those classes which are needed for me to get schema information and proper naming. So I mainly combined functionality of Settings.ttinclude and SQLServer.ttinclude as I am using SQL Server with Application block 5. As SQLServer.ttinclude is mainly directives so I have included there my own directives so that I can reuse those in generating DAO.
Generating DTO :
DTO generation code is very simple as I am getting Table schema information using Subsonic T4 template directives.1: <#@ include file="SQLServer.ttinclude" #>
2: using System;
3:
4: namespace <#=Namespace #>
5: {
6: <#
7:
8: var tables = LoadTables();
9:
10: foreach(Table tbl in tables)
11: {
12:
13: #>
14: public class <#=tbl.ClassName #>
15: {
16: <# foreach(Column col in tbl.Columns)
17: {
18: if(col.SysType !="string" && col.SysType !="byte[]" && col.IsNullable==true)
19: {
20: #>
21: public <#=col.SysType#>? <#=col.CleanName#> {get;set;}
22:
23: <#
24: }
25: else
26: {
27: #>
28: public <#=col.SysType#> <#=col.CleanName#> {get;set;}
29:
30: <#}
31: }#>
32: }
33: <#}#>
34: }
There have another symbol <#… #> which define Control blocks. The control flow is defined in this block. In DTO if any column is defined as Nullable then in DTO same named property will be nullable.
Generating DAO :
Now DAO part. When I first designed my DAO that time I wrote simple scripts for selection columns, inserting columns also updating without using any variable for column Name. So if any of column name changed or if I do not need to consider a single column I had to find out that column from script string. After doing my operation I could understand after running as queries were in string. So I had to generate script again from database for safety. To overcome this problem I have used name of column with constants and also made a collections of columns names for query operations.For country table the constants are :
1: private const string IdentityColumn = "Id";
2: private const string ISOColumn = "ISO";
3: private const string NameColumn = "Name";
4: private const string PrintableNameColumn = "PrintableName";
5: private const string ISO3Column = "ISO3";
6: private const string NumCodeColumn = "NumCode";
7: private const string HasStateColumn = "HasState";
8: private static readonly string[] ColumnCollections = new[] { IdentityColumn, ISOColumn, NameColumn, PrintableNameColumn, ISO3Column, NumCodeColumn, HasStateColumn };
For making where clause for update or delete operations we have to set where clause like
1: WHERE
2: Id = @Id
3: AND
4: ISO = @ISO
1: private static readonly string[] KeyColumns = new string[] { "Id", "ISO" };
2: private static readonly string WhereClause = KeyColumns.JoinFormat(" AND ", "{0} = @{0}");
Lets see the Update query for Country table :
1: private string UpdateCountrySQL()
2: {
3: var sql = string.Format(@" UPDATE
4: Countries
5: SET
6: {1}
7: WHERE
8: {0}
9: ", WhereClause, ColumnCollections.Except(KeyColumns).JoinFormat(" , ", "{0} = @{0}"));
10: return sql;
11: }
Also another thing I have considered is giving parameter for nullable columns.
1: parameter = command.CreateParameter();
2: parameter.ParameterName = string.Format("@{0}", @ISO3Column);
3: parameter.Value = (object)country.ISO3 ?? DBNull.Value;
4: command.Parameters.Add(parameter);
T4 Template generation for DAO with such logic was not difficult. Also did not took very long code. JoinFormat() extension method saved many code also made it easy to write.
Generating Repository:
T4 template code for repository is also very simple. Just a little have considered when table do not have Identity Column then we have to use primary keys columns to get single item from repository. T4 template code for this logic :1: if(tbl.Columns.Any(c=>c.AutoIncrement==true))
2: {
3: #>
4: public <#=tbl.ClassName#> GetById(object id)
5: {
6: return _<#=tbl.ClassName.ToLower()#>Dao.Get<#=tbl.ClassName#>ById(id);
7: }
8: <#}
9: else if(IsKeyColumnsRequire(tbl.Columns.SingleOrDefault(c=>c.AutoIncrement==true),tbl.Columns.Where(c=>c.IsPK==true)))
10: {
11: #>
12: public <#=tbl.ClassName#> GetByKey(<#=tbl.ClassName#> <#=tbl.ClassName.ToLower()#>)
13: {
14: return _<#=tbl.ClassName.ToLower()#>Dao.Get<#=tbl.ClassName#>ByKey(<#=tbl.ClassName.ToLower()#>);
15: }
16: <# } #>