Background
During working with legacy code, I have found that many people used DataTable/DataSet instead of strongly typed objects. They are using some code likestring id = row["id"].ToString();instead of
string id = request.Id;
It is becoming a maintenance hell because of several reasons:
- I do not know the data type from database, so I need to debug into database procedure
- I do not know whether the data is nullable or not, again I need to debug into database
- When I need to change the data type, I need to search for every implementation, change it and make sure it does not break
- Keep the same programming style, using the DataTable, with the risk that you add another more maintenance hell object
- Refactor it, with the risk of breaking is higher
Traditional Way: Converter
Yes, it can be done using a defined strongly-typed entity, and using a converter to convert it. Example of code is:public class Converter{ public Request Convert(DataRow row){ Request result = new Request(); result.Id = row["id"].ToString(); //rest of conversion here } }
Then it is very annoying during the usage:
public void UseConverter(DataRow row){ Converter converter = new Converter(); Request = converter.Convert(row); }
Of course, you can use static method, but once again you just create another trap for maintenance problem later on. Short tips, static method are hard to test, and cannot be mock/injected.
Traditional Way: Constructor Converter
Another way to handle this case is to add a constructor to the entity, making it accept DataRow and convert it as an entity. Example:public class Request{ public Request(DataRow row){ this.Id = row["id"].ToString(); } public string Id{get;set;} }
But using this design, you are risking of:
1. Having your entity class has many constructors, whether they are used or not. Making it dirty
2. Create a dependency of your entity class with DataRow. It will be confusing later on.
Using Interface to Wrap the Implementation
In order to solve constructor converter problem, we can use the interface to represent the entity, making it temporal dependent with the DataRow.public interface IRequest{ string Id{ get; } //up to the developer to add set accessor } public DataRowRequest:IRequest{ public DataRowRequest(DataRow row){ this._id = row["id"].ToString(); } private string _id; public string Id{ get{ return id; } } }
And the usage will be:
public void RequestFromDataRow(DataRow row){ DataRowRequest request = new DataRowRequest(row); RequestConsumer(request); } // it is better if the consumer is in different class public void RequestConsumer(IRequest request){ string id = request.Id; }
This way, you can get some benefits:
1. The consumer does not dependent at all with DataRow
2. DataRowRequest cannot be made without DataRow
3. The purpose of DataRowRequest is clear, having only 1 constructor accepting DataRow
This technique can also be used to handle static objects. I have asked a question at stack exchange here.
Conclusion
Using Interface correctly can decouple your consumer with other object, which in practical are not used at all by the consumer (in this case, the DataRow). You can implement the interface in many ways, and the consumer does not need to know about it.More of it, using the wrapper with interface leaves clean design for the application. The purpose of the interface are clear, and the implementation are also clear. In the future, when the DataTable/DataRow is not used anymore, and changed to ORM, the implementation can be easily disposed, and the consumer will not need any changes at all.
No comments:
Post a Comment