I have a .net core 5 application that has an appsettings.json
file, in which I have listed connection strings to the databases.
I have defined a Database
class that has the responsibility of handing out IDbConnection
objects.
public class Database
{
public Database(IConfiguration config) // config is injected
{
Db1ConnectionString = config.GetConnectionString("db1");
Db2ConnectionString = config.GetConnectionString("db2");
}
public IDbConnection GetConnection()
{
// do some logic to decide, either Db1 or Db2 to use
return new SQLiteConnection(SelectedDbConnectionString);
}
}
Then I am injecting this Database
in Startup.cs
services.AddScoped<IDatabase, Database>(); // IDatabase is interface of Database
Let's say I have a class of Person
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
I am using Dapper to fetch IEnumerable<Person>
. I am writing logic to fetch collections of objects in the same class itself. Meaning: Person class knows how to fetch a collection of its self. Animal
class has its own logic of fetching a collection of its self.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public static IEnumerable<Person> GetAll()
{
string sql = "select id, name from person where ...... ";
using var con = Database.GetConnection(); // this does not work
// because the GetConnection() is not static.
return con.Query<Person>(sql);
}
}
The code above will not work because I need an instance of Database
inside a static method. I though of injecting Database
in Person
constructor, but that will make things difficult for Dapper. So I guess I will need to write an empty constructor for Person
. But then the other constructor (that acceptes IDatabase
) can't keep hold of the reference of that database object until the static method requires it.
I thought of converting Database
into a static class, or changing GetConnection()
into static method, but then I will not be able to inject it in Startup.cs
.
I don't want to hardcore the connection strings, as they may change at run time.
I feel like there is a straight forward solution to this problem, and it must be obvious too.
If you require a dependency inside a static method, you'll end up with Method Injection, as this answer describes. This means that your Person
class becomes the following:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
// IDatabase is supplied using Method Injection
public static IEnumerable<Person> GetAll(IDatabase database)
{
string sql = "select id, name from person where ...... ";
using var con = database.GetConnection();
return con.Query<Person>(sql);
}
}
Consequence of method injection, however, is that the dependency (i.e. IDatabase
) is no longer hidden to the consumers, but instead need to be supplied by them. e.g.:
public sealed class PersonService : IPersonService
{
private readonly IDatabase database;
public PersonService(IDatabase database)
{
this.database = database;
}
public void ChangeName(int id, string newName)
{
var persons = Person.GetAll(this.database);
var person = persons.Single(p => p.Id == id);
person.Name = newName;
person.Save(this.database);
}
}
In practice, this means that those consumers should either get this dependency injected into them either using Constructor Injection or Method Injection. The previous example with PersonService
shows Constructor Injection.
Having to supply dependencies through Method Injection can be unpractical for many reasons: first of all, it complicates consumers with an extra dependency, as the example above shows.
The use of such static method is probably not the best solution. However, making the method an instance method, on the other hand, is also not practical, because:
IDatabase
into Person
, but that complicates the creation of Person
instances, as they are typically created by your O/RM mapper (e.g. Entity Framework). Also, you are injecting a dependency that is only needed in the case that GetAll
is being called.Person
in order to get all persons. e.g.:
var person = new Person(database);
return person.GetAll(); // Get all of what? Persons of that person?
IDatabase
into person, you'll soon end up with a dozen or more dependencies into Person
, because you likely add many more person-related methods in this class. eg.:
var person = new Person(database, timeProvider, userContext)
{
Id = 100,
Name = "Steven"
};
// Save might require the current time and the user executing the operation
return person.Save();
This might lead to the conclusion that having this GetAll
method on Person
method not be the best solution. Rather than placing this method (and many others) inside Person
, it could be cleaner to separate the concerns of data access and your domain entity.
A very common pattern that is used for such situations is the Repository Pattern. There are several variations of this pattern, but in essence you define a separate interface for your data-access logic. This was already suggested in the comments by @Selvin and me. For instance, you can define an IPersonRepository
interface:
public interface IPersonRepository
{
IEnumerable<Person> GetAll();
void Save(Person person);
}
This allows moving the data access logic into separate class:
public sealed class SqlPersonRepository : IPersonRepository
{
private readonly IDatabase database;
public SqlPersonRepository(IDatabase database)
{
this.database = database;
}
public IEnumerable<Person> GetAll()
{
string sql = "select id, name from person where ...... ";
using var con = this.database.GetConnection();
return con.Query<Person>(sql);
}
public void Save(Person person) ...
}
Advantage of separating this logic from the Person
class, is that allows you to place the Person
class and the IPersonRepository
inside your Domain layer project, while you keep the SqlPersonRepository
implementation hidden from the domain layer by placing it inside your Data Access layer project (where this project takes a dependency on the Domain layer project).
Obvious consequence of this design is that any class that requires getting all persons, needs to get IPersonRepository
injected into it. But when you are practicing Dependency Injection, this will typically not be a problem, and actually something that is encouraged. This can be demonstrated in a changed version of the previous PersonService
:
public sealed class PersonService : IPersonService
{
private readonly IPersonRepository repository;
public PersonService(IPersonRepository repository)
{
this.repository = repository;
}
public void ChangeName(int id, string newName)
{
var persons = this.repository.GetAll();
var person = persons.Single(p => p.Id == id);
person.Name = newName;
this.repository.Save(person);
}
}