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

Support DbDataSource #2429

Merged
merged 1 commit into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<Project>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest</AnalysisLevel>
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<PackageVersion Include="Npgsql" Version="$(NpgsqlVersion)" />
<PackageVersion Include="Npgsql.NodaTime" Version="$(NpgsqlVersion)" />
<PackageVersion Include="Npgsql.NetTopologySuite" Version="$(NpgsqlVersion)" />
<PackageVersion Include="Npgsql.DependencyInjection" Version="$(NpgsqlVersion)" />

<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
Expand Down
2 changes: 2 additions & 0 deletions src/EFCore.PG.NTS/EFCore.PG.NTS.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<PropertyGroup>
<AssemblyName>Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite</AssemblyName>
<RootNamespace>Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks Condition="'$(DeveloperBuild)' == 'True'">net7.0</TargetFrameworks>

<Authors>Shay Rojansky</Authors>
<Description>NetTopologySuite PostGIS spatial support plugin for PostgreSQL/Npgsql Entity Framework Core provider.</Description>
Expand Down
2 changes: 2 additions & 0 deletions src/EFCore.PG.NodaTime/EFCore.PG.NodaTime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<PropertyGroup>
<AssemblyName>Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime</AssemblyName>
<RootNamespace>Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks Condition="'$(DeveloperBuild)' == 'True'">net7.0</TargetFrameworks>

<Authors>Shay Rojansky</Authors>
<Description>NodaTime support plugin for PostgreSQL/Npgsql Entity Framework Core provider.</Description>
Expand Down
2 changes: 2 additions & 0 deletions src/EFCore.PG/EFCore.PG.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<PropertyGroup>
<AssemblyName>Npgsql.EntityFrameworkCore.PostgreSQL</AssemblyName>
<RootNamespace>Npgsql.EntityFrameworkCore.PostgreSQL</RootNamespace>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition="'$(DeveloperBuild)' == 'True'">net7.0</TargetFrameworks>

<Authors>Shay Rojansky;Austin Drenski;Yoh Deadfall;</Authors>
<Description>PostgreSQL/Npgsql provider for Entity Framework Core.</Description>
Expand Down
32 changes: 31 additions & 1 deletion src/EFCore.PG/Extensions/NpgsqlDatabaseFacadeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
using System.Data.Common;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;
Expand All @@ -23,4 +25,32 @@ public static class NpgsqlDatabaseFacadeExtensions
/// <returns>True if Npgsql is being used; false otherwise.</returns>
public static bool IsNpgsql(this DatabaseFacade database)
=> database.ProviderName == typeof(NpgsqlOptionsExtension).GetTypeInfo().Assembly.GetName().Name;

/// <summary>
/// Sets the underlying <see cref="DbDataSource" /> configured for this <see cref="DbContext" />.
/// </summary>
/// <remarks>
/// <para>
/// It may not be possible to change the data source if existing connection, if any, is open.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-connections">Connections and connection strings</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="databaseFacade">The <see cref="DatabaseFacade" /> for the context.</param>
/// <param name="dataSource">The connection string.</param>
public static void SetDbDataSource(this DatabaseFacade databaseFacade, DbDataSource dataSource)
=> ((NpgsqlRelationalConnection)GetFacadeDependencies(databaseFacade).RelationalConnection).DbDataSource = dataSource;

private static IRelationalDatabaseFacadeDependencies GetFacadeDependencies(DatabaseFacade databaseFacade)
{
var dependencies = ((IDatabaseFacadeDependenciesAccessor)databaseFacade).Dependencies;

if (dependencies is IRelationalDatabaseFacadeDependencies relationalDependencies)
{
return relationalDependencies;
}

throw new InvalidOperationException(RelationalStrings.RelationalNotInUse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,43 @@ public static DbContextOptionsBuilder UseNpgsql(
return optionsBuilder;
}

/// <summary>
/// Configures the context to connect to a PostgreSQL database with Npgsql.
/// </summary>
/// <param name="optionsBuilder">A builder for setting options on the context.</param>
/// <param name="dataSource">A <see cref="DbDataSource" /> which will be used to get database connections.</param>
/// <param name="npgsqlOptionsAction">An optional action to allow additional Npgsql-specific configuration.</param>
/// <returns>
/// The options builder so that further configuration can be chained.
/// </returns>
public static DbContextOptionsBuilder UseNpgsql(
this DbContextOptionsBuilder optionsBuilder,
DbDataSource dataSource,
Action<NpgsqlDbContextOptionsBuilder>? npgsqlOptionsAction = null)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
Check.NotNull(dataSource, nameof(dataSource));

var extension = (NpgsqlOptionsExtension)GetOrCreateExtension(optionsBuilder).WithDataSource(dataSource);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);

ConfigureWarnings(optionsBuilder);

npgsqlOptionsAction?.Invoke(new NpgsqlDbContextOptionsBuilder(optionsBuilder));

return optionsBuilder;
}

/// <summary>
/// <para>
/// Configures the context to connect to a PostgreSQL server with Npgsql, but without initially setting any
/// <see cref="DbConnection" /> or connection string.
/// <see cref="DbConnection" />, <see cref="DbDataSource" /> or connection string.
/// </para>
/// <para>
/// The connection or connection string must be set before the <see cref="DbContext" /> is used to connect
/// to a database. Set a connection using <see cref="RelationalDatabaseFacadeExtensions.SetDbConnection" />.
/// Set a connection string using <see cref="RelationalDatabaseFacadeExtensions.SetConnectionString" />.
/// The connection, data source or connection string must be set before the <see cref="DbContext" /> is used to connect
/// to a database. Set a connection using <see cref="RelationalDatabaseFacadeExtensions.SetDbConnection" />, a data source using
/// <see cref="NpgsqlDatabaseFacadeExtensions.SetDbDataSource" />, or a connection string using
/// <see cref="RelationalDatabaseFacadeExtensions.SetConnectionString" />.
/// </para>
/// </summary>
/// <param name="optionsBuilder">The builder being used to configure the context.</param>
Expand Down Expand Up @@ -156,6 +184,23 @@ public static DbContextOptionsBuilder<TContext> UseNpgsql<TContext>(
=> (DbContextOptionsBuilder<TContext>)UseNpgsql(
(DbContextOptionsBuilder)optionsBuilder, connection, npgsqlOptionsAction);

/// <summary>
/// Configures the context to connect to a PostgreSQL database with Npgsql.
/// </summary>
/// <param name="optionsBuilder">A builder for setting options on the context.</param>
/// <param name="dataSource">A <see cref="DbDataSource" /> which will be used to get database connections.</param>
/// <param name="npgsqlOptionsAction">An optional action to allow additional Npgsql-specific configuration.</param>
/// <returns>
/// The options builder so that further configuration can be chained.
/// </returns>
public static DbContextOptionsBuilder<TContext> UseNpgsql<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder,
DbDataSource dataSource,
Action<NpgsqlDbContextOptionsBuilder>? npgsqlOptionsAction = null)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseNpgsql(
(DbContextOptionsBuilder)optionsBuilder, dataSource, npgsqlOptionsAction);

/// <summary>
/// Returns an existing instance of <see cref="NpgsqlOptionsExtension"/>, or a new instance if one does not exist.
/// </summary>
Expand Down
43 changes: 42 additions & 1 deletion src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Data.Common;
using System.Globalization;
using System.Net.Security;
using System.Text;
Expand All @@ -9,9 +10,16 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
/// </summary>
public class NpgsqlOptionsExtension : RelationalOptionsExtension
{
private DbDataSource? _dataSource, _calculatedDataSource;
private DbContextOptionsExtensionInfo? _info;
private readonly List<UserRangeDefinition> _userRangeDefinitions;

/// <summary>
/// The <see cref="DbDataSource" />, or <see langword="null" /> if a connection string or <see cref="DbConnection" /> was used
/// instead of a <see cref="DbDataSource" />.
/// </summary>
public virtual DbDataSource? DataSource => _calculatedDataSource;

/// <summary>
/// The name of the database for administrative operations.
/// </summary>
Expand Down Expand Up @@ -65,6 +73,7 @@ public NpgsqlOptionsExtension()
/// <param name="copyFrom">The instance to copy.</param>
public NpgsqlOptionsExtension(NpgsqlOptionsExtension copyFrom) : base(copyFrom)
{
_dataSource = copyFrom._dataSource;
AdminDatabase = copyFrom.AdminDatabase;
PostgresVersion = copyFrom.PostgresVersion;
UseRedshift = copyFrom.UseRedshift;
Expand All @@ -85,6 +94,21 @@ public NpgsqlOptionsExtension(NpgsqlOptionsExtension copyFrom) : base(copyFrom)
/// </summary>
public override int? MinBatchSize => base.MinBatchSize ?? 2;

/// <summary>
/// Creates a new instance with all options the same as for this instance, but with the given option changed.
/// It is unusual to call this method directly. Instead use <see cref="DbContextOptionsBuilder" />.
/// </summary>
/// <param name="dataSource">The option to change.</param>
/// <returns>A new instance with the option changed.</returns>
public virtual RelationalOptionsExtension WithDataSource(DbDataSource? dataSource)
{
var clone = (NpgsqlOptionsExtension)Clone();

clone._dataSource = dataSource;

return clone;
}

/// <summary>
/// Returns a copy of the current instance configured with the specified range mapping.
/// </summary>
Expand Down Expand Up @@ -176,6 +200,18 @@ public override void Validate(IDbContextOptions options)
{
base.Validate(options);

// If we don't have an explicitly-configured data source, try to get one from the application service provider.
_calculatedDataSource = _dataSource
?? options.FindExtension<CoreOptionsExtension>()?.ApplicationServiceProvider?.GetService<NpgsqlDataSource>();

if (_calculatedDataSource is not null
&& (ProvideClientCertificatesCallback is not null
|| RemoteCertificateValidationCallback is not null
|| ProvidePasswordCallback is not null))
{
throw new InvalidOperationException();
}

if (UseRedshift && PostgresVersion is not null)
{
throw new InvalidOperationException($"{nameof(UseRedshift)} and {nameof(PostgresVersion)} cannot both be set");
Expand Down Expand Up @@ -320,7 +356,7 @@ public override string LogFragment
builder.Append(";");
}

builder.Length = builder.Length -1;
builder.Length -= 1;
builder.Append("] ");
}

Expand All @@ -339,6 +375,11 @@ public override int GetServiceProviderHashCode()
hashCode.Add(userRangeDefinition);
}

if (Extension._dataSource is not null)
{
hashCode.Add(Extension._dataSource.ConnectionString);
}

hashCode.Add(Extension.AdminDatabase);
hashCode.Add(Extension.PostgresVersion);
hashCode.Add(Extension.UseRedshift);
Expand Down
7 changes: 1 addition & 6 deletions src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ public class NpgsqlSingletonOptions : INpgsqlSingletonOptions
/// </summary>
public static readonly Version DefaultPostgresVersion = new(12, 0);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
/// <inheritdoc />
public virtual Version PostgresVersion { get; private set; } = null!;

/// <summary>
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore.PG/Properties/NpgsqlStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.PG/Properties/NpgsqlStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,7 @@
<data name="RowValueComparisonRequiresTuplesOfSameLength" xml:space="preserve">
<value>Row values comparisons require two tuple arguments of the same length.</value>
</data>
<data name="CannotUseDataSourceWithAuthCallbacks" xml:space="preserve">
<value>Cannot set ProvideClientCertificatesCallback, RemoteCertificateValidationCallback or ProvidePasswordCallback when a data source is provided.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface INpgsqlRelationalConnection : IRelationalConnection
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
INpgsqlRelationalConnection CreateMasterConnection();
INpgsqlRelationalConnection CreateAdminConnection();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
8 changes: 4 additions & 4 deletions src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public NpgsqlDatabaseCreator(
/// </summary>
public override void Create()
{
using (var masterConnection = _connection.CreateMasterConnection())
using (var masterConnection = _connection.CreateAdminConnection())
{
try
{
Expand All @@ -82,7 +82,7 @@ public override void Create()
/// </summary>
public override async Task CreateAsync(CancellationToken cancellationToken = default)
{
using (var masterConnection = _connection.CreateMasterConnection())
using (var masterConnection = _connection.CreateAdminConnection())
{
try
{
Expand Down Expand Up @@ -278,7 +278,7 @@ public override void Delete()
{
ClearAllPools();

using (var masterConnection = _connection.CreateMasterConnection())
using (var masterConnection = _connection.CreateAdminConnection())
{
Dependencies.MigrationCommandExecutor
.ExecuteNonQuery(CreateDropCommands(), masterConnection);
Expand All @@ -295,7 +295,7 @@ public override async Task DeleteAsync(CancellationToken cancellationToken = def
{
ClearAllPools();

using (var masterConnection = _connection.CreateMasterConnection())
using (var masterConnection = _connection.CreateAdminConnection())
{
await Dependencies.MigrationCommandExecutor
.ExecuteNonQueryAsync(CreateDropCommands(), masterConnection, cancellationToken)
Expand Down
Loading