Search code examples
c#dependency-injectiondapper

How can I inject an object into a static method?


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.


Solution

  • 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:

    • It means you'll have to inject 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.
    • It is very weird to have to instantiate a specific 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?
      
    • When you start injecting 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);
        }
    }