Compare commits
22 Commits
720340f0dd
...
6df46d3c2b
Author | SHA1 | Date | |
---|---|---|---|
|
6df46d3c2b | ||
|
7eed044713 | ||
|
ad62629a1d | ||
|
4dbba5c2f3 | ||
|
e8e0ea8f60 | ||
|
3d2c2524c1 | ||
|
0b4f638cb9 | ||
|
7482f08b4f | ||
|
2fd363d4b4 | ||
|
bf02139d64 | ||
|
d745706082 | ||
|
44b978309a | ||
|
6cbccd3d7d | ||
|
d079f12b16 | ||
|
90c0740778 | ||
|
e5ddf60850 | ||
|
df73060f99 | ||
|
f169ca3287 | ||
|
1266932d4e | ||
|
2242fe8733 | ||
|
2e2dc40cfb | ||
|
de8e30dbb4 |
6
.github/workflows/publish-release.yml
vendored
6
.github/workflows/publish-release.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- run: dotnet restore
|
- run: dotnet restore
|
||||||
- run: dotnet build --no-restore -c Release -p:Version=${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}
|
- 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: 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"
|
- 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:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
- name: Publish to NuGet
|
- name: Publish to NuGet
|
||||||
|
@ -45,3 +45,7 @@ jobs:
|
||||||
dotnet nuget push "FruityFoundation.Base.${{ 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.Base.${{ 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.FsBase.${{ 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.FsBase.${{ 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.Db.${{ 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.Db.${{ 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.${{ 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
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FakeItEasy" Version="8.2.0" />
|
<PackageReference Include="FakeItEasy" Version="8.3.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||||
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
|
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FruityFoundation.Base.Structures;
|
using FruityFoundation.Base.Structures;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
@ -51,6 +52,86 @@ public class MaybeExtensionTests
|
||||||
Assert.That(result.HasValue, Is.False);
|
Assert.That(result.HasValue, Is.False);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Enumerable_FirstOrEmptyAsync_WithEmptyEnumerable_ReturnsEmptyMaybe()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var data = ToAsyncEnumerable(Array.Empty<int>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await data.FirstOrEmptyAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result, Is.InstanceOf<Maybe<int>>());
|
||||||
|
Assert.That(result.HasValue, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Enumerable_FirstOrEmptyAsync_WithMatchingPredicate_ReturnsMaybeWithValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var data = ToAsyncEnumerable([1, 2, 3, 4]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await data.FirstOrEmptyAsync(x => x > 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result, Is.InstanceOf<Maybe<int>>());
|
||||||
|
Assert.That(result.HasValue, Is.True);
|
||||||
|
Assert.That(result.Value, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Enumerable_FirstOrEmptyAsync_WithMatchingAsyncPredicate_ReturnsMaybeWithValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var data = ToAsyncEnumerable([1, 2, 3, 4]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await data.FirstOrEmptyAsync(async x =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return x > 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result, Is.InstanceOf<Maybe<int>>());
|
||||||
|
Assert.That(result.HasValue, Is.True);
|
||||||
|
Assert.That(result.Value, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Enumerable_FirstOrEmptyAsync_WithNonMatchingPredicate_ReturnsEmptyMaybe()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var data = ToAsyncEnumerable([1, 2, 3, 4]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await data.FirstOrEmptyAsync(x => x > 100);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result, Is.InstanceOf<Maybe<int>>());
|
||||||
|
Assert.That(result.HasValue, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Enumerable_FirstOrEmptyAsync_WithNonMatchingAsyncPredicate_ReturnsEmptyMaybe()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var data = ToAsyncEnumerable([1, 2, 3, 4]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await data.FirstOrEmptyAsync(async x =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return x > 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result, Is.InstanceOf<Maybe<int>>());
|
||||||
|
Assert.That(result.HasValue, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void MaybeNullableTests()
|
public void MaybeNullableTests()
|
||||||
{
|
{
|
||||||
|
@ -125,4 +206,12 @@ public class MaybeExtensionTests
|
||||||
Assert.That(result, Is.InstanceOf<Maybe<int>>());
|
Assert.That(result, Is.InstanceOf<Maybe<int>>());
|
||||||
Assert.That(result.HasValue, Is.False);
|
Assert.That(result.HasValue, Is.False);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(IEnumerable<T> enumerable)
|
||||||
|
{
|
||||||
|
foreach (var item in enumerable)
|
||||||
|
yield return item;
|
||||||
|
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<Company />
|
<Company />
|
||||||
<Product>FruityFoundation.Base</Product>
|
<Product>FruityFoundation.Base</Product>
|
||||||
<RepositoryUrl>https://github.com/kyleratti/FruityFoundation</RepositoryUrl>
|
<RepositoryUrl>https://github.com/kyleratti/FruityFoundation</RepositoryUrl>
|
||||||
<Version>1.9.0</Version>
|
<Version>2.1.0</Version>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<AssemblyName>FruityFoundation.Base</AssemblyName>
|
<AssemblyName>FruityFoundation.Base</AssemblyName>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FruityFoundation.Base.Structures;
|
namespace FruityFoundation.Base.Structures;
|
||||||
|
|
||||||
|
@ -22,6 +23,64 @@ public static class MaybeExtensions
|
||||||
return Maybe.Empty<T>();
|
return Maybe.Empty<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async ValueTask<Maybe<T>> FirstOrEmptyAsync<T>(this IAsyncEnumerable<T> source, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (source is IList<T> { Count: > 0 } list)
|
||||||
|
return Maybe.Create(list[0]);
|
||||||
|
|
||||||
|
await using var e = source
|
||||||
|
.ConfigureAwait(false)
|
||||||
|
.WithCancellation(cancellationToken)
|
||||||
|
.GetAsyncEnumerator();
|
||||||
|
|
||||||
|
if (await e.MoveNextAsync())
|
||||||
|
return e.Current;
|
||||||
|
|
||||||
|
return Maybe.Empty<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async ValueTask<Maybe<T>> FirstOrEmptyAsync<T>(this IAsyncEnumerable<T> source, Func<T, bool> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (source is IList<T> { Count: > 0 } list)
|
||||||
|
return list[0];
|
||||||
|
|
||||||
|
await using var e = source
|
||||||
|
.ConfigureAwait(false)
|
||||||
|
.WithCancellation(cancellationToken)
|
||||||
|
.GetAsyncEnumerator();
|
||||||
|
|
||||||
|
while (await e.MoveNextAsync())
|
||||||
|
{
|
||||||
|
var value = e.Current;
|
||||||
|
|
||||||
|
if (predicate(value))
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Maybe.Empty<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async ValueTask<Maybe<T>> FirstOrEmptyAsync<T>(this IAsyncEnumerable<T> source, Func<T, ValueTask<bool>> asyncPredicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (source is IList<T> { Count: > 0 } list)
|
||||||
|
return list[0];
|
||||||
|
|
||||||
|
await using var e = source
|
||||||
|
.ConfigureAwait(false)
|
||||||
|
.WithCancellation(cancellationToken)
|
||||||
|
.GetAsyncEnumerator();
|
||||||
|
|
||||||
|
while (await e.MoveNextAsync())
|
||||||
|
{
|
||||||
|
var value = e.Current;
|
||||||
|
|
||||||
|
if (await asyncPredicate(value).ConfigureAwait(false))
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Maybe.Empty<T>();
|
||||||
|
}
|
||||||
|
|
||||||
public static Maybe<TValue> TryGetValue<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) =>
|
public static Maybe<TValue> TryGetValue<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) =>
|
||||||
dict.TryGetValue(key, out var value) ? Maybe.Create(value) : Maybe.Empty<TValue>();
|
dict.TryGetValue(key, out var value) ? Maybe.Create(value) : Maybe.Empty<TValue>();
|
||||||
|
|
||||||
|
|
54
CHANGELOG.md
54
CHANGELOG.md
|
@ -1,4 +1,58 @@
|
||||||
Changelog
|
Changelog
|
||||||
|
<a name="2.1.0"></a>
|
||||||
|
## [2.1.0](https://www.github.com/kyleratti/FruityFoundation/releases/tag/v2.1.0) (2024-09-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add F# tryQueryFirst ([ad62629](https://www.github.com/kyleratti/FruityFoundation/commit/ad62629a1d92248ce87b125b7b960dca215ce44b))
|
||||||
|
* add FirstOrEmptyAsync ([4dbba5c](https://www.github.com/kyleratti/FruityFoundation/commit/4dbba5c2f3d90ad1709d91aee13d32b9cb672835))
|
||||||
|
|
||||||
|
<a name="2.0.0"></a>
|
||||||
|
## [2.0.0](https://www.github.com/kyleratti/FruityFoundation/releases/tag/v2.0.0) (2024-07-12)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* factor connection factory into Sqlite package ([0b4f638](https://www.github.com/kyleratti/FruityFoundation/commit/0b4f638cb98568dcbb92baa1a4d76d5f91f6316b))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* set new package to same version number ([3d2c252](https://www.github.com/kyleratti/FruityFoundation/commit/3d2c2524c1c61546e7ae51ce6a6893cebf91e1ee))
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
* factor connection factory into Sqlite package ([0b4f638](https://www.github.com/kyleratti/FruityFoundation/commit/0b4f638cb98568dcbb92baa1a4d76d5f91f6316b))
|
||||||
|
|
||||||
|
<a name="1.12.1"></a>
|
||||||
|
## [1.12.1](https://www.github.com/kyleratti/FruityFoundation/releases/tag/v1.12.1) (2024-07-06)
|
||||||
|
|
||||||
|
<a name="1.12.0"></a>
|
||||||
|
## [1.12.0](https://www.github.com/kyleratti/FruityFoundation/releases/tag/v1.12.0) (2024-07-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add FSharp database abstraction layer ([d745706](https://www.github.com/kyleratti/FruityFoundation/commit/d7457060829e897c726f5d7522f56bfac11b8e9a))
|
||||||
|
|
||||||
|
<a name="1.11.0"></a>
|
||||||
|
## [1.11.0](https://www.github.com/kyleratti/FruityFoundation/releases/tag/v1.11.0) (2024-07-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add data access DI helper ([6cbccd3](https://www.github.com/kyleratti/FruityFoundation/commit/6cbccd3d7dd8308bd6e65fd28362f36c61dedd5e))
|
||||||
|
|
||||||
|
<a name="1.10.0"></a>
|
||||||
|
## [1.10.0](https://www.github.com/kyleratti/FruityFoundation/releases/tag/v1.10.0) (2024-06-24)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* initial port of data access ([de8e30d](https://www.github.com/kyleratti/FruityFoundation/commit/de8e30dbb41d8d70b685324847b07f7800e31488))
|
||||||
|
* make data access layer generic ([1266932](https://www.github.com/kyleratti/FruityFoundation/commit/1266932d4e5f76adb6a181234e644a74da273fa0))
|
||||||
|
* multi target .NET 6 and .NET 8 ([2242fe8](https://www.github.com/kyleratti/FruityFoundation/commit/2242fe8733f808fb6055baa14cf28123a31fa9dc))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add missing version node ([90c0740](https://www.github.com/kyleratti/FruityFoundation/commit/90c074077806f0e470081463480a716bcf552b0c))
|
||||||
|
* include license file ([f169ca3](https://www.github.com/kyleratti/FruityFoundation/commit/f169ca3287116ee17c5e32485af8a9b9107603b8))
|
||||||
|
|
||||||
<a name="1.9.0"></a>
|
<a name="1.9.0"></a>
|
||||||
## [1.9.0](https://www.github.com/kyleratti/FruityFoundation/releases/tag/v1.9.0) (2024-05-03)
|
## [1.9.0](https://www.github.com/kyleratti/FruityFoundation/releases/tag/v1.9.0) (2024-05-03)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<Company />
|
<Company />
|
||||||
<Product>FruityFoudnation.Db</Product>
|
<Product>FruityFoudnation.Db</Product>
|
||||||
<RepositoryUrl>https://github.com/kyleratti/FruityFoundation</RepositoryUrl>
|
<RepositoryUrl>https://github.com/kyleratti/FruityFoundation</RepositoryUrl>
|
||||||
<Version>1.9.0</Version>
|
<Version>2.1.0</Version>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||||
|
@ -24,4 +24,8 @@
|
||||||
<Compile Include="Db.fs" />
|
<Compile Include="Db.fs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
5
Directory.Build.props
Normal file
5
Directory.Build.props
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,34 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<Version>2.1.0</Version>
|
||||||
|
<Authors>Kyle Ratti</Authors>
|
||||||
|
<RepositoryUrl>https://github.com/kyleratti/FruityFoundation</RepositoryUrl>
|
||||||
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\LICENSE" Pack="true" PackagePath="" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="ReadOnlyDb.fs" />
|
||||||
|
<Compile Include="ReadWriteDb.fs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Base\Base.csproj" />
|
||||||
|
<ProjectReference Include="..\FruityFoundation.DataAccess.Abstractions\FruityFoundation.DataAccess.Abstractions.csproj" />
|
||||||
|
<ProjectReference Include="..\FsBase\FsBase.fsproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FSharp.Control.TaskSeq" Version="0.4.0" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,41 @@
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module FruityFoundation.DataAccess.Abstractions.FSharp.ReadOnlyDb
|
||||||
|
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Threading
|
||||||
|
open FSharp.Control
|
||||||
|
open FruityFoundation.DataAccess.Abstractions
|
||||||
|
open FruityFoundation.FsBase
|
||||||
|
|
||||||
|
let private toKeyValuePair (parms : (string * obj) seq) =
|
||||||
|
parms
|
||||||
|
|> Seq.map (fun (name, value) -> KeyValuePair(name, value))
|
||||||
|
|
||||||
|
let query<'a> (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.Query<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let queryUnbuffered<'a> (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = taskSeq {
|
||||||
|
yield! connection.QueryUnbuffered<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let querySingle<'a> (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.QuerySingle<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let tryQueryFirst<'a> (connection : ReadOnly IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
let! result = connection.TryQueryFirst<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
return result |> Option.fromMaybe
|
||||||
|
}
|
||||||
|
|
||||||
|
let execute (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.Execute(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let executeScalar<'a> (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.ExecuteScalar<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let executeReader (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.ExecuteReader(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module FruityFoundation.DataAccess.Abstractions.FSharp.ReadWriteDb
|
||||||
|
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Threading
|
||||||
|
open FSharp.Control
|
||||||
|
open FruityFoundation.DataAccess.Abstractions
|
||||||
|
open FruityFoundation.FsBase
|
||||||
|
|
||||||
|
let private toKeyValuePair (parms : (string * obj) seq) =
|
||||||
|
parms
|
||||||
|
|> Seq.map (fun (name, value) -> KeyValuePair(name, value))
|
||||||
|
|
||||||
|
let query<'a> (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.Query<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let queryUnbuffered<'a> (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = taskSeq {
|
||||||
|
yield! connection.QueryUnbuffered<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let querySingle<'a> (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.QuerySingle<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let tryQueryFirst<'a> (connection : ReadOnly IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
let! result = connection.TryQueryFirst<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
return result |> Option.fromMaybe
|
||||||
|
}
|
||||||
|
|
||||||
|
let execute (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.Execute(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let executeScalar<'a> (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.ExecuteScalar<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let executeReader (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.ExecuteReader(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace FruityFoundation.DataAccess.Abstractions;
|
||||||
|
|
||||||
|
public abstract class ConnectionType { }
|
||||||
|
|
||||||
|
public abstract class ReadOnly : ConnectionType { }
|
||||||
|
|
||||||
|
public abstract class ReadWrite : ReadOnly { }
|
|
@ -0,0 +1,23 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<TargetFrameworks>net8.0;net6.0</TargetFrameworks>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<Authors>Kyle Ratti</Authors>
|
||||||
|
<RepositoryUrl>https://github.com/kyleratti/FruityFoundation</RepositoryUrl>
|
||||||
|
<Version>2.1.0</Version>
|
||||||
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\LICENSE" Pack="true" PackagePath="" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Base\Base.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Data.Common;
|
||||||
|
using FruityFoundation.Base.Structures;
|
||||||
|
|
||||||
|
namespace FruityFoundation.DataAccess.Abstractions;
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedTypeParameter
|
||||||
|
public interface IDatabaseConnection<out TConnectionType> where TConnectionType : ConnectionType
|
||||||
|
{
|
||||||
|
public Task<IEnumerable<T>> Query<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||||
|
public IAsyncEnumerable<T> QueryUnbuffered<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||||
|
public Task<T> QuerySingle<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Maybe<T>> TryQueryFirst<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||||
|
public Task Execute(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||||
|
public Task<T?> ExecuteScalar<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||||
|
public Task<DbDataReader> ExecuteReader(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace FruityFoundation.DataAccess.Abstractions;
|
||||||
|
|
||||||
|
public interface IDatabaseTransactionConnection<out TConnectionType> : IDatabaseConnection<TConnectionType>, IDisposable, IAsyncDisposable
|
||||||
|
where TConnectionType : ConnectionType
|
||||||
|
{
|
||||||
|
public Task Commit(CancellationToken cancellationToken);
|
||||||
|
public Task Rollback(CancellationToken cancellationToken);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace FruityFoundation.DataAccess.Abstractions;
|
||||||
|
|
||||||
|
public interface IDbConnectionFactory
|
||||||
|
{
|
||||||
|
public INonTransactionalDbConnection<ReadWrite> CreateConnection();
|
||||||
|
public INonTransactionalDbConnection<ReadOnly> CreateReadOnlyConnection();
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Data;
|
||||||
|
using FruityFoundation.Base.Structures;
|
||||||
|
|
||||||
|
namespace FruityFoundation.DataAccess.Abstractions;
|
||||||
|
|
||||||
|
public interface INonTransactionalDbConnection<TConnectionType> : IDatabaseConnection<TConnectionType>, IDisposable, IAsyncDisposable
|
||||||
|
where TConnectionType : ConnectionType
|
||||||
|
{
|
||||||
|
public Task<IDatabaseTransactionConnection<TConnectionType>> CreateTransaction(CancellationToken cancellationToken);
|
||||||
|
public Task<IDatabaseTransactionConnection<TConnectionType>> CreateTransaction(IsolationLevel isolationLevel, CancellationToken cancellationToken);
|
||||||
|
}
|
152
FruityFoundation.DataAccess.Core/DbTransaction.cs
Normal file
152
FruityFoundation.DataAccess.Core/DbTransaction.cs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Dapper;
|
||||||
|
using FruityFoundation.Base.Structures;
|
||||||
|
using FruityFoundation.DataAccess.Abstractions;
|
||||||
|
|
||||||
|
namespace FruityFoundation.DataAccess.Core;
|
||||||
|
|
||||||
|
// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
|
||||||
|
public class DbTransaction<TConnectionType> : IDatabaseTransactionConnection<TConnectionType>
|
||||||
|
where TConnectionType : ConnectionType
|
||||||
|
{
|
||||||
|
private readonly DbTransaction _transaction;
|
||||||
|
|
||||||
|
internal DbTransaction(DbTransaction transaction)
|
||||||
|
{
|
||||||
|
_transaction = transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<IEnumerable<T>> Query<T>(
|
||||||
|
string sql,
|
||||||
|
object? param = null,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (_transaction.Connection is not { } conn)
|
||||||
|
throw new InvalidOperationException("Transaction connection cannot be null");
|
||||||
|
|
||||||
|
var command = new CommandDefinition(sql, param, transaction: _transaction, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
return await conn.QueryAsync<T>(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async IAsyncEnumerable<T> QueryUnbuffered<T>(
|
||||||
|
string sql,
|
||||||
|
object? param = null,
|
||||||
|
[EnumeratorCancellation] CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (_transaction.Connection is not { } conn)
|
||||||
|
throw new InvalidOperationException("Transaction connection cannot be null");
|
||||||
|
|
||||||
|
var query = conn.QueryUnbufferedAsync<T>(sql, param, transaction: _transaction)
|
||||||
|
.WithCancellation(cancellationToken);
|
||||||
|
|
||||||
|
await foreach (var item in query)
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<T> QuerySingle<T>(string sql, object? param = null, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (_transaction.Connection is not { } conn)
|
||||||
|
throw new InvalidOperationException("Transaction connection cannot be null");
|
||||||
|
|
||||||
|
var command = new CommandDefinition(sql, param, transaction: _transaction, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
return await conn.QuerySingleAsync<T>(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<Maybe<T>> TryQueryFirst<T>(string sql, object? param = null, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (_transaction.Connection is not { } conn)
|
||||||
|
throw new InvalidOperationException("Transaction connection cannot be null");
|
||||||
|
|
||||||
|
return await conn.QueryUnbufferedAsync<T>(sql, param, transaction: _transaction)
|
||||||
|
.FirstOrEmptyAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task Execute(string sql, object? param = null, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (_transaction.Connection is not { } conn)
|
||||||
|
throw new InvalidOperationException("Transaction connection cannot be null");
|
||||||
|
|
||||||
|
var command = new CommandDefinition(sql, param, transaction: _transaction, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
await conn.ExecuteAsync(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<T?> ExecuteScalar<T>(string sql, object? param = null, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (_transaction.Connection is not { } conn)
|
||||||
|
throw new InvalidOperationException("Transaction connection cannot be null");
|
||||||
|
|
||||||
|
var command = new CommandDefinition(sql, param, transaction: _transaction, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
return await conn.ExecuteScalarAsync<T>(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<DbDataReader> ExecuteReader(
|
||||||
|
string sql,
|
||||||
|
object? param = null,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (_transaction.Connection is not { } conn)
|
||||||
|
throw new InvalidOperationException("Transaction connection cannot be null");
|
||||||
|
|
||||||
|
var command = new CommandDefinition(sql, param, transaction: _transaction, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
return await conn.ExecuteReaderAsync(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task Commit(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _transaction.CommitAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task Rollback(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _transaction.RollbackAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
#pragma warning disable IDISP007
|
||||||
|
_transaction.Dispose();
|
||||||
|
#pragma warning restore IDISP007
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual async ValueTask DisposeAsyncCore()
|
||||||
|
{
|
||||||
|
#pragma warning disable IDISP007
|
||||||
|
await _transaction.DisposeAsync();
|
||||||
|
#pragma warning restore IDISP007
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await DisposeAsyncCore();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<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>2.1.0</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="..\Base\Base.csproj" />
|
||||||
|
<ProjectReference Include="..\FruityFoundation.DataAccess.Abstractions\FruityFoundation.DataAccess.Abstractions.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
134
FruityFoundation.DataAccess.Core/NonTransactionalDbConnection.cs
Normal file
134
FruityFoundation.DataAccess.Core/NonTransactionalDbConnection.cs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Dapper;
|
||||||
|
using FruityFoundation.Base.Structures;
|
||||||
|
using FruityFoundation.DataAccess.Abstractions;
|
||||||
|
|
||||||
|
namespace FruityFoundation.DataAccess.Core;
|
||||||
|
|
||||||
|
// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
|
||||||
|
public class NonTransactionalDbConnection<TConnectionType> : INonTransactionalDbConnection<TConnectionType>
|
||||||
|
where TConnectionType : ConnectionType
|
||||||
|
{
|
||||||
|
private readonly DbConnection _connection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// C'tor
|
||||||
|
/// </summary>
|
||||||
|
public NonTransactionalDbConnection(DbConnection connection)
|
||||||
|
{
|
||||||
|
_connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<IEnumerable<T>> Query<T>(
|
||||||
|
string sql,
|
||||||
|
object? param = null,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
) => await _connection.QueryAsync<T>(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async IAsyncEnumerable<T> QueryUnbuffered<T>(
|
||||||
|
string sql,
|
||||||
|
object? param = null,
|
||||||
|
[EnumeratorCancellation] CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var query = _connection.QueryUnbufferedAsync<T>(sql, param, transaction: null)
|
||||||
|
.WithCancellation(cancellationToken);
|
||||||
|
|
||||||
|
await foreach (var item in query)
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<T> QuerySingle<T>(
|
||||||
|
string sql,
|
||||||
|
object? param = null,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
) => await _connection.QuerySingleAsync<T>(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<Maybe<T>> TryQueryFirst<T>(
|
||||||
|
string sql,
|
||||||
|
object? param = null,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
) =>
|
||||||
|
await _connection.QueryUnbufferedAsync<T>(sql, param, transaction: null)
|
||||||
|
.FirstOrEmptyAsync(cancellationToken);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task Execute(
|
||||||
|
string sql,
|
||||||
|
object? param = null,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
) => await _connection.ExecuteAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<T?> ExecuteScalar<T>(
|
||||||
|
string sql,
|
||||||
|
object? param = null,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
) => await _connection.ExecuteScalarAsync<T>(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<DbDataReader> ExecuteReader(
|
||||||
|
string sql,
|
||||||
|
object? param = null,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
) => await _connection.ExecuteReaderAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<IDatabaseTransactionConnection<TConnectionType>> CreateTransaction(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!_connection.State.HasFlag(ConnectionState.Open))
|
||||||
|
await _connection.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
|
var tx = await _connection.BeginTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new DbTransaction<TConnectionType>(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<IDatabaseTransactionConnection<TConnectionType>> CreateTransaction(IsolationLevel isolationLevel, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!_connection.State.HasFlag(ConnectionState.Open))
|
||||||
|
await _connection.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
|
var tx = await _connection.BeginTransactionAsync(isolationLevel, cancellationToken);
|
||||||
|
|
||||||
|
return new DbTransaction<TConnectionType>(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
#pragma warning disable IDISP007
|
||||||
|
_connection.Dispose();
|
||||||
|
#pragma warning restore IDISP007
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual async ValueTask DisposeAsyncCore()
|
||||||
|
{
|
||||||
|
#pragma warning disable IDISP007
|
||||||
|
await _connection.DisposeAsync();
|
||||||
|
#pragma warning restore IDISP007
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await DisposeAsyncCore();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>2.1.0</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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="ReadWriteDbTests.fs" />
|
||||||
|
<Compile Include="ReadOnlyDbTests.fs" />
|
||||||
|
<Compile Include="Program.fs"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||||
|
<PackageReference Include="FakeItEasy" Version="8.3.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"/>
|
||||||
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<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,4 @@
|
||||||
|
module Program =
|
||||||
|
|
||||||
|
[<EntryPoint>]
|
||||||
|
let main _ = 0
|
|
@ -0,0 +1,249 @@
|
||||||
|
module FruityFoundation.Tests.DataAccess.Abstractions.FSharp.ReadOnlyDbTests
|
||||||
|
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Threading
|
||||||
|
open FSharp.Control
|
||||||
|
open FakeItEasy
|
||||||
|
open FruityFoundation.DataAccess.Abstractions
|
||||||
|
open FruityFoundation.DataAccess.Abstractions.FSharp
|
||||||
|
open NUnit.Framework
|
||||||
|
|
||||||
|
let fakeDbConnection : IDatabaseConnection<ReadOnly> = A.Fake<IDatabaseConnection<ReadOnly>> ()
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Query_Calls_IDatabaseConnection_Query_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.query fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Query(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Query_Calls_IDatabaseConnection_Query_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.query fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Query(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms)
|
||||||
|
||> ReadOnlyDb.queryUnbuffered fakeDbConnection CancellationToken.None
|
||||||
|
|> TaskSeq.toArrayAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms)
|
||||||
|
||> ReadOnlyDb.queryUnbuffered fakeDbConnection CancellationToken.None
|
||||||
|
|> TaskSeq.toArrayAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.querySingle fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QuerySingle(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.querySingle fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QuerySingle(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Execute_Calls_IDatabaseConnection_Execute_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.execute fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Execute(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Execute_Calls_IDatabaseConnection_Execute_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.execute fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Execute(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.executeScalar fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.executeScalar fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.executeReader fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.executeReader fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
|
@ -0,0 +1,249 @@
|
||||||
|
module FruityFoundation.Tests.DataAccess.Abstractions.FSharp.ReadWriteDbTests
|
||||||
|
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Threading
|
||||||
|
open FSharp.Control
|
||||||
|
open FakeItEasy
|
||||||
|
open FruityFoundation.DataAccess.Abstractions
|
||||||
|
open FruityFoundation.DataAccess.Abstractions.FSharp
|
||||||
|
open NUnit.Framework
|
||||||
|
|
||||||
|
let fakeDbConnection : IDatabaseConnection<ReadWrite> = A.Fake<IDatabaseConnection<ReadWrite>> ()
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Query_Calls_IDatabaseConnection_Query_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.query fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Query(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Query_Calls_IDatabaseConnection_Query_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.query fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Query(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms)
|
||||||
|
||> ReadWriteDb.queryUnbuffered fakeDbConnection CancellationToken.None
|
||||||
|
|> TaskSeq.toArrayAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms)
|
||||||
|
||> ReadWriteDb.queryUnbuffered fakeDbConnection CancellationToken.None
|
||||||
|
|> TaskSeq.toArrayAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.querySingle fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QuerySingle(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.querySingle fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QuerySingle(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Execute_Calls_IDatabaseConnection_Execute_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.execute fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Execute(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Execute_Calls_IDatabaseConnection_Execute_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.execute fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Execute(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.executeScalar fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.executeScalar fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.executeReader fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.executeReader fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
|
@ -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>>());
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,16 +19,18 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="FsUnit" Version="6.0.0" />
|
<PackageReference Include="FsUnit" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||||
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
|
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||||
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Base\Base.csproj" />
|
||||||
<ProjectReference Include="..\FsBase\FsBase.fsproj" />
|
<ProjectReference Include="..\FsBase\FsBase.fsproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||||
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
|
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
@ -28,6 +28,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Base\Base.csproj" />
|
||||||
<ProjectReference Include="..\FsBase\FsBase.fsproj" />
|
<ProjectReference Include="..\FsBase\FsBase.fsproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,20 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FruityFoundation.Tests.FsBa
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.Tests.FsBaseInterop", "FruityFoundation.Tests.FsBaseInterop\FruityFoundation.Tests.FsBaseInterop.csproj", "{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.Tests.FsBaseInterop", "FruityFoundation.Tests.FsBaseInterop\FruityFoundation.Tests.FsBaseInterop.csproj", "{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataAccess", "DataAccess", "{5C3A014A-7931-4A36-95F0-5EFE15AB06A3}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess.Abstractions", "FruityFoundation.DataAccess.Abstractions\FruityFoundation.DataAccess.Abstractions.csproj", "{C003E247-C62E-4830-94E4-F274D8466A5C}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess.Core", "FruityFoundation.DataAccess.Core\FruityFoundation.DataAccess.Core.csproj", "{B65527CC-218A-4EA3-93DC-985713B5DFF4}"
|
||||||
|
EndProject
|
||||||
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FruityFoundation.DataAccess.Abstractions.FSharp", "FruityFoundation.DataAccess.Abstractions.FSharp\FruityFoundation.DataAccess.Abstractions.FSharp.fsproj", "{B2E0156B-B631-4C80-A129-59472D2D0A77}"
|
||||||
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -44,10 +58,40 @@ Global
|
||||||
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C003E247-C62E-4830-94E4-F274D8466A5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C003E247-C62E-4830-94E4-F274D8466A5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C003E247-C62E-4830-94E4-F274D8466A5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C003E247-C62E-4830-94E4-F274D8466A5C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B2E0156B-B631-4C80-A129-59472D2D0A77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B2E0156B-B631-4C80-A129-59472D2D0A77}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B2E0156B-B631-4C80-A129-59472D2D0A77}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B2E0156B-B631-4C80-A129-59472D2D0A77}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{27F4FB64-7A51-4315-BDAA-6EE07736C976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{50A75644-A1C3-4495-9DEB-DBB12D9334B5} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
{50A75644-A1C3-4495-9DEB-DBB12D9334B5} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
||||||
{EBDC3640-4E47-43FE-BF0D-4BFFD07FE2EF} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
{EBDC3640-4E47-43FE-BF0D-4BFFD07FE2EF} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
||||||
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
||||||
|
{C003E247-C62E-4830-94E4-F274D8466A5C} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
|
||||||
|
{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
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<PackageId>FruityFoundation.FsBase</PackageId>
|
<PackageId>FruityFoundation.FsBase</PackageId>
|
||||||
<Authors>Kyle Ratti</Authors>
|
<Authors>Kyle Ratti</Authors>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<Version>1.9.0</Version>
|
<Version>2.1.0</Version>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>8.0</LangVersion>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Update="FSharp.Core" Version="8.0.200" />
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user