0

I have a need to inject a dependency into my DbContext Class for my project. I am trying to do this in a way that follows "Clean Architecture".

I need to be able to use the DbContext's empty constructor and still reference the connection string from a config file.

I make use of this Generic Repository and inject this into services. It is defined in the Application Layer.

     public class GenericRepository<C, T> : IGenericRepository<C, T> where T : class where C : DbContext, new()
     {
        private DbContext _entities = new C();
     }

In my EF Core Context class (defined in the persistence layer) I have this:

   public partial class MyContext : DbContext
   {
       private IConnectionStringProvider _connectionStringProvider;  

        public virtual DbSet<MyEntity> { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {  
                optionsBuilder.UseSqlServer(_connectionStringProvider.GetConnectionString());
            }
        }
    }

I also have the implementation for IConnectionStringProvider defined in the Persistence layer. I want this implementation to be easily swappable if need be. At the moment it reads from App.config and uses the ConfigurationManager nuget package. But this could change in the future so it needs to be easily swappable.

The IConnectionStringProvider is defined in the Application layer:

 public interface IConnectionStringProvider
 {
     string GetConnectionString();
 }

In my Presentation layer I have an ASP.NET Core MVC project. In StartUp.cs I am using the standard way of injecting the dbContext to my contollers:

services.AddDbContext<ContractsWorkloadContext>();

I can get this to work just fine if I override the OnModelCreatingMethod in the following way.

 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 {
     if (!optionsBuilder.IsConfigured)
     {
         _connectionStringProvider = new ConnectionStringProvider();
         optionsBuilder.UseSqlServer(ConnectionStringProvider.GetConnectionString());
     }
 }

This method allows me to use connection string values from configuration with an empty context constructor instance, which is needed for it to work with my generic repository, and also still be able to use the constructor with a DbContextOptionsBuilder parameter.

My only problem left to solve is how to achieve this without the Context being dependent upon the implementation.

I have tried Autofac property injection by registering the IConnectionStringProvider interface and implementation and also registering the Context class with properties "Autowired". But the property is always null.

And yes I understand that the above is a field and not a property. I have tried to modify the context class to have a property instead of a field. I have tried to even create a method to set the field and use method injection with autofac as well. But the property/field is always null in every case.

 builder.RegisterType<ConnectionStringProvider>().As<IConnectionStringProvider>()();
 builder.RegisterType<MyContext>().PropertiesAutowired();

And

 builder.RegisterType<ContractsWorkloadContext>().OnActivated(e =>
 {
     var provider = e.Context.Resolve<IConnectionStringProvider>();
     e.Instance.SetConnectionstringProvider(provider);
 });

In summary I need to:

  1. Inject the connection string into the context
  2. Allow for empty constructor instance creation
  3. Maintain "Clean Architecture" Design

Any help here would be appreciated.

Thanks.

Zach Painter
  • 180
  • 1
  • 9
  • "Allow for empty constructor instance creation" - why? Can you elaborate on it? It looks somewhat suspicious. Only DI container should know how to create instances, and autofac actually tries to find a constructor with the most number of parameters that container is able to provide - which means that you don't really need parameterless constructor at all. What use case am I missing? – Alexander Leonov Apr 12 '19 at 03:10
  • I have a generic repository that takes a entity framework db context type parameter...(this uses the new() constraint). If I want to use that with entity framework core I need the empty constructor method to create the context in my repository implementation. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/new-constraint – Zach Painter Apr 12 '19 at 19:46
  • 1
    Well, new() constraint is needed for something that needs to create instances - which is exactly the opposite to the whole DI idea, so I still didn't get the idea... Can that constraint be removed and replaced with DI managing DbContext instances for your repository? – Alexander Leonov Apr 12 '19 at 21:54
  • That is exaclty what I am doing now... Thank you. – Zach Painter Mar 23 '20 at 16:03

2 Answers2

1

What I ended up doing was creating a Static reference to the Connection String and then a readonly property in the context that returns that value.

By doing it this way I am able to abstract the IConnectionStringProvider in the application layer and implement it in the Infrastructure layer. This will allow the implementation to be swapped out with minimal effort if need be.

Also, by adding the readonly property that returns the static connections string, I am able to use a Generic Repository that makes use of new() and still get the connection string from a config file.

The only downside to this is that the connection string is stored in memory for the entire application lifetime.

DbContext

The static value is set by the provider implementation in the StartUp.cs file in the presentation layer.

Set Static Value

This definitely works... But if there is a better way then I'm all ears... :)

Zach Painter
  • 180
  • 1
  • 9
  • 1
    Was there any reason that you have not used something like `services.AddDbContext(options => { options.UseSqlServer(Configuration.GetConnectionString("default")); });` – Hamid Mayeli Mar 16 '20 at 16:37
  • 1
    The reason was because I had a generic repository that took a db context type parameter and called new Context() in the implementation. I have long since abandoned that repository and now use an EF core implementation where configuration can be injected through di container. In short I do use the above snippet like you suggested. Thank you. – Zach Painter Mar 23 '20 at 15:33
-1

This is a better way... for any who stumble upon this..

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

The more specific Repository implementations are just classes that extend the base repository implementation. Such as:

EFCustomerRepository : EFRepository<MyContext, Customer>

Now I can inject IMyEFCoreDatabase any where throughout the app via dependency injection and reference the repositories stored on the implementation.

This is a better way than above because my repositories no longer call new C(). Instead they now inject the context via constructor injection.

Zach Painter
  • 180
  • 1
  • 9