2

...Maybe using TFactory in AddDbContextFactory<TContext, TFactory> in EF Core extensions?

I've only seen AddDbContextFactory examples being used with just the TContext generic. They're always very explicit to say you have to use a using statement.

In similar situations (when I useClass in Angular or AddScoped in .NET Core), I make the variable I want to see in a constructor the first generic argument and the second generic argument what actually gets injected. You know, like:

services.AddScoped<IService, RealService>();

Obviously, this isn't the case with

services.AddDbContextFactory<ADbContextIHaveBeenInjecting, AFactoryThatWillReturnADbContextIHaveBeenInjecting>();

I was hoping this would eliminate the need to do the whole using thing.

Is there another way I can do this without having to re-write every injected DbContext to conform with their prescribed:

public void DoSomething()
{
    using (var context = _contextFactory.CreateDbContext())
    {
        // ...
    }
}

As I said, my hope was to use something like this for the factory:

public class MyDbContextFactory : IDbContextFactory<MyDbContext>
{

    public MyDbContextFactory(DbContextOptions options)
    {
    }

    public MyDbContext CreateDbContext()
    {
        var ProviderName = GetProviderName();
   
        switch (ProviderName)
        {
            case "System.Data.SqlClient":
                return new SqlServerDbContext(new DbContextOptionsBuilder<SqlServerDbContext>().UseSqlServer(ConnectionString).Options);
            case "Npgsql":
                return new PostgreSqlDbContext(new DbContextOptionsBuilder<PostgreSqlDbContext>().UseNpgsql(ConnectionString).Options);
            default:
                throw new NullReferenceException("Missing provider name for DbContext. Should be Npgsql or System.Data.SqlClient");
        }
    }

 
}

Then, set it up in Startup.cs ConfigureServices like:

services.AddDbContextFactory<MyDbContext, MyDbContextFactory>();

So I could inject in a class like this:

public class MyController : BaseApiController
{

    private readonly MyDbContext _myDbContext;


    public MyController(MyDbContext myDbContext)
    {
        _myDbContext = myDbContext;
    }


    [HttpGet("GetACount")]
    public IActionResult GetACount()
    {
        var count = _myDbContext.MyRecord.Count();

        return Ok(count);
    }
...

Is there a way to do this using the AddDbContextFactory? What is TFactory actually for? Is there another way to do this?

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Gabriel Kunkel
  • 2,643
  • 5
  • 25
  • 47
  • how do you expect `GetProviderName` to be implemented? – Daniel A. White Jan 20 '22 at 18:29
  • It was just a "non-distracting example." Short story: injected configuration gets updated live and we get ProviderName from that. – Gabriel Kunkel Jan 20 '22 at 18:32
  • You don't need to use a context factory. Just use IOptions and map to a section in your json file like this: "DatabaseConnections": { "OracleConnections": [ { "Alias": "Optional", "ConnectionString": "Required" }, { "Alias": "Optional", "ConnectionString": "Required" } ], "MSSqlConnections": [ { "Alias": "Optional", "ConnectionString": "Required" } ], "SqliteConnections" [ { "Alias": "Optional", "ConnectionString": "Required" } ] } – GH DevOps Jan 20 '22 at 18:41
  • All providers inherit from IDbConnection. You can use that as your interface to inject into your container. – GH DevOps Jan 20 '22 at 18:42

2 Answers2

2

DbContextFactory is specifically intended to require you to manage the lifecycle of your DbContext, because Blazor server apps don't use a Scope-per-Http request like ASP.NET Core does, so a Scoped DbContext won't work.

If you want a Scoped DbContext just use .AddDbContext intead of .AddDbContextFactory.

If you have registered a DbContextFactory but still want to inject a scoped DbContext direcly in your services, then register it like this:

    services.AddScoped<MyDbContext>(sp =>
    {
        var cf = sp.GetRequiredService<IDbContextFactory<MyDbContext>>();

        var db = cf.CreateDbContext();

        return db;

    });
David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • Thanks, David. The thought was that adding the DbContextFactory was critical to handle concurrent dbcontexts. Is that addressed with just using AddDbContext? ...And with the example you gave, does that create the opportunity to switch between dbContexts while the code is live? ...So each time dbContext is used it checks the ProviderName and returns the context based on that ProviderName using a switch/if-else? – Gabriel Kunkel Jan 20 '22 at 18:55
  • Using concurrent DbContext instances in a single controller _also_ requires DbContextFactory, and opting-out of the single DbContext per HTTP Request. And yes this factory method run on each request for a DbContext, so you can add conditional logic there. But you can _also_ do that with this overload `services.AddDbContext((sp, options) =>` – David Browne - Microsoft Jan 20 '22 at 19:01
  • I understand. Is there anything additional that needs to be done to be, "opting-out of the single DbContext per HTTP Request"? (Other than using the DbContextFactory?) ...It seems like there's no way to just inject a different subclass of DbContext on the fly, so I'll have to use the using statement. Right? – Gabriel Kunkel Jan 20 '22 at 19:34
  • There probably is a better way. But this question is too cluttered up to answer it. Post a new question. Are you trying to switch per request between RDBMSs? Do you _need_ different DbContext subtypes, or is that part of a proposed solution? What framework are you using, ie ASP.NET Core MVC? Something else? – David Browne - Microsoft Jan 20 '22 at 19:49
  • 1
    For anyone reading, we settled on this construction with my factory: ```services.AddDbContextFactory(); services.AddScoped(p => p.GetRequiredService>().CreateDbContext());``` Works now. Thanks, David. – Gabriel Kunkel Jan 20 '22 at 21:44
1

Dependency inject the concrete class. create a factory to select the subclass by type. Create a parent class with a private DbContext _dbContext. Inherit the subclass from the parent class and call the parent class constructor with :base(dbContext) of the subclass. The parent class can now access in its methods the subclass context. The subclass can share the methods of the parent class for (add, select, update, and deleting by set the data context of the subclass). the subclass will dependency inject the specific dbcontext in its constructor and set the parent class dbcontext variable in its constructor. the subclass repository class can then access the base class methods within its body.

in startup define the subclass repository pattern

in public void ConfigureServices(IServiceCollection services)

  var connectionString = Configuration.GetConnectionString("ABCContext_DbCoreConnectionString");
  services.AddDbContext<ABCContext>(options1 => options1.UseSqlServer(connectionString));

 services.AddTransient<IRepositoryMySubclass, RepositoryMySubclass>();

sub class

  public RepositorySubclass(ABCContext dbContext) : base(dbContext)
    {
        _dbContext = dbContext;
    }
Golden Lion
  • 3,840
  • 2
  • 26
  • 35
  • Thanks. How would I switch between SqlServerDbContext and PostgreSQLDbContext on the fly, after Startup.cs has been run? – Gabriel Kunkel Jan 20 '22 at 18:39
  • you can dependency inject both or either of the subclass dbcontext in the constructor. the dependency injection resolver will then create the concrete class. otherwise you have to use generics and invoke to with a factory to build your concrete class - similar to services. – Golden Lion Jan 20 '22 at 18:43
  • dependency injection occurs in the constructor of the subclass of the repository subclass. you can invoke the subclass based on a type of generic type if necessary. – Golden Lion Jan 20 '22 at 18:45