INTRODUCTION
This tutorial exposes a use case of the “Data Transfer Object” pattern design. The latter is part of a .Net solution allowing access to data via the Web Services.
PRESENTATION OF THE NEED
For the different examples, we establish the following need:
Access to different interaction functions with the “Person” table: Add, Edit, Delete and Recover.
Columns | Type | Description |
ID | int | Primary Key, Not Null |
Name | varchar(50) | Null |
Age | int | Not Null |
Table name: “Person”
ANALYSIS OF THE NEED
Of course, a need such as it turns out to be quite simple: we could quite directly code the four methods.
Nevertheless, like any good developer we are, we still want to make the thing as scalable and generic as possible. It is within this framework that we can present the following architecture:
Figure: Web Services Architecture based on the DTO design pattern
At this level, you will say to me: “it is very beautiful the diagram but it serves what”. I propose you to continue the rest of the document, which will explain the role of each module by module.
DEVELOPMENT
Tip: Create a solution that contains a separate project for each component.
(project references are expressed by the double arrows of the diagram above)
Using the “Data Transfer Object” Pattern
A use of this pattern (coupled with the design pattern: “Assemble”) breaks down this need in terms of business objects under 3 distinct parts. All this in order to make it easier to use business objects corresponding to functional needs.
Domain Objects
This is a simple stream containing generic data. It exists natively in the .Net framework in the form of classes such as: DataSet, DataReader …
For our example, the class used will be “DataSet”.
Data Transfer Objects
Each class adheres to functional specifications both in terms of its fields and properties, but does not implement any business logic at the method level. Indeed, it is only a container for the transport of business information.
We will add to each DTO class the XML serialization that will eventually allow communication with any platform and any language.
Code:
Class file “Person.cs“:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | [XmlRoot(ElementName="Person")] public class Person { private int _id = -1; private string _name = null; private int _age = -1; [XmlAttribute(AttributeName="Identification")] public int Id { get { return _id; } set { _id=value;} } [XmlAttribute(AttributeName="Name")] public string Name { get { return _name; } set { _name=value;} } [XmlAttribute(AttributeName="Age")] public int Age { get { return _age; } set { _age=value;} } public Person() { } public Person(int id, string name, int age) { _id = id; _name = name; _age = age; } } |
PersonList.cs class file
1 2 3 4 5 6 7 8 9 | public class PersonsList : List<Persons> { public PersonsList() : base() { } } |
The Assemble class
This class is the link between DTO and OD. It allows, via static methods, the conversion of a DTO to a DO and vice versa.
In this context, we can easily provide an XML feed containing DTO objects. Conversely, we can easily interpret an XML stream in DTO.
Code:
Static Class File “Assembler“:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public static class Assembler { public static DataSet CreatePersonDataSet(PersonList listPerson) { DataSet rDataset = new DataSet("Person"); DataTable table = rDataset.Tables.Add(); table.Columns.Add("id", typeof(int)); table.Columns.Add("name", typeof(string)); table.Columns.Add("age", typeof(int)); foreach(Person curPerson in listPerson) { DataRow curRow = table.NewRow(); curRow[0] = (int) curPerson.Id; curRow[1] = (string) curPeron.Name; curRow[2] = (int) curPerson.Age; table.Rows.Add(curRow); } return rDataSet; } public static PersonList CreatePersonDTO(DataSet ds) { PersonList rList = new PersonList(); foreach(DataRow curRow in ds.Tables[0].Rows) { rList.Add(new Person( (int) curRow["id"], (string) curRow["name"], (int) curRow["age"]); } return rList; } } |
“Business Data Access Objects” & “Data Access Objects”
“Business Data Access Objects” & “Data Access Objects”
These two components, normally distinct, are exposed here as a single component. This one takes care of all the functionalities concerning the access to the data: recovery of persons, addition / modification / suppression of a person.
File: “DataAccess.cs”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | public class DataAccess { private string _connectionString = null; public string ConnectionString { get { return _connectionString; } } public DataAccess(string connectionString) { _connectionString = connectionString; } public void AddPerson(int id, string name, int age) { using (SqlConnection connection = new SqlConnection(_connectionString)) { connection.Open(); //-- INSERT -- using (SqlCommand command = new SqlCommand("INSERT INTO Person VALUES(@id, @name, @age)", connection)) { command.Parameters.Add(new SqlParameter("@id", id)); command.Parameters.Add(new SqlParameter("@name", name)); command.Parameters.Add(new SqlParameter("@age", age)); command.ExecuteNonQuery(); } connection.Close(); } } public void UpdatePerson(int id, string name, int age) { using (SqlConnection connection = new SqlConnection(_connectionString)) { connection.Open(); //-- UPDATE -- using (SqlCommand command = new SqlCommand("UPDATE Person SET name=@name, age=@age WHERE id=@id", connection)) { command.Parameters.Add(new SqlParameter("@name", name)); command.Parameters.Add(new SqlParameter("@age", age)); command.Parameters.Add(new SqlParameter("@id", id)); command.ExecuteNonQuery(); } connection.Close(); } } public void DeletePerson(int id) { using (SqlConnection connection = new SqlConnection(_connectionString)) { connection.Open(); //-- DELETE -- using (SqlCommand command = new SqlCommand("DELETE Person WHERE id=@id", connection)) { command.Parameters.Add(new SqlParameter("@name", name)); command.Parameters.Add(new SqlParameter("@age", age)); command.Parameters.Add(new SqlParameter("@id", id)); command.ExecuteNonQuery(); } connection.Close(); } } public DataSet GetPersons() { DataSet ds = new DataSet("Person"); using (SqlConnection connection = new SqlConnection(_connectionString)) { connection.Open(); //-- SELECT -- using(SqlDataAdapter da = new SqlDataAdapter( "SELECT id, name, age FROM Person", connection)) { da.Fill(ds); } connection.Close(); } return ds; } } |
As you will understand, the ideal would be to separate the business part from data access. Access to the data would only take care of connecting and executing queries on the database. Thus, we could easily change from one type of database to another (eg Sql Server, Oracle, Base Access, MySQL …) without affecting the data access business logic.
To do this, I can only advise you to use a very well known library of .Net developers: “Enterprise Library 3.1”. This integrates a component called “Data Access Application Block” which corresponds perfectly to this need. It allows the selection of the type of the database via the configuration file.
Url : http://msdn.microsoft.com/en-us/library/aa480453.aspx
Web Services
All that remains is to give users access to the data. A Web Service layer is ideal for the situation. This will interface between user and execution of requests.
Code:
File:Persons.asmx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public class WebService : System.Web.Services.WebService { public WebService() { } [WebMethod] public void AddPerson(int id, string name, int age) { DataAccess da = new DataAccess("connection"); da.AddPerson(id, name, age); } [WebMethod] public void UpdatePerson(int id, string name, int age) { DataAccess da = new DataAccess("connection"); da.UpdatePerson(id, name, age); } [WebMethod] public void DeletePerson(int id) { DataAccess da = new DataAccess("connection"); da.DeletePerson(id, name, age); } [WebMethod] public string GetPersons() { DataAccess da = new DataAccess("connection"); DataSet ds = da.GetPersons(); //Create DTO PersonList personList = Assembler.CreatePersonDTO(ds); //Serialize to XML StringWriter sw = new StringWriter(); XmlSerializer s = new XmlSerializer(typeof(personList)); s.Serialize(sw, personList); return sw.ToString(); //-- } } |
Conclusion
So here it is, the set-up is over.
But, what are the advantages of such an architecture?
Such a project then becomes a facade of multi-platform and multi-language services. Any type of project (Windows application, website, Windows service …) can be grafted, interact and interpret very easily (XML standardization) the data, whatever the language used.
Adding a column to the table will simply add one member to the DTO class and two rows to the Assemble class.
The change of database type will only interfere with the data access part (and if the “Enterprise Library” library is used, it will show up in the only modification of the configuration file).