After a lots of read about Repository and Data Mapper I decided to implement those patterns in a test project. Since I\'m new to these I\'d like to get your views about how
A few points:
It strikes me that overall, you have a good design. That's evidenced, in part, by the fact that you can make changes in it with little impact on any classes outside of the ones that are changed (low coupling). That said, it's very close to what Entity Framework does, so while it's a good personal project, I'd consider using EF first before implementing it in a production project.
Your DataMapper class could be made generic (say, GenericDataMapper<T>
) using reflection. Iterate over the properties of type T using reflection, and get them from the data row dynamically.
Assuming you do make a Generic DataMapper, you could consider making a CreateRepository<T>()
method on DataLayer, so that users don't need to worry about the details of which type of Mapper to pick.
A minor critique- you assume that all entities will have a single integer ID named "Id", and that a stored procedures will be set up to retrieve them by such. You may be able to improve your design here by allowing for primary keys of differing types, again maybe by using generics.
You probably don't want to re-use Connection and Command objects the way you do. That isn't thread safe, and even if it was, you'd end up with some surprising and hard-to-debug race conditions around DB Transactions. You either should create new Connection and Command objects for each function call (making sure to dispose of them after you are done), or implement some synchronization around the methods that access the database.
For instance, I'd suggest this alternate version of ExecuteReader:
public override IDataReader ExecuteReader(Command command) {
var connection = new SqlConnection(connString);
command.Connection = connection;
return command.ExecuteReader();
}
Your old one re-used the command object, which could lead to race conditions between multithreaded callers. You also want to create a new connection, because the old connection might be engaged in a transaction started by a different caller. If you want to re-use transactions, you should create a connection, begin a transaction, and re-use that transaction until you have executed all of the commands that you want to associate with the transaction. As an example, you could create overloads of your ExecuteXXX methods like this:
public override IDataReader ExecuteReader(Command command, ref SqlTransaction transaction) {
SqlConnection connection = null;
if (transaction == null) {
connection = new SqlConnection(connString);
transaction = connection.BeginTransaction();
} else {
connection = transaction.Connection;
}
command.Connection = connection;
return command.ExecuteReader();
}
// When you call this, you can pass along a transaction by reference. If it is null, a new one will be created for you, and returned via the ref parameter for re-use in your next call:
SqlTransaction transaction = null;
// This line sets up the transaction and executes the first command
var myFirstReader = mySqlDataLayer.ExecuteReader(someCommandObject, ref transaction);
// This next line gets executed on the same transaction as the previous one.
var myOtherReader = mySqlDataLayer.ExecuteReader(someOtherCommandObject, ref transaction);
// Be sure to commit the transaction afterward!
transaction.Commit();
// Be a good kid and clean up after yourself
transaction.Connection.Dispose();
transaction.Dispose();