Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency injection with NpgsqlDataSourceBuilder and DBContext #2876

Closed
pafrench opened this issue Sep 18, 2023 · 8 comments
Closed

Dependency injection with NpgsqlDataSourceBuilder and DBContext #2876

pafrench opened this issue Sep 18, 2023 · 8 comments

Comments

@pafrench
Copy link
Contributor

pafrench commented Sep 18, 2023

Hello

I'm using Npgsql.EntityFrameworkCore.PostgreSQL 7.0.4 and am trying to use the newer NpgsqlDataSource (alongside DBContexts).

We have two separate DBContexts - essentially a reader and a writer (ReadOnly & Default) which are being registered via DI using:

builder.Services.AddDbContext<DefaultDbContext>( etc. );
builder.Services.AddDbContext<ReadOnlyDbContext>( etc. );

How do I register two separate NpgsqlDataSources (again, one for read-only and one for read/write)? The existing DI only lets me register one.

Also I don't see how the registered NpgsqlDataSource is related to the DBContext; are they completely separate? Or is there a way when registering the DBContext to use the registered NpgsqlDataSource?

Kind regards,
Paul

@roji
Copy link
Member

roji commented Sep 18, 2023

DbContext is an EF-level concept, whereas NpgsqlDataSource (or which extends DbDataSource) is a lower-level, System.Data concept.

When registering a DbContext in DI via AddDbContext, you can specify the DbDataSource context to use (in the "etc" part of your code above); see the 7.0 release notes for a code sample. When using EF, there's no real reason to register the NpgsqlDataSource itself in DI - it's just something that the EF DbContext is configured with.

@pafrench
Copy link
Contributor Author

Thanks for that, I see how to pass in the DbDataSource in the code sample you provided:

// Pass your data source to the EF provider:
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(dataSource);

But... how do I register two separate, distinct NpgsqlDataSources in DI? I appreciate it's not required using EF, but our existing db health checks need to run a bespoke query against the db (just a SELECT 1) and we were previously creating new connections, which is now being discouraged.

@roji
Copy link
Member

roji commented Sep 18, 2023

Note that EF itself exposes a health check feature, the standard way to do this in an ASP.NET application is described here.

Even if you want to do a bespoke health check, you can still resolve an EF DbContext from DI, and then execute whatever custom SQL query you want via ExecuteSql. You can even go more low-level and get a lower-level DbConnection from your DbContext, which is exactly what NpgsqlDataSource would provide.

In other words, there really shouldn't be a reason for you to directly register an NpgsqlDataSource in DI if you're using EF - you should be able to do things via EF in that case. If you still want to do this, then you indeed currently cannot register two different NpgsqlDataSources in the same DI container, since DI is type-based. However, the upcoming 8.0 release is introduceding keyed DI services, and npgsql/npgsql#5134 tracks making this smoother when using Npgsql.

I'm going to go ahead and close this issue as the questions seem to have been answered, but feel free to post back if you have additional questions or thoughts.

@roji roji closed this as not planned Won't fix, can't repro, duplicate, stale Sep 18, 2023
@pafrench
Copy link
Contributor Author

Sorry @roji , one more question on this.

I'm happy to use the DbContext as instructed above, but I need to use the UsePeriodicPasswordProvider to provide the password via AWS's Secret Manager at runtime.

Previously I was using ProvidePasswordCallback when setting up the DBContexts, but this has been marked as obsolete - what should I be using instead?

There's nothing else explicit for the DBContext within UseNpgsql and I can't pass in a DbDataSource as I can't register two different NpgsqlDataSources as previously discussed (and I can't simply new one up as shown the release notes as I need to rely on another DI provided service that encapsulates the secret manager code).

@pafrench
Copy link
Contributor Author

One more observation; in the sample code in the release notes, the use of UsePeriodicPasswordProvider() is incomplete (it's missing the passwordProvider func) and the dataSource variable passed in to UseNpgsql() is disposed of in the outer scope.

@roji
Copy link
Member

roji commented Sep 19, 2023

If you have more than on NpgsqlDataSource in your application, you simply can't use DI on them (not until keyed DI services is fully supported after 8.0). That means you're going to have to find a way to create your NpgsqlDataSources outside of DI - with the secret management - and then pass the data sources to EF's UseNpgsql(). You should be able to hook up to AWS's secret manager without DI (at which point you'd just instantiate NpgsqlDataSourceBuilder directly as shown in the sample).

(you always have the option of creating your own DI service provider just for the purpose of producing an NpgsqlDataSource instance, but that really shouldn't be necessary).

Re the sample,

[...] it's missing the passwordProvider func

Yeah, this isn't intended to be full documentation for UsePeriodicPasswordProvider (here are the full docs), only a quick code sample to help people understand the NpgsqlDataSource concept. I'll add three dots to indicate that parameters are required.

[...] the dataSource variable passed in to UseNpgsql() is disposed of in the outer scope

Is that a problem? That means it gets disposed when the program exits, which seems correct, no?

@pafrench
Copy link
Contributor Author

If you have more than on NpgsqlDataSource in your application, you simply can't use DI on them (not until keyed DI services

I'm using composition and am registering a ReadWriteDbDataSourceBuilder and ReadOnlyDbDataSourceBuilder:

public class ReadWriteDbDataSourceBuilder
{
    public DbDataSource DataSource { get; }

    public ReadWriteDbDataSourceBuilder(IConfiguration configuration, IDatabasePasswordManager databasePasswordManager)
    {
        var readWriteDataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.GetConnectionString(Constants.ConnectionStringNames.Writer));
        readWriteDataSourceBuilder.EnableParameterLogging();
        databasePasswordManager.ConfigurePassword(readWriteDataSourceBuilder, DbConnectionType.Writer);
        DataSource = readWriteDataSourceBuilder.Build();
    }
}

This way when I call UseNpgsql() I can pass in the data source using DI

provider.GetRequiredService<ReadOnlyDbDataSourceBuilder>().DataSource

Re the sample,

[...] it's missing the passwordProvider func

Yeah, this isn't intended to be full documentation for UsePeriodicPasswordProvider (here are the full docs), only a quick code sample to help people understand the NpgsqlDataSource concept. I'll add three dots to indicate that parameters are required.

All good.

[...] the dataSource variable passed in to UseNpgsql() is disposed of in the outer scope

Is that a problem? That means it gets disposed when the program exits, which seems correct, no?

Fair enough, it's a false positive from Rider (ReSharper?). It's now irrelevant anyway to how I've got it setup.

Thanks for all your speedy/helpful replies!

@roji
Copy link
Member

roji commented Sep 20, 2023

Great that it worked out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants