I often see/write classes that contain every part of the application where the application will talk to a SQL server. For example, you may have a class like this
public class Data
{
private Func<IDbConnection> Factory { get; }
public Data(Func<IDbConnection> factory)
{
Factory = factory;
}
public IList<Customer> FindAll()
{
using (var connection = Factory.Invoke())
{
const string sql = "SELECT Id, Name FROM Customer";
using (var command = new SqlCommand(sql, (SqlConnection) connection))
{
command.Connection.Open();
// Then execute command and return the results
}
}
}
public Customer FindOne(int CustomerID)
{
using (var connection = Factory.Invoke())
{
string sql = $"SELECT Id, Name FROM Customer WHERE ID = {CustomerID}";
using (var command = new SqlCommand(sql, (SqlConnection) connection))
{
command.Connection.Open();
// Then execute command and return the results
}
}
}
public IList<Job> FindAll()
{
using (var connection = Factory.Invoke())
{
const string sql = "SELECT Id, Name FROM Jobs";
using (var command = new SqlCommand(sql, (SqlConnection) connection))
{
command.Connection.Open();
// Then execute command and return the results
}
}
}
}
and you would expect to add a new method in this class whenever you need your application to talk to SQL in a different way (e.g. when you need to add a new query).
Clearly, this is a terrible idea. Before you know it, you will have 100 methods in the same file. Above all else, it violates the Open-Closed principle because it will have to change whenever we want to add a new SQL query to our application.
At face value, a more sensible idea would be to put the Customer
methods in their own class and the Job
methods in their own separate class. Let's call them CustomerSql
and JobSql
This is much nicer, but it violates the Open-Closed principle for the same reason that the original design did: If I want to add a new SQL query for Customer
s, then I will have to change CustomerSql
.
This leave us only with the idea that each call to SQL should be in its own class. For example, the FindAll()
method for Customer
s should be in a different class to the FindOne()
method for Customer
s. Is this considered best practice? And if so, how can I square it with dependency injection? Whenever I try to imagine implementing it, I find myself in dependency injection Hell. With one class per SQL call, I cannot imagine how ugly my Composition Root would be. Even when using a DI framework such as Microsoft.Extensions.DependencyInjection
(it's the one I'm stuck with), I can't imagine it turning out well if I'm writing a CRUD app that has Form1
depend on 30 different SQL queries. I suspect that an example showing that this is actually very simple would instantly address my concerns.