feat: factor connection factory into Sqlite package
BREAKING CHANGE: The previous abstraction was confusing/misleading and was forcefully registering all connections as transient. These changes invert the relationship a bit and change the injectable INonTransactionalDbConnection<TConnectionType> into scoped. It also allows us to provide an explicit connection factory for Sqlite and an extension method that's easier to use while preserving the very slim nature of the core data access package.
This commit is contained in:
parent
7482f08b4f
commit
0b4f638cb9
3
.github/workflows/publish-release.yml
vendored
3
.github/workflows/publish-release.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
|||
- run: dotnet restore
|
||||
- run: dotnet build --no-restore -c Release -p:Version=${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}
|
||||
- run: dotnet pack --no-build --no-restore --nologo --output=dist -c Release
|
||||
- run: gh release create "v${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}" --notes-file CHANGELOG.md "dist/FruityFoundation.Base.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" "dist/FruityFoundation.Db.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" "dist/FruityFoundation.FsBase.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" "dist/FruityFoundation.DataAccess.Abstractions.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" "dist/FruityFoundation.DataAccess.Core.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg"
|
||||
- run: gh release create "v${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}" --notes-file CHANGELOG.md "dist/FruityFoundation.Base.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" "dist/FruityFoundation.Db.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" "dist/FruityFoundation.FsBase.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" "dist/FruityFoundation.DataAccess.Abstractions.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" "dist/FruityFoundation.DataAccess.Core.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" "dist/FruityFoundation.DataAccess.Sqlite.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
- name: Publish to NuGet
|
||||
|
@ -48,3 +48,4 @@ jobs:
|
|||
dotnet nuget push "FruityFoundation.DataAccess.Abstractions.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
dotnet nuget push "FruityFoundation.DataAccess.Abstractions.FSharp.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
dotnet nuget push "FruityFoundation.DataAccess.Core.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
dotnet nuget push "FruityFoundation.DataAccess.Sqlite.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
|
|
5
Directory.Build.props
Normal file
5
Directory.Build.props
Normal file
|
@ -0,0 +1,5 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -1,27 +0,0 @@
|
|||
using System.Data.Common;
|
||||
using FruityFoundation.DataAccess.Abstractions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FruityFoundation.DataAccess.Core;
|
||||
|
||||
public class DbConnectionFactory : IDbConnectionFactory
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DbConnectionFactory(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public INonTransactionalDbConnection<ReadWrite> CreateConnection()
|
||||
{
|
||||
var nonTxConnection = _serviceProvider.GetRequiredService<INonTransactionalDbConnection<ReadWrite>>();
|
||||
return nonTxConnection;
|
||||
}
|
||||
|
||||
public INonTransactionalDbConnection<ReadOnly> CreateReadOnlyConnection()
|
||||
{
|
||||
var nonTxConnection = _serviceProvider.GetRequiredService<INonTransactionalDbConnection<ReadOnly>>();
|
||||
return nonTxConnection;
|
||||
}
|
||||
}
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using FruityFoundation.DataAccess.Abstractions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FruityFoundation.DataAccess.Core;
|
||||
|
||||
[ExcludeFromCodeCoverage(Justification = "Dependency injection helpers")]
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static void AddDataAccessCore(
|
||||
this IServiceCollection services,
|
||||
Func<IServiceProvider, INonTransactionalDbConnection<ReadWrite>> readWriteConnectionImplementationFactory,
|
||||
Func<IServiceProvider, INonTransactionalDbConnection<ReadOnly>> readOnlyConnectionImplementationFactory
|
||||
)
|
||||
{
|
||||
services.AddTransient<INonTransactionalDbConnection<ReadWrite>>(readWriteConnectionImplementationFactory);
|
||||
services.AddTransient<INonTransactionalDbConnection<ReadOnly>>(readOnlyConnectionImplementationFactory);
|
||||
services.AddSingleton<IDbConnectionFactory, DbConnectionFactory>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>Kyle Ratti</Authors>
|
||||
<RepositoryUrl>https://github.com/kyleratti/FruityFoundation</RepositoryUrl>
|
||||
<Version>1.12.2</Version>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<TargetFrameworks>net8.0;net6.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FruityFoundation.DataAccess.Abstractions\FruityFoundation.DataAccess.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\FruityFoundation.DataAccess.Core\FruityFoundation.DataAccess.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,36 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using FruityFoundation.DataAccess.Abstractions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FruityFoundation.DataAccess.Sqlite;
|
||||
|
||||
[ExcludeFromCodeCoverage(Justification = "Dependency injection helpers")]
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static void AddSqliteDataAccess(
|
||||
this IServiceCollection services,
|
||||
Func<IServiceProvider, string> getReadWriteConnectionString,
|
||||
Func<IServiceProvider, string> getReadOnlyConnectionString
|
||||
)
|
||||
{
|
||||
services.AddScoped<INonTransactionalDbConnection<ReadWrite>>(serviceProvider =>
|
||||
{
|
||||
var connectionFactory = serviceProvider.GetRequiredService<IDbConnectionFactory>();
|
||||
|
||||
return connectionFactory.CreateConnection();
|
||||
});
|
||||
|
||||
services.AddScoped<INonTransactionalDbConnection<ReadOnly>>(serviceProvider =>
|
||||
{
|
||||
var connectionFactory = serviceProvider.GetRequiredService<IDbConnectionFactory>();
|
||||
|
||||
return connectionFactory.CreateReadOnlyConnection();
|
||||
});
|
||||
|
||||
services.AddSingleton<IDbConnectionFactory, SqliteDbConnectionFactory>(serviceProvider =>
|
||||
new SqliteDbConnectionFactory(
|
||||
serviceProvider,
|
||||
getReadOnlyConnectionString: getReadOnlyConnectionString,
|
||||
getReadWriteConnectionString: getReadWriteConnectionString));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using FruityFoundation.DataAccess.Abstractions;
|
||||
using FruityFoundation.DataAccess.Core;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace FruityFoundation.DataAccess.Sqlite;
|
||||
|
||||
public class SqliteDbConnectionFactory : IDbConnectionFactory
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly Func<IServiceProvider, string> _getReadWriteConnectionString;
|
||||
private readonly Func<IServiceProvider, string> _getReadOnlyConnectionString;
|
||||
|
||||
public SqliteDbConnectionFactory(
|
||||
IServiceProvider serviceProvider,
|
||||
Func<IServiceProvider, string> getReadWriteConnectionString,
|
||||
Func<IServiceProvider, string> getReadOnlyConnectionString)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_getReadWriteConnectionString = getReadWriteConnectionString;
|
||||
_getReadOnlyConnectionString = getReadOnlyConnectionString;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public INonTransactionalDbConnection<ReadWrite> CreateConnection()
|
||||
{
|
||||
var connectionString = _getReadWriteConnectionString(_serviceProvider);
|
||||
var connection = new SqliteConnection(connectionString);
|
||||
|
||||
return new NonTransactionalDbConnection<ReadWrite>(connection);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public INonTransactionalDbConnection<ReadOnly> CreateReadOnlyConnection()
|
||||
{
|
||||
var connectionString = _getReadOnlyConnectionString(_serviceProvider);
|
||||
var connection = new SqliteConnection(connectionString);
|
||||
|
||||
return new NonTransactionalDbConnection<ReadOnly>(connection);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FruityFoundation.DataAccess.Abstractions.FSharp\FruityFoundation.DataAccess.Abstractions.FSharp.fsproj" />
|
||||
<ProjectReference Include="..\FruityFoundation.DataAccess.Abstractions\FruityFoundation.DataAccess.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="NUnit" Version="3.14.0"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.9.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="NUnit.Framework"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FruityFoundation.DataAccess.Abstractions\FruityFoundation.DataAccess.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\FruityFoundation.DataAccess.Sqlite\FruityFoundation.DataAccess.Sqlite.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,39 @@
|
|||
using FruityFoundation.DataAccess.Abstractions;
|
||||
using FruityFoundation.DataAccess.Sqlite;
|
||||
|
||||
namespace FruityFoundation.Tests.DataAccess.Sqlite;
|
||||
|
||||
public class SqliteDbConnectionFactoryTests
|
||||
{
|
||||
[Test]
|
||||
public void CreateConnection_WithValidConnectionString_ReturnsNonTransactionalDbConnection_ReadWrite()
|
||||
{
|
||||
// Arrange
|
||||
var connectionFactory = new SqliteDbConnectionFactory(
|
||||
serviceProvider: null!,
|
||||
getReadWriteConnectionString: _ => "Data Source=:memory:",
|
||||
getReadOnlyConnectionString: null!);
|
||||
|
||||
// Act
|
||||
var result = connectionFactory.CreateConnection();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<INonTransactionalDbConnection<ReadWrite>>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateReadOnlyConnection_WithValidConnectionString_ReturnsNonTransactionalDbConnection_ReadOnly()
|
||||
{
|
||||
// Arrange
|
||||
var connectionFactory = new SqliteDbConnectionFactory(
|
||||
serviceProvider: null!,
|
||||
getReadWriteConnectionString: null!,
|
||||
getReadOnlyConnectionString: _ => "Data Source=:memory:");
|
||||
|
||||
// Act
|
||||
var result = connectionFactory.CreateReadOnlyConnection();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<INonTransactionalDbConnection<ReadOnly>>());
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Base\Base.csproj" />
|
||||
<ProjectReference Include="..\FsBase\FsBase.fsproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Base\Base.csproj" />
|
||||
<ProjectReference Include="..\FsBase\FsBase.fsproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -24,6 +24,10 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FruityFoundation.DataAccess
|
|||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FruityFoundation.Tests.DataAccess.Abstractions.FSharp", "FruityFoundation.Tests.DataAccess.Abstractions.FSharp\FruityFoundation.Tests.DataAccess.Abstractions.FSharp.fsproj", "{27F4FB64-7A51-4315-BDAA-6EE07736C976}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess.Sqlite", "FruityFoundation.DataAccess.Sqlite\FruityFoundation.DataAccess.Sqlite.csproj", "{2DB2E605-68E8-47E3-A183-985C1A52B56A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.Tests.DataAccess.Sqlite", "FruityFoundation.Tests.DataAccess.Sqlite\FruityFoundation.Tests.DataAccess.Sqlite.csproj", "{A1AB7658-7310-490A-82BE-DAABCC204EA4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -70,6 +74,14 @@ Global
|
|||
{27F4FB64-7A51-4315-BDAA-6EE07736C976}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{27F4FB64-7A51-4315-BDAA-6EE07736C976}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{27F4FB64-7A51-4315-BDAA-6EE07736C976}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2DB2E605-68E8-47E3-A183-985C1A52B56A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2DB2E605-68E8-47E3-A183-985C1A52B56A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2DB2E605-68E8-47E3-A183-985C1A52B56A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2DB2E605-68E8-47E3-A183-985C1A52B56A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A1AB7658-7310-490A-82BE-DAABCC204EA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1AB7658-7310-490A-82BE-DAABCC204EA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1AB7658-7310-490A-82BE-DAABCC204EA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1AB7658-7310-490A-82BE-DAABCC204EA4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{50A75644-A1C3-4495-9DEB-DBB12D9334B5} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
||||
|
@ -79,5 +91,7 @@ Global
|
|||
{B65527CC-218A-4EA3-93DC-985713B5DFF4} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
|
||||
{B2E0156B-B631-4C80-A129-59472D2D0A77} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
|
||||
{27F4FB64-7A51-4315-BDAA-6EE07736C976} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
||||
{2DB2E605-68E8-47E3-A183-985C1A52B56A} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
|
||||
{A1AB7658-7310-490A-82BE-DAABCC204EA4} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
Loading…
Reference in New Issue
Block a user