From 1f686b7513ac46962fba469e5a550ce34b9995e1 Mon Sep 17 00:00:00 2001 From: Chris Philips Date: Wed, 30 Aug 2023 00:31:19 -0700 Subject: [PATCH] Feat: EntityFramework Snapshots (#108) * bugfix: don't publish transaction if it wasn't committed to the database * feat: EntityFramework snapshots * test: EntityFramework snapshots * fix: remove test code * refactor: provide a dbContext which handles the boilerplate * fix(test): update tests to match new structure * refactor: use ValueConverter for the ValueObjects * refactor: make choosing table names mandatory * refactor: use ValueObjects instead of Raw Values to keep usage consistent * refactor: don't require a named DbSet property prevents using one DbContext for multiple snapshots (and therefore prevents related snapshots) * refactor: use real transactions for entity framework snapshots in test mode * refactor: update snapshot reference if one already exists * chore: coverage for TimeStamp converter * chore: exclude ToString overrides from coverage * chore: exclude functioning CommitTransaction from tests Tests should be running in TestMode, and should not actually commit data. * chore: remove unused method * fix: swap order of methods * fix: working delete method * Update global.json * chore: include debug logs * refactor: SnapshotReference _has one_ Snapshot instead of _owns_ snapshot * nit: explicit OnDelete behavior * fix: IEntityFrameworkSnapshot belongs in its own file, and in the Snapshots namespace * refactor: better snapshot cleanup 1. delete snapshot when all references deleted 2. delete snapshot when all references are changed to point to a different snapshot 3. provide an option to opt-out of cleanup behavior * refactor: force a constructor instead of using IDbContextFactory this allows for a single DbContext to have multiple connection configurations (e.g., read-only connection string) * fix: somehow this wasn't on this branch originally * refactor: rename bits and bobs IEntityDbContext, EntityDbContextBase does not explicitly depend on involving snapshots * refactor: IEntityDbContextFactory allow the end user to create DbContexts using just session option names * Empty Commit --- EntityDb.sln | 9 +- global.json | 2 +- .../Properties/AssemblyInfo.cs | 1 + .../Converters/IdConverter.cs | 15 ++ .../Converters/TimeStampConverter.cs | 15 ++ .../Converters/VersionNumberConverter.cs | 15 ++ .../DbContexts/EntityDbContextBase.cs | 27 +++ .../DbContexts/EntityDbContextFactory.cs | 26 +++ .../DbContexts/IEntityDbContext.cs | 19 ++ .../DbContexts/IEntityDbContextFactory.cs | 20 ++ .../EntityDb.EntityFramework.csproj | 17 ++ .../Extensions/ServiceCollectionExtensions.cs | 48 +++++ ...bTransactionRepositoryFactoryExtensions.cs | 18 ++ .../Predicates/PredicateBuilder.cs | 41 ++++ .../Sessions/EntityFrameworkSession.cs | 195 ++++++++++++++++++ .../EntityFrameworkSnapshotSessionOptions.cs | 39 ++++ .../Sessions/IEntityFrameworkSession.cs | 22 ++ .../TestModeEntityFrameworkSession.cs | 48 +++++ .../EntityFrameworkSnapshotRepository.cs | 61 ++++++ ...ntityFrameworkSnapshotRepositoryFactory.cs | 53 +++++ .../Snapshots/IEntityFrameworkSnapshot.cs | 17 ++ ...ntityFrameworkSnapshotRepositoryFactory.cs | 24 +++ .../Snapshots/SnapshotReference.cs | 40 ++++ .../SnapshotReferenceTypeConfiguration.cs | 46 +++++ ...ntityFrameworkSnapshotRepositoryFactory.cs | 89 ++++++++ .../packages.lock.json | 137 ++++++++++++ .../MongoDbTransactionSessionOptions.cs | 2 + .../Sessions/RedisSnapshotSessionOptions.cs | 2 + .../SqlDbTransactionSessionOptions.cs | 2 + .../Entities/EntityTests.cs | 16 +- .../EntityDb.Common.Tests.csproj | 2 + .../DbContexts/GenericDbContext.cs | 55 +++++ .../Implementations/Entities/TestEntity.cs | 34 ++- .../Projections/OneToOneProjection.cs | 39 +++- .../Seeders/TransactionSeeder.cs | 4 +- .../Snapshots/ISnapshotWithTestLogic.cs | 6 +- .../Projections/ProjectionsTests.cs | 4 +- .../Snapshots/SnapshotTests.cs | 59 +++++- test/EntityDb.Common.Tests/TestsBase.cs | 69 ++++++- ...ntitySnapshotTransactionSubscriberTests.cs | 4 +- .../Transactions/TransactionTests.cs | 2 +- test/EntityDb.Common.Tests/packages.lock.json | 120 ++++++++--- .../EntityDb.MongoDb.Tests/packages.lock.json | 121 ++++++++--- test/EntityDb.Mvc.Tests/packages.lock.json | 121 ++++++++--- test/EntityDb.Redis.Tests/packages.lock.json | 121 ++++++++--- 45 files changed, 1670 insertions(+), 157 deletions(-) create mode 100644 src/EntityDb.EntityFramework/Converters/IdConverter.cs create mode 100644 src/EntityDb.EntityFramework/Converters/TimeStampConverter.cs create mode 100644 src/EntityDb.EntityFramework/Converters/VersionNumberConverter.cs create mode 100644 src/EntityDb.EntityFramework/DbContexts/EntityDbContextBase.cs create mode 100644 src/EntityDb.EntityFramework/DbContexts/EntityDbContextFactory.cs create mode 100644 src/EntityDb.EntityFramework/DbContexts/IEntityDbContext.cs create mode 100644 src/EntityDb.EntityFramework/DbContexts/IEntityDbContextFactory.cs create mode 100644 src/EntityDb.EntityFramework/EntityDb.EntityFramework.csproj create mode 100644 src/EntityDb.EntityFramework/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/EntityDb.EntityFramework/Extensions/TestModeMongoDbTransactionRepositoryFactoryExtensions.cs create mode 100644 src/EntityDb.EntityFramework/Predicates/PredicateBuilder.cs create mode 100644 src/EntityDb.EntityFramework/Sessions/EntityFrameworkSession.cs create mode 100644 src/EntityDb.EntityFramework/Sessions/EntityFrameworkSnapshotSessionOptions.cs create mode 100644 src/EntityDb.EntityFramework/Sessions/IEntityFrameworkSession.cs create mode 100644 src/EntityDb.EntityFramework/Sessions/TestModeEntityFrameworkSession.cs create mode 100644 src/EntityDb.EntityFramework/Snapshots/EntityFrameworkSnapshotRepository.cs create mode 100644 src/EntityDb.EntityFramework/Snapshots/EntityFrameworkSnapshotRepositoryFactory.cs create mode 100644 src/EntityDb.EntityFramework/Snapshots/IEntityFrameworkSnapshot.cs create mode 100644 src/EntityDb.EntityFramework/Snapshots/IEntityFrameworkSnapshotRepositoryFactory.cs create mode 100644 src/EntityDb.EntityFramework/Snapshots/SnapshotReference.cs create mode 100644 src/EntityDb.EntityFramework/Snapshots/SnapshotReferenceTypeConfiguration.cs create mode 100644 src/EntityDb.EntityFramework/Snapshots/TestModeEntityFrameworkSnapshotRepositoryFactory.cs create mode 100644 src/EntityDb.EntityFramework/packages.lock.json create mode 100644 test/EntityDb.Common.Tests/Implementations/DbContexts/GenericDbContext.cs diff --git a/EntityDb.sln b/EntityDb.sln index 3b2837a0..3f73be88 100644 --- a/EntityDb.sln +++ b/EntityDb.sln @@ -47,7 +47,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityDb.SqlDb", "src\Entit EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityDb.Json", "src\EntityDb.Json\EntityDb.Json.csproj", "{4936FFE0-98E5-43A2-89C9-0415A13CAA9B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityDb.Provisioner", "src\EntityDb.Provisioner\EntityDb.Provisioner.csproj", "{26FCDB9D-0DE3-4BB9-858D-3E2C3EF763E3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityDb.Provisioner", "src\EntityDb.Provisioner\EntityDb.Provisioner.csproj", "{26FCDB9D-0DE3-4BB9-858D-3E2C3EF763E3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityDb.EntityFramework", "src\EntityDb.EntityFramework\EntityDb.EntityFramework.csproj", "{199606BF-6283-4684-A224-4DA7E80D8F45}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -115,6 +117,10 @@ Global {26FCDB9D-0DE3-4BB9-858D-3E2C3EF763E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {26FCDB9D-0DE3-4BB9-858D-3E2C3EF763E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {26FCDB9D-0DE3-4BB9-858D-3E2C3EF763E3}.Release|Any CPU.Build.0 = Release|Any CPU + {199606BF-6283-4684-A224-4DA7E80D8F45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {199606BF-6283-4684-A224-4DA7E80D8F45}.Debug|Any CPU.Build.0 = Debug|Any CPU + {199606BF-6283-4684-A224-4DA7E80D8F45}.Release|Any CPU.ActiveCfg = Release|Any CPU + {199606BF-6283-4684-A224-4DA7E80D8F45}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -135,6 +141,7 @@ Global {F2491666-31D1-47B5-A493-F25E167D1FDF} = {ABACFBCC-B59F-4616-B6CC-99C37AEC8960} {4936FFE0-98E5-43A2-89C9-0415A13CAA9B} = {ABACFBCC-B59F-4616-B6CC-99C37AEC8960} {26FCDB9D-0DE3-4BB9-858D-3E2C3EF763E3} = {ABACFBCC-B59F-4616-B6CC-99C37AEC8960} + {199606BF-6283-4684-A224-4DA7E80D8F45} = {ABACFBCC-B59F-4616-B6CC-99C37AEC8960} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E9D288EE-9351-4018-ABE8-B0968AEB0465} diff --git a/global.json b/global.json index 32cf95ef..ceb31304 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.100", + "version": "7.0.201", "allowPrerelease": false, "rollForward": "disable" } diff --git a/src/EntityDb.Common/Properties/AssemblyInfo.cs b/src/EntityDb.Common/Properties/AssemblyInfo.cs index 4061b50f..912c98c8 100644 --- a/src/EntityDb.Common/Properties/AssemblyInfo.cs +++ b/src/EntityDb.Common/Properties/AssemblyInfo.cs @@ -3,6 +3,7 @@ // src [assembly: InternalsVisibleTo("EntityDb.SqlDb")] [assembly: InternalsVisibleTo("EntityDb.Npgsql")] +[assembly: InternalsVisibleTo("EntityDb.EntityFramework")] [assembly: InternalsVisibleTo("EntityDb.InMemory")] [assembly: InternalsVisibleTo("EntityDb.MongoDb")] [assembly: InternalsVisibleTo("EntityDb.Provisioner")] diff --git a/src/EntityDb.EntityFramework/Converters/IdConverter.cs b/src/EntityDb.EntityFramework/Converters/IdConverter.cs new file mode 100644 index 00000000..441fcdc5 --- /dev/null +++ b/src/EntityDb.EntityFramework/Converters/IdConverter.cs @@ -0,0 +1,15 @@ +using EntityDb.Abstractions.ValueObjects; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using System.Linq.Expressions; + +namespace EntityDb.EntityFramework.Converters; + +internal class IdConverter : ValueConverter +{ + private static readonly Expression> IdToGuid = (id) => id.Value; + private static readonly Expression> GuidToId = (guid) => new Id(guid); + + public IdConverter() : base(IdToGuid, GuidToId, null) + { + } +} diff --git a/src/EntityDb.EntityFramework/Converters/TimeStampConverter.cs b/src/EntityDb.EntityFramework/Converters/TimeStampConverter.cs new file mode 100644 index 00000000..e5664e25 --- /dev/null +++ b/src/EntityDb.EntityFramework/Converters/TimeStampConverter.cs @@ -0,0 +1,15 @@ +using EntityDb.Abstractions.ValueObjects; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using System.Linq.Expressions; + +namespace EntityDb.EntityFramework.Converters; + +internal class TimeStampConverter : ValueConverter +{ + private static readonly Expression> TimeStampToDateTime = (timeStamp) => timeStamp.Value; + private static readonly Expression> DateTimeToTimeStamp = (dateTime) => new TimeStamp(dateTime); + + public TimeStampConverter() : base(TimeStampToDateTime, DateTimeToTimeStamp, null) + { + } +} diff --git a/src/EntityDb.EntityFramework/Converters/VersionNumberConverter.cs b/src/EntityDb.EntityFramework/Converters/VersionNumberConverter.cs new file mode 100644 index 00000000..84ef7145 --- /dev/null +++ b/src/EntityDb.EntityFramework/Converters/VersionNumberConverter.cs @@ -0,0 +1,15 @@ +using EntityDb.Abstractions.ValueObjects; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using System.Linq.Expressions; + +namespace EntityDb.EntityFramework.Converters; + +internal class VersionNumberConverter : ValueConverter +{ + private static readonly Expression> VersionNumberToUlong = (versionNumber) => versionNumber.Value; + private static readonly Expression> UlongToVersionNumber = (@ulong) => new VersionNumber(@ulong); + + public VersionNumberConverter() : base(VersionNumberToUlong, UlongToVersionNumber, null) + { + } +} diff --git a/src/EntityDb.EntityFramework/DbContexts/EntityDbContextBase.cs b/src/EntityDb.EntityFramework/DbContexts/EntityDbContextBase.cs new file mode 100644 index 00000000..3fb75b6f --- /dev/null +++ b/src/EntityDb.EntityFramework/DbContexts/EntityDbContextBase.cs @@ -0,0 +1,27 @@ +using EntityDb.Abstractions.ValueObjects; +using EntityDb.EntityFramework.Converters; +using Microsoft.EntityFrameworkCore; + +namespace EntityDb.EntityFramework.DbContexts; + +/// +/// A DbContext that adds basic converters for types defined in +/// +public abstract class EntityDbContextBase : DbContext +{ + /// + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder + .Properties() + .HaveConversion(); + + configurationBuilder + .Properties() + .HaveConversion(); + + configurationBuilder + .Properties() + .HaveConversion(); + } +} diff --git a/src/EntityDb.EntityFramework/DbContexts/EntityDbContextFactory.cs b/src/EntityDb.EntityFramework/DbContexts/EntityDbContextFactory.cs new file mode 100644 index 00000000..ca8d5471 --- /dev/null +++ b/src/EntityDb.EntityFramework/DbContexts/EntityDbContextFactory.cs @@ -0,0 +1,26 @@ +using EntityDb.EntityFramework.Sessions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace EntityDb.EntityFramework.DbContexts; + +internal class EntityDbContextFactory : IEntityDbContextFactory + where TDbContext : DbContext, IEntityDbContext +{ + private readonly IOptionsFactory _optionsFactory; + + public EntityDbContextFactory(IOptionsFactory optionsFactory) + { + _optionsFactory = optionsFactory; + } + + public TDbContext Create(string snapshotSessionOptionsName) + { + return TDbContext.Construct(_optionsFactory.Create(snapshotSessionOptionsName)); + } + + TDbContext IEntityDbContextFactory.Create(EntityFrameworkSnapshotSessionOptions snapshotSessionOptions) + { + return TDbContext.Construct(snapshotSessionOptions); + } +} diff --git a/src/EntityDb.EntityFramework/DbContexts/IEntityDbContext.cs b/src/EntityDb.EntityFramework/DbContexts/IEntityDbContext.cs new file mode 100644 index 00000000..4661b7a2 --- /dev/null +++ b/src/EntityDb.EntityFramework/DbContexts/IEntityDbContext.cs @@ -0,0 +1,19 @@ +using EntityDb.EntityFramework.Sessions; +using Microsoft.EntityFrameworkCore; + +namespace EntityDb.EntityFramework.DbContexts; + +/// +/// A type of a that can be used for EntityDb purposes. +/// +/// The type of the +public interface IEntityDbContext + where TDbContext : DbContext, IEntityDbContext +{ + /// + /// Returns a new that will be configured using . + /// + /// The options for the database + /// A new that will be configured using . + static abstract TDbContext Construct(EntityFrameworkSnapshotSessionOptions entityFrameworkSnapshotSessionOptions); +} diff --git a/src/EntityDb.EntityFramework/DbContexts/IEntityDbContextFactory.cs b/src/EntityDb.EntityFramework/DbContexts/IEntityDbContextFactory.cs new file mode 100644 index 00000000..4fba5f01 --- /dev/null +++ b/src/EntityDb.EntityFramework/DbContexts/IEntityDbContextFactory.cs @@ -0,0 +1,20 @@ +using EntityDb.EntityFramework.Sessions; +using Microsoft.EntityFrameworkCore; + +namespace EntityDb.EntityFramework.DbContexts; + +/// +/// Represents a type used to create instances of . +/// +/// The type of the . +public interface IEntityDbContextFactory +{ + internal TDbContext Create(EntityFrameworkSnapshotSessionOptions snapshotSessionOptions); + + /// + /// Create a new instance of . + /// + /// The agent's use case for the . + /// A new instance of . + TDbContext Create(string snapshotSessionOptionsName); +} diff --git a/src/EntityDb.EntityFramework/EntityDb.EntityFramework.csproj b/src/EntityDb.EntityFramework/EntityDb.EntityFramework.csproj new file mode 100644 index 00000000..2ec2f457 --- /dev/null +++ b/src/EntityDb.EntityFramework/EntityDb.EntityFramework.csproj @@ -0,0 +1,17 @@ + + + + + EntityDb EventSourcing DDD CQRS + An implementation of the EntityDb Snapshot Repository interface, specifically for Entity Framework. + + + + + + + + + + + diff --git a/src/EntityDb.EntityFramework/Extensions/ServiceCollectionExtensions.cs b/src/EntityDb.EntityFramework/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..b5ff49a8 --- /dev/null +++ b/src/EntityDb.EntityFramework/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,48 @@ +using EntityDb.Abstractions.Snapshots; +using EntityDb.Common.Extensions; +using EntityDb.EntityFramework.DbContexts; +using EntityDb.EntityFramework.Snapshots; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System.Diagnostics.CodeAnalysis; + +namespace EntityDb.EntityFramework.Extensions; + +/// +/// Extensions for service collections. +/// +[ExcludeFromCodeCoverage(Justification = "Don't need coverage for non-test mode.")] +public static class ServiceCollectionExtensions +{ + /// + /// Adds a production-ready implementation of to a service + /// collection. + /// + /// The type of the snapshot stored in the repository. + /// The type of the snapshot stored in the repository. + /// The service collection. + /// Modifies the behavior of the repository to accomodate tests. + public static void AddEntityFrameworkSnapshots(this IServiceCollection serviceCollection, bool testMode = false) + where TSnapshot : class, IEntityFrameworkSnapshot + where TDbContext : DbContext, IEntityDbContext + { + serviceCollection.Add, EntityDbContextFactory> + ( + testMode ? ServiceLifetime.Singleton : ServiceLifetime.Transient + ); + + serviceCollection.Add> + ( + testMode ? ServiceLifetime.Singleton : ServiceLifetime.Transient + ); + + serviceCollection.Add> + ( + testMode ? ServiceLifetime.Singleton : ServiceLifetime.Transient, + serviceProvider => serviceProvider + .GetRequiredService>() + .UseTestMode(serviceProvider, testMode) + ); + } +} diff --git a/src/EntityDb.EntityFramework/Extensions/TestModeMongoDbTransactionRepositoryFactoryExtensions.cs b/src/EntityDb.EntityFramework/Extensions/TestModeMongoDbTransactionRepositoryFactoryExtensions.cs new file mode 100644 index 00000000..629e685b --- /dev/null +++ b/src/EntityDb.EntityFramework/Extensions/TestModeMongoDbTransactionRepositoryFactoryExtensions.cs @@ -0,0 +1,18 @@ +using EntityDb.EntityFramework.Snapshots; +using System.Diagnostics.CodeAnalysis; + +namespace EntityDb.EntityFramework.Extensions; + +internal static class EntityFrameworkSnapshotRepositoryFactoryExtensions +{ + [ExcludeFromCodeCoverage(Justification = "Tests are only meant to run in test mode.")] + public static IEntityFrameworkSnapshotRepositoryFactory UseTestMode( + this IEntityFrameworkSnapshotRepositoryFactory entityFrameworkSnapshotRepositoyFactory, + IServiceProvider serviceProvider, + bool testMode) + { + return testMode + ? TestModeEntityFrameworkSnapshotRepositoryFactory.Create(serviceProvider, entityFrameworkSnapshotRepositoyFactory) + : entityFrameworkSnapshotRepositoyFactory; + } +} diff --git a/src/EntityDb.EntityFramework/Predicates/PredicateBuilder.cs b/src/EntityDb.EntityFramework/Predicates/PredicateBuilder.cs new file mode 100644 index 00000000..244d0de0 --- /dev/null +++ b/src/EntityDb.EntityFramework/Predicates/PredicateBuilder.cs @@ -0,0 +1,41 @@ +using EntityDb.EntityFramework.Snapshots; +using System.Linq.Expressions; + +namespace EntityDb.EntityFramework.Predicates; + +/// +/// Based on http://www.albahari.com/nutshell/predicatebuilder.aspx +/// +internal static class PredicateExpressionBuilder +{ + private static Expression> False() + { + return _ => false; + } + + private static Expression> Or + ( + Expression> left, + Expression> right + ) + { + return Expression.Lambda> + ( + Expression.OrElse + ( + left.Body, + Expression.Invoke(right, left.Parameters) + ), + left.Parameters + ); + } + + public static Expression> Or + ( + IEnumerable inputs, + Func>> mapper + ) + { + return inputs.Aggregate(False(), (predicate, input) => Or(predicate, mapper.Invoke(input))); + } +} diff --git a/src/EntityDb.EntityFramework/Sessions/EntityFrameworkSession.cs b/src/EntityDb.EntityFramework/Sessions/EntityFrameworkSession.cs new file mode 100644 index 00000000..c5d64f07 --- /dev/null +++ b/src/EntityDb.EntityFramework/Sessions/EntityFrameworkSession.cs @@ -0,0 +1,195 @@ +using EntityDb.Common.Disposables; +using EntityDb.Common.Exceptions; +using EntityDb.EntityFramework.Predicates; +using EntityDb.EntityFramework.Snapshots; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using Pointer = EntityDb.Abstractions.ValueObjects.Pointer; + +namespace EntityDb.EntityFramework.Sessions; + +internal class EntityFrameworkSession : DisposableResourceBaseClass, IEntityFrameworkSession + where TSnapshot : class, IEntityFrameworkSnapshot + where TDbContext : DbContext +{ + private readonly TDbContext _dbContext; + private readonly DbSet> _snapshotReferences; + private readonly DbSet _snapshots; + private readonly EntityFrameworkSnapshotSessionOptions _options; + + DbContext IEntityFrameworkSession.DbContext => _dbContext; + private IDbContextTransaction? Transaction { get; set; } + + public EntityFrameworkSession(TDbContext dbContext, EntityFrameworkSnapshotSessionOptions options) + { + _dbContext = dbContext; + _snapshotReferences = dbContext.Set>(); + _snapshots = dbContext.Set(); + _options = options; + } + + private static Expression, bool>> SnapshotPointerPredicate(Pointer snapshotPointer) + { + return snapshotReference => + snapshotReference.PointerId == snapshotPointer.Id && + snapshotReference.PointerVersionNumber == snapshotPointer.VersionNumber; + } + + private async Task ShouldDeleteSnapshot(SnapshotReference snapshotReference, CancellationToken cancellationToken) + { + if (_options.KeepSnapshotsWithoutSnapshotReferences) + { + return false; + } + + var otherSnapshotReferences = await _snapshotReferences + .Where + ( + relatedSnapshotReference => + relatedSnapshotReference.Id != snapshotReference.Id && + relatedSnapshotReference.SnapshotId == snapshotReference.SnapshotId && + relatedSnapshotReference.SnapshotVersionNumber == snapshotReference.SnapshotVersionNumber + ) + .AnyAsync(cancellationToken); + + return !otherSnapshotReferences; + } + + public async Task Delete(IEnumerable snapshotPointers, CancellationToken cancellationToken) + { + AssertNotReadOnly(); + + var snapshotReferences = await _snapshotReferences + .Include(snapshotReference => snapshotReference.Snapshot) + .Where(PredicateExpressionBuilder.Or(snapshotPointers, SnapshotPointerPredicate)) + .ToArrayAsync(cancellationToken); + + foreach (var snapshotReference in snapshotReferences) + { + if (await ShouldDeleteSnapshot(snapshotReference, cancellationToken)) + { + _snapshots.Remove(snapshotReference.Snapshot); + } + + _snapshotReferences.Remove(snapshotReference); + } + + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task Get(Pointer snapshotPointer, CancellationToken cancellationToken) + { + var snapshotReference = await _snapshotReferences + .Include(reference => reference.Snapshot) + .AsNoTracking() + .Where(SnapshotPointerPredicate(snapshotPointer)) + .SingleOrDefaultAsync(cancellationToken); + + return snapshotReference?.Snapshot; + } + + public async Task Upsert(Pointer snapshotPointer, TSnapshot snapshot, CancellationToken cancellationToken) + { + AssertNotReadOnly(); + + var sapshotExists = await _snapshots + .Where(snapshot.GetKeyPredicate()) + .AnyAsync(cancellationToken); + + if (!sapshotExists) + { + _snapshots.Add(snapshot); + } + + var previousSnapshotReference = await _snapshotReferences + .Include(snapshotReference => snapshotReference.Snapshot) + .Where(SnapshotPointerPredicate(snapshotPointer)) + .SingleOrDefaultAsync(cancellationToken); + + if (previousSnapshotReference != null) + { + if (await ShouldDeleteSnapshot(previousSnapshotReference, cancellationToken)) + { + _snapshots.Remove(previousSnapshotReference.Snapshot); + } + + previousSnapshotReference.SnapshotId = snapshot.GetId(); + previousSnapshotReference.SnapshotVersionNumber = snapshot.GetVersionNumber(); + previousSnapshotReference.Snapshot = snapshot; + } + else + { + _snapshotReferences.Add(new SnapshotReference + { + Id = Guid.NewGuid(), + PointerId = snapshotPointer.Id, + PointerVersionNumber = snapshotPointer.VersionNumber, + SnapshotId = snapshot.GetId(), + SnapshotVersionNumber = snapshot.GetVersionNumber(), + Snapshot = snapshot, + }); + } + + await _dbContext.SaveChangesAsync(cancellationToken); + } + + private void AssertNotReadOnly() + { + if (_options.ReadOnly) + { + throw new CannotWriteInReadOnlyModeException(); + } + } + + public static IEntityFrameworkSession Create + ( + IServiceProvider serviceProvider, + TDbContext dbContext, + EntityFrameworkSnapshotSessionOptions options + ) + { + return ActivatorUtilities.CreateInstance>(serviceProvider, dbContext, options); + } + + public async Task StartTransaction(CancellationToken cancellationToken) + { + Transaction = await _dbContext.Database.BeginTransactionAsync(cancellationToken); + } + + [ExcludeFromCodeCoverage(Justification = + "Tests should run with the Debug configuration, and should not execute this method.")] + public async Task CommitTransaction(CancellationToken cancellationToken) + { + if (Transaction != null) + { + await Transaction.CommitAsync(cancellationToken); + await Transaction.DisposeAsync(); + + Transaction = null; + } + } + + public async Task AbortTransaction(CancellationToken cancellationToken) + { + if (Transaction != null) + { + await Transaction.RollbackAsync(cancellationToken); + await Transaction.DisposeAsync(); + + Transaction = null; + } + } + + public IEntityFrameworkSession WithSnapshotSessionOptions(EntityFrameworkSnapshotSessionOptions snapshotSessionOptions) + { + return new EntityFrameworkSession(_dbContext, snapshotSessionOptions); + } + + public override ValueTask DisposeAsync() + { + return _dbContext.DisposeAsync(); + } +} diff --git a/src/EntityDb.EntityFramework/Sessions/EntityFrameworkSnapshotSessionOptions.cs b/src/EntityDb.EntityFramework/Sessions/EntityFrameworkSnapshotSessionOptions.cs new file mode 100644 index 00000000..2d28768c --- /dev/null +++ b/src/EntityDb.EntityFramework/Sessions/EntityFrameworkSnapshotSessionOptions.cs @@ -0,0 +1,39 @@ +using EntityDb.Abstractions.Snapshots; +using EntityDb.EntityFramework.Snapshots; +using System.Diagnostics.CodeAnalysis; + +namespace EntityDb.EntityFramework.Sessions; + +/// +/// Configuration options for the EntityFramework implementation of . +/// +public class EntityFrameworkSnapshotSessionOptions +{ + /// + /// This property is not used by the package. It only provides a convenient way to access + /// the connection string using IOptions, which does not appear to be a convienent thing + /// to do in vanilla Enitity Framework. + /// + public string ConnectionString { get; set; } = default!; + + /// + /// If true, indicates the agent only intends to execute queries. + /// + public bool ReadOnly { get; set; } + + /// + /// If false, a snapshot will be deleted if there are no + /// records pointing to the snapshot record. + /// + /// + /// You may consider setting this to true if there are other records which reference a specific snapshot. + /// + public bool KeepSnapshotsWithoutSnapshotReferences { get; set; } + + /// + [ExcludeFromCodeCoverage(Justification = "This is only overridden to make test names better.")] + public override string ToString() + { + return $"{nameof(EntityFrameworkSnapshotSessionOptions)}"; + } +} diff --git a/src/EntityDb.EntityFramework/Sessions/IEntityFrameworkSession.cs b/src/EntityDb.EntityFramework/Sessions/IEntityFrameworkSession.cs new file mode 100644 index 00000000..d81515c0 --- /dev/null +++ b/src/EntityDb.EntityFramework/Sessions/IEntityFrameworkSession.cs @@ -0,0 +1,22 @@ +using EntityDb.Abstractions.Disposables; +using EntityDb.Abstractions.ValueObjects; +using Microsoft.EntityFrameworkCore; + +namespace EntityDb.EntityFramework.Sessions; + +internal interface IEntityFrameworkSession : IDisposableResource +{ + internal DbContext DbContext { get; } + + IEntityFrameworkSession WithSnapshotSessionOptions(EntityFrameworkSnapshotSessionOptions snapshotSessionOptions); + + Task StartTransaction(CancellationToken cancellationToken); + Task CommitTransaction(CancellationToken cancellationToken); + Task AbortTransaction(CancellationToken cancellationToken); + + Task Upsert(Pointer snapshotPointer, TSnapshot snapshot, CancellationToken cancellationToken); + + Task Get(Pointer snapshotPointer, CancellationToken cancellationToken); + + Task Delete(IEnumerable snapshotPointers, CancellationToken cancellationToken); +} diff --git a/src/EntityDb.EntityFramework/Sessions/TestModeEntityFrameworkSession.cs b/src/EntityDb.EntityFramework/Sessions/TestModeEntityFrameworkSession.cs new file mode 100644 index 00000000..fc750f83 --- /dev/null +++ b/src/EntityDb.EntityFramework/Sessions/TestModeEntityFrameworkSession.cs @@ -0,0 +1,48 @@ +using EntityDb.Abstractions.ValueObjects; +using EntityDb.Common.Disposables; +using Microsoft.EntityFrameworkCore; + +namespace EntityDb.EntityFramework.Sessions; + +internal record TestModeEntityFrameworkSession(IEntityFrameworkSession EntityFrameworkSession) : DisposableResourceBaseRecord, IEntityFrameworkSession +{ + DbContext IEntityFrameworkSession.DbContext => EntityFrameworkSession.DbContext; + + public Task StartTransaction(CancellationToken cancellationToken) + { + // Test Mode Transactions are started in the Test Mode Repository Factory + return Task.CompletedTask; + } + + public Task CommitTransaction(CancellationToken cancellationToken) + { + // Test Mode Transactions are never committed + return Task.CompletedTask; + } + + public Task AbortTransaction(CancellationToken cancellationToken) + { + // Test Mode Transactions are aborted in the Test Mode Repository Factory + return Task.CompletedTask; + } + + public Task Upsert(Pointer snapshotPointer, TSnapshot snapshot, CancellationToken cancellationToken) + { + return EntityFrameworkSession.Upsert(snapshotPointer, snapshot, cancellationToken); + } + + public Task Get(Pointer snapshotPointer, CancellationToken cancellationToken) + { + return EntityFrameworkSession.Get(snapshotPointer, cancellationToken); + } + + public Task Delete(IEnumerable snapshotPointers, CancellationToken cancellationToken) + { + return EntityFrameworkSession.Delete(snapshotPointers, cancellationToken); + } + + public IEntityFrameworkSession WithSnapshotSessionOptions(EntityFrameworkSnapshotSessionOptions snapshotSessionOptions) + { + return this with { EntityFrameworkSession = EntityFrameworkSession.WithSnapshotSessionOptions(snapshotSessionOptions) }; + } +} diff --git a/src/EntityDb.EntityFramework/Snapshots/EntityFrameworkSnapshotRepository.cs b/src/EntityDb.EntityFramework/Snapshots/EntityFrameworkSnapshotRepository.cs new file mode 100644 index 00000000..94e7db08 --- /dev/null +++ b/src/EntityDb.EntityFramework/Snapshots/EntityFrameworkSnapshotRepository.cs @@ -0,0 +1,61 @@ +using EntityDb.Abstractions.Snapshots; +using EntityDb.Abstractions.ValueObjects; +using EntityDb.Common.Disposables; +using EntityDb.EntityFramework.Sessions; + +namespace EntityDb.EntityFramework.Snapshots; + +internal class EntityFrameworkSnapshotRepository : DisposableResourceBaseClass, ISnapshotRepository +{ + private readonly IEntityFrameworkSession _entityFrameworkSession; + + public EntityFrameworkSnapshotRepository(IEntityFrameworkSession entityFrameworkSession) + { + _entityFrameworkSession = entityFrameworkSession; + } + + public async Task DeleteSnapshots(Pointer[] snapshotPointers, CancellationToken cancellationToken = default) + { + try + { + await _entityFrameworkSession.StartTransaction(cancellationToken); + + await _entityFrameworkSession.Delete(snapshotPointers, cancellationToken); + + await _entityFrameworkSession.CommitTransaction(cancellationToken); + + return true; + } + catch + { + await _entityFrameworkSession.AbortTransaction(cancellationToken); + + throw; + } + } + + public Task GetSnapshotOrDefault(Pointer snapshotPointer, CancellationToken cancellationToken = default) + { + return _entityFrameworkSession.Get(snapshotPointer, cancellationToken); + } + + public async Task PutSnapshot(Pointer snapshotPointer, TSnapshot snapshot, CancellationToken cancellationToken = default) + { + try + { + await _entityFrameworkSession.StartTransaction(cancellationToken); + + await _entityFrameworkSession.Upsert(snapshotPointer, snapshot, cancellationToken); + + await _entityFrameworkSession.CommitTransaction(cancellationToken); + + return true; + } + catch + { + await _entityFrameworkSession.AbortTransaction(cancellationToken); + + throw; + } + } +} diff --git a/src/EntityDb.EntityFramework/Snapshots/EntityFrameworkSnapshotRepositoryFactory.cs b/src/EntityDb.EntityFramework/Snapshots/EntityFrameworkSnapshotRepositoryFactory.cs new file mode 100644 index 00000000..4c0c1254 --- /dev/null +++ b/src/EntityDb.EntityFramework/Snapshots/EntityFrameworkSnapshotRepositoryFactory.cs @@ -0,0 +1,53 @@ +using EntityDb.Abstractions.Snapshots; +using EntityDb.Common.Disposables; +using EntityDb.Common.Snapshots; +using EntityDb.EntityFramework.DbContexts; +using EntityDb.EntityFramework.Sessions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace EntityDb.EntityFramework.Snapshots; + +internal class EntityFrameworkSnapshotRepositoryFactory : DisposableResourceBaseClass, + IEntityFrameworkSnapshotRepositoryFactory + where TSnapshot : class, IEntityFrameworkSnapshot + where TDbContext : DbContext, IEntityDbContext +{ + private readonly IServiceProvider _serviceProvider; + private readonly IOptionsFactory _optionsFactory; + private readonly IEntityDbContextFactory _dbContextFactory; + + public EntityFrameworkSnapshotRepositoryFactory + ( + IServiceProvider serviceProvider, + IOptionsFactory optionsFactory, + IEntityDbContextFactory dbContextFactory + ) + { + _serviceProvider = serviceProvider; + _optionsFactory = optionsFactory; + _dbContextFactory = dbContextFactory; + } + + public ISnapshotRepository CreateRepository(IEntityFrameworkSession entityFrameworkSession) + { + var entityFrameworkSnapshotRepository = new EntityFrameworkSnapshotRepository + ( + entityFrameworkSession + ); + + return TryCatchSnapshotRepository.Create(_serviceProvider, entityFrameworkSnapshotRepository); + } + + public Task> CreateSession(EntityFrameworkSnapshotSessionOptions options, CancellationToken cancellationToken) + { + var dbContext = _dbContextFactory.Create(options); + + return Task.FromResult(EntityFrameworkSession.Create(_serviceProvider, dbContext, options)); + } + + public EntityFrameworkSnapshotSessionOptions GetSessionOptions(string snapshotSessionOptionsName) + { + return _optionsFactory.Create(snapshotSessionOptionsName); + } +} diff --git a/src/EntityDb.EntityFramework/Snapshots/IEntityFrameworkSnapshot.cs b/src/EntityDb.EntityFramework/Snapshots/IEntityFrameworkSnapshot.cs new file mode 100644 index 00000000..65a76736 --- /dev/null +++ b/src/EntityDb.EntityFramework/Snapshots/IEntityFrameworkSnapshot.cs @@ -0,0 +1,17 @@ +using EntityDb.Common.Snapshots; +using System.Linq.Expressions; + +namespace EntityDb.EntityFramework.Snapshots; + +/// +/// Indicates that a snapshot is compatible with EntityDb.EntityFramework implementations. +/// +/// The type of the snapshot +public interface IEntityFrameworkSnapshot : ISnapshot +{ + /// + /// Returns an expression of a function that can be used to check if a copy of this record already exists. + /// + /// An expression of a function that can be used to check if a copy of this record already exists. + Expression> GetKeyPredicate(); +} diff --git a/src/EntityDb.EntityFramework/Snapshots/IEntityFrameworkSnapshotRepositoryFactory.cs b/src/EntityDb.EntityFramework/Snapshots/IEntityFrameworkSnapshotRepositoryFactory.cs new file mode 100644 index 00000000..9777116e --- /dev/null +++ b/src/EntityDb.EntityFramework/Snapshots/IEntityFrameworkSnapshotRepositoryFactory.cs @@ -0,0 +1,24 @@ +using EntityDb.Abstractions.Snapshots; +using EntityDb.EntityFramework.Sessions; + +namespace EntityDb.EntityFramework.Snapshots; + +internal interface IEntityFrameworkSnapshotRepositoryFactory : ISnapshotRepositoryFactory +{ + async Task> ISnapshotRepositoryFactory.CreateRepository( + string snapshotSessionOptionsName, CancellationToken cancellationToken) + { + var options = GetSessionOptions(snapshotSessionOptionsName); + + var entityFrameworkSession = await CreateSession(options, cancellationToken); + + return CreateRepository(entityFrameworkSession); + } + + EntityFrameworkSnapshotSessionOptions GetSessionOptions(string snapshotSessionOptionsName); + + Task> CreateSession(EntityFrameworkSnapshotSessionOptions options, + CancellationToken cancellationToken); + + ISnapshotRepository CreateRepository(IEntityFrameworkSession entityFrameworkSession); +} diff --git a/src/EntityDb.EntityFramework/Snapshots/SnapshotReference.cs b/src/EntityDb.EntityFramework/Snapshots/SnapshotReference.cs new file mode 100644 index 00000000..d547183b --- /dev/null +++ b/src/EntityDb.EntityFramework/Snapshots/SnapshotReference.cs @@ -0,0 +1,40 @@ +using EntityDb.Abstractions.ValueObjects; + +namespace EntityDb.EntityFramework.Snapshots; + +/// +/// Represents a unique snapshot and its pointer. +/// +/// +public class SnapshotReference +{ + /// + /// Te ID of the Reference record. + /// + public required Guid Id { get; init; } + + /// + /// The ID of the Snapshot Pointer. + /// + public required Id PointerId { get; init; } + + /// + /// The Version Number of the Snapshot Pointer. + /// + public required VersionNumber PointerVersionNumber { get; init; } + + /// + /// The Id of the Snapshot. + /// + public required Id SnapshotId { get; set; } + + /// + /// The VersionNumber of the Snapshot. + /// + public required VersionNumber SnapshotVersionNumber { get; set; } + + /// + /// The Snapshot. + /// + public required TSnapshot Snapshot { get; set; } +} diff --git a/src/EntityDb.EntityFramework/Snapshots/SnapshotReferenceTypeConfiguration.cs b/src/EntityDb.EntityFramework/Snapshots/SnapshotReferenceTypeConfiguration.cs new file mode 100644 index 00000000..833b58b2 --- /dev/null +++ b/src/EntityDb.EntityFramework/Snapshots/SnapshotReferenceTypeConfiguration.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace EntityDb.EntityFramework.Snapshots; + +/// +public class SnapshotReferenceTypeConfiguration : IEntityTypeConfiguration> + where TSnapshot : class +{ + private readonly string _snapshotReferencesTableName; + + /// + /// Configure the napshot Reference Type. + /// + /// The name of the table for snapshot references. + public SnapshotReferenceTypeConfiguration(string snapshotReferencesTableName) + { + _snapshotReferencesTableName = snapshotReferencesTableName; + } + + /// + public virtual void Configure(EntityTypeBuilder> snapshotReferenceBuilder) + { + snapshotReferenceBuilder.ToTable(_snapshotReferencesTableName); + + snapshotReferenceBuilder + .HasKey(snapshotReference => snapshotReference.Id); + + snapshotReferenceBuilder + .HasAlternateKey(snapshotReference => new + { + snapshotReference.PointerId, + snapshotReference.PointerVersionNumber + }); + + snapshotReferenceBuilder + .HasOne(snapshotReference => snapshotReference.Snapshot) + .WithMany() + .HasForeignKey(snapshotReference => new + { + snapshotReference.SnapshotId, + snapshotReference.SnapshotVersionNumber, + }) + .OnDelete(DeleteBehavior.Cascade); + } +} diff --git a/src/EntityDb.EntityFramework/Snapshots/TestModeEntityFrameworkSnapshotRepositoryFactory.cs b/src/EntityDb.EntityFramework/Snapshots/TestModeEntityFrameworkSnapshotRepositoryFactory.cs new file mode 100644 index 00000000..f29ca43b --- /dev/null +++ b/src/EntityDb.EntityFramework/Snapshots/TestModeEntityFrameworkSnapshotRepositoryFactory.cs @@ -0,0 +1,89 @@ +using EntityDb.Abstractions.Snapshots; +using EntityDb.Common.Disposables; +using EntityDb.EntityFramework.Sessions; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace EntityDb.EntityFramework.Snapshots; + +internal class TestModeEntityFrameworkSnapshotRepositoryFactory : DisposableResourceBaseClass, IEntityFrameworkSnapshotRepositoryFactory +{ + private readonly ILogger> _logger; + private readonly IEntityFrameworkSnapshotRepositoryFactory _entityFrameworkSnapshotRepositoryFactory; + + private (IEntityFrameworkSession Normal, TestModeEntityFrameworkSession TestMode)? _sessions; + + public TestModeEntityFrameworkSnapshotRepositoryFactory + ( + ILogger> logger, + IEntityFrameworkSnapshotRepositoryFactory entityFrameworkSnapshotRepositoryFactory + ) + { + _logger = logger; + _entityFrameworkSnapshotRepositoryFactory = entityFrameworkSnapshotRepositoryFactory; + } + + public ISnapshotRepository CreateRepository(IEntityFrameworkSession entityFrameworkSession) + { + return _entityFrameworkSnapshotRepositoryFactory.CreateRepository(entityFrameworkSession); + } + + public async Task> CreateSession(EntityFrameworkSnapshotSessionOptions options, + CancellationToken cancellationToken) + { + if (_sessions.HasValue) + { + return _sessions.Value.TestMode + .WithSnapshotSessionOptions(options); + } + + var normalOptions = new EntityFrameworkSnapshotSessionOptions + { + ConnectionString = options.ConnectionString, + KeepSnapshotsWithoutSnapshotReferences = options.KeepSnapshotsWithoutSnapshotReferences, + }; + + var normalSession = await _entityFrameworkSnapshotRepositoryFactory.CreateSession(normalOptions, cancellationToken); + + try + { + var databaseCreator = (RelationalDatabaseCreator)normalSession.DbContext.Database.GetService(); + + await databaseCreator.CreateTablesAsync(cancellationToken); + } + catch (Exception exception) + { + _logger.LogDebug(exception, "It looks like the database tables have already been created"); + } + + var testModeSession = new TestModeEntityFrameworkSession(normalSession); + + await normalSession.StartTransaction(default); + + _sessions = (normalSession, testModeSession); + + return _sessions.Value.TestMode + .WithSnapshotSessionOptions(options); + } + + public override async ValueTask DisposeAsync() + { + if (_sessions.HasValue) + { + await _sessions.Value.Normal.AbortTransaction(default); + await _sessions.Value.Normal.DisposeAsync(); + } + } + + public EntityFrameworkSnapshotSessionOptions GetSessionOptions(string snapshotSessionOptionsName) + { + return _entityFrameworkSnapshotRepositoryFactory.GetSessionOptions(snapshotSessionOptionsName); + } + + public static TestModeEntityFrameworkSnapshotRepositoryFactory Create(IServiceProvider serviceProvider, IEntityFrameworkSnapshotRepositoryFactory entityFrameworkSnapshotRepositoryFactory) + { + return ActivatorUtilities.CreateInstance>(serviceProvider, entityFrameworkSnapshotRepositoryFactory); + } +} diff --git a/src/EntityDb.EntityFramework/packages.lock.json b/src/EntityDb.EntityFramework/packages.lock.json new file mode 100644 index 00000000..74b6287f --- /dev/null +++ b/src/EntityDb.EntityFramework/packages.lock.json @@ -0,0 +1,137 @@ +{ + "version": 1, + "dependencies": { + "net7.0": { + "Microsoft.EntityFrameworkCore.Relational": { + "type": "Direct", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "eQiYygtR2xZ0Uy7KtiFRHpoEx/U8xNwbNRgu1pEJgSxbJLtg6tDL1y2YcIbSuIRSNEljXIIHq/apEhGm1QL70g==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "7.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "7.0.0" + } + }, + "System.Linq.Async": { + "type": "Direct", + "requested": "[6.0.1, )", + "resolved": "6.0.1", + "contentHash": "0YhHcaroWpQ9UCot3Pizah7ryAzQhNvobLMSxeDIGmnXfkQn8u5owvpOH0K6EVB+z9L7u6Cc4W17Br/+jyttEQ==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" + }, + "Microsoft.EntityFrameworkCore": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "9W+IfmAzMrp2ZpKZLhgTlWljSBM9Erldis1us61DAGi+L7Q6vilTbe1G2zDxtYO8F2H0I0Qnupdx5Cp4s2xoZw==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "7.0.0", + "Microsoft.EntityFrameworkCore.Analyzers": "7.0.0", + "Microsoft.Extensions.Caching.Memory": "7.0.0", + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.Logging": "7.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Pfu3Zjj5+d2Gt27oE9dpGiF/VobBB+s5ogrfI9sBsXQE1SG49RqVz5+IyeNnzhyejFrPIQsPDRMchhcojy4Hbw==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Qkd2H+jLe37o5ku+LjT6qf7kAHY75Yfn2bBDQgqr13DTOLYpEy1Mt93KPFjaZvIu/srEcbfGGMRL7urKm5zN8Q==" + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "xpidBs2KCE2gw1JrD0quHE72kvCaI3xFql5/Peb2GRtUuZX+dYPoK/NTdVMiM67Svym0M0Df9A3xyU0FbMQhHw==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" + }, + "entitydb.abstractions": { + "type": "Project", + "dependencies": { + "System.Linq.Async": "[6.0.1, )" + } + }, + "entitydb.common": { + "type": "Project", + "dependencies": { + "EntityDb.Abstractions": "[1.0.0, )", + "System.Linq.Async": "[6.0.1, )" + } + } + } + } +} \ No newline at end of file diff --git a/src/EntityDb.MongoDb/Sessions/MongoDbTransactionSessionOptions.cs b/src/EntityDb.MongoDb/Sessions/MongoDbTransactionSessionOptions.cs index 44d7789f..aa2daaae 100644 --- a/src/EntityDb.MongoDb/Sessions/MongoDbTransactionSessionOptions.cs +++ b/src/EntityDb.MongoDb/Sessions/MongoDbTransactionSessionOptions.cs @@ -1,5 +1,6 @@ using EntityDb.Abstractions.Transactions; using MongoDB.Driver; +using System.Diagnostics.CodeAnalysis; namespace EntityDb.MongoDb.Sessions; @@ -34,6 +35,7 @@ public class MongoDbTransactionSessionOptions public TimeSpan? WriteTimeout { get; set; } /// + [ExcludeFromCodeCoverage(Justification = "This is only overridden to make test names better.")] public override string ToString() { return $"{nameof(MongoDbTransactionSessionOptions)}"; diff --git a/src/EntityDb.Redis/Sessions/RedisSnapshotSessionOptions.cs b/src/EntityDb.Redis/Sessions/RedisSnapshotSessionOptions.cs index cc55c78c..65c27f7b 100644 --- a/src/EntityDb.Redis/Sessions/RedisSnapshotSessionOptions.cs +++ b/src/EntityDb.Redis/Sessions/RedisSnapshotSessionOptions.cs @@ -1,5 +1,6 @@ using EntityDb.Abstractions.Snapshots; using StackExchange.Redis; +using System.Diagnostics.CodeAnalysis; namespace EntityDb.Redis.Sessions; @@ -30,6 +31,7 @@ public class RedisSnapshotSessionOptions public bool SecondaryPreferred { get; set; } /// + [ExcludeFromCodeCoverage(Justification = "This is only overridden to make test names better.")] public override string ToString() { return $"{nameof(RedisSnapshotSessionOptions)}<{typeof(TSnapshot).Name}"; diff --git a/src/EntityDb.SqlDb/Sessions/SqlDbTransactionSessionOptions.cs b/src/EntityDb.SqlDb/Sessions/SqlDbTransactionSessionOptions.cs index dd4cdd39..41ae493c 100644 --- a/src/EntityDb.SqlDb/Sessions/SqlDbTransactionSessionOptions.cs +++ b/src/EntityDb.SqlDb/Sessions/SqlDbTransactionSessionOptions.cs @@ -1,5 +1,6 @@ using EntityDb.Abstractions.Transactions; using System.Data; +using System.Diagnostics.CodeAnalysis; namespace EntityDb.SqlDb.Sessions; @@ -39,6 +40,7 @@ public class SqlDbTransactionSessionOptions public TimeSpan? ReadTimeout { get; set; } /// + [ExcludeFromCodeCoverage(Justification = "This is only overridden to make test names better.")] public override string ToString() { return $"{nameof(SqlDbTransactionSessionOptions)}"; diff --git a/test/EntityDb.Common.Tests/Entities/EntityTests.cs b/test/EntityDb.Common.Tests/Entities/EntityTests.cs index 0a544d14..3e6bb5cb 100644 --- a/test/EntityDb.Common.Tests/Entities/EntityTests.cs +++ b/test/EntityDb.Common.Tests/Entities/EntityTests.cs @@ -32,7 +32,7 @@ private static async Task BuildTransaction VersionNumber to, TEntity? entity = default ) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { var transactionBuilder = await serviceScope.ServiceProvider .GetRequiredService>() @@ -54,7 +54,7 @@ private static async Task BuildTransaction private async Task Generic_GivenEntityWithNVersions_WhenGettingAtVersionM_ThenReturnAtVersionM( TransactionsAdder transactionsAdder, SnapshotAdder entitySnapshotAdder) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { // ARRANGE @@ -97,7 +97,7 @@ private async Task Generic_GivenEntityWithNVersions_WhenGettingAtVersionM_ThenRe private async Task Generic_GivenExistingEntityWithNoSnapshot_WhenGettingEntity_ThenGetCommandsRuns( EntityAdder entityAdder) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { // ARRANGE @@ -172,7 +172,7 @@ private async Task Generic_GivenExistingEntityWithNoSnapshot_WhenGettingEntity_T private async Task Generic_GivenNoSnapshotRepositoryFactory_WhenCreatingEntityRepository_ThenNoSnapshotRepository( EntityAdder entityAdder) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { // ARRANGE @@ -202,7 +202,7 @@ private async Task private async Task Generic_GivenNoSnapshotSessionOptions_WhenCreatingEntityRepository_ThenNoSnapshotRepository( EntityAdder entityAdder) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { // ARRANGE @@ -233,7 +233,7 @@ private async Task private async Task Generic_GivenSnapshotRepositoryFactoryAndSnapshotSessionOptions_WhenCreatingEntityRepository_ThenNoSnapshotRepository< TEntity>(EntityAdder entityAdder) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { // ARRANGE @@ -264,7 +264,7 @@ private async Task private async Task Generic_GivenSnapshotAndNewCommands_WhenGettingSnapshotOrDefault_ThenReturnNewerThanSnapshot( EntityAdder entityAdder) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { // ARRANGE @@ -302,7 +302,7 @@ private async Task private async Task Generic_GivenNonExistentEntityId_WhenGettingCurrentEntity_ThenThrow( EntityAdder entityAdder) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { // ARRANGE diff --git a/test/EntityDb.Common.Tests/EntityDb.Common.Tests.csproj b/test/EntityDb.Common.Tests/EntityDb.Common.Tests.csproj index 0dc9cf41..47634e17 100644 --- a/test/EntityDb.Common.Tests/EntityDb.Common.Tests.csproj +++ b/test/EntityDb.Common.Tests/EntityDb.Common.Tests.csproj @@ -2,12 +2,14 @@ + + diff --git a/test/EntityDb.Common.Tests/Implementations/DbContexts/GenericDbContext.cs b/test/EntityDb.Common.Tests/Implementations/DbContexts/GenericDbContext.cs new file mode 100644 index 00000000..97ac65dc --- /dev/null +++ b/test/EntityDb.Common.Tests/Implementations/DbContexts/GenericDbContext.cs @@ -0,0 +1,55 @@ +using EntityDb.Common.Tests.Implementations.Snapshots; +using EntityDb.EntityFramework.DbContexts; +using EntityDb.EntityFramework.Sessions; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace EntityDb.Common.Tests.Implementations.DbContexts; + +internal class GenericDbContext : EntityDbContextBase, IEntityDbContext> + where TSnapshot : class, ISnapshotWithTestLogic +{ + private readonly EntityFrameworkSnapshotSessionOptions _options; + + public GenericDbContext(EntityFrameworkSnapshotSessionOptions options) + { + _options = options; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .UseNpgsql($"{_options.ConnectionString};Include Error Detail=true") + .EnableSensitiveDataLogging(); + } + + public static GenericDbContext Construct(EntityFrameworkSnapshotSessionOptions entityFrameworkSnapshotSessionOptions) + { + return new GenericDbContext(entityFrameworkSnapshotSessionOptions); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ApplyConfiguration(new SnapshotReferenceTypeConfiguration()); + modelBuilder.ApplyConfiguration(new SnapshotTypeConfiguration()); + } +} + +public class SnapshotReferenceTypeConfiguration : EntityFramework.Snapshots.SnapshotReferenceTypeConfiguration + where TSnapshot : class, ISnapshotWithTestLogic +{ + public SnapshotReferenceTypeConfiguration() : base($"{typeof(TSnapshot).Name}SnapshotReferences") + { + } +} + +public class SnapshotTypeConfiguration : IEntityTypeConfiguration + where TSnapshot : class, ISnapshotWithTestLogic +{ + public virtual void Configure(EntityTypeBuilder snapshotBuilder) + { + TSnapshot.Configure(snapshotBuilder); + } +} \ No newline at end of file diff --git a/test/EntityDb.Common.Tests/Implementations/Entities/TestEntity.cs b/test/EntityDb.Common.Tests/Implementations/Entities/TestEntity.cs index 72a8cd6d..759f9337 100644 --- a/test/EntityDb.Common.Tests/Implementations/Entities/TestEntity.cs +++ b/test/EntityDb.Common.Tests/Implementations/Entities/TestEntity.cs @@ -1,20 +1,33 @@ -using EntityDb.Abstractions.ValueObjects; +using System.Linq.Expressions; +using EntityDb.Abstractions.ValueObjects; using EntityDb.Common.Entities; using EntityDb.Common.Tests.Implementations.Commands; using EntityDb.Common.Tests.Implementations.Snapshots; +using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace EntityDb.Common.Tests.Implementations.Entities; -public record TestEntity - ( - Id Id, - VersionNumber VersionNumber = default - ) - : IEntity, ISnapshotWithTestLogic +public record TestEntity : IEntity, ISnapshotWithTestLogic { + public required Id Id { get; init; } + public VersionNumber VersionNumber { get; init; } + public static TestEntity Construct(Id entityId) { - return new TestEntity(entityId); + return new TestEntity + { + Id = entityId, + }; + } + + public static void Configure(EntityTypeBuilder testEntityBuilder) + { + testEntityBuilder + .HasKey(testEntity => new + { + testEntity.Id, + testEntity.VersionNumber, + }); } public Id GetId() @@ -57,4 +70,9 @@ public TestEntity WithVersionNumber(VersionNumber versionNumber) public static AsyncLocal?> ShouldRecordLogic { get; } = new(); public static AsyncLocal?> ShouldRecordAsLatestLogic { get; } = new(); + + public Expression> GetKeyPredicate() + { + return (testEntity) => testEntity.Id == Id && testEntity.VersionNumber == VersionNumber; + } } \ No newline at end of file diff --git a/test/EntityDb.Common.Tests/Implementations/Projections/OneToOneProjection.cs b/test/EntityDb.Common.Tests/Implementations/Projections/OneToOneProjection.cs index 8a3cd23b..a3914809 100644 --- a/test/EntityDb.Common.Tests/Implementations/Projections/OneToOneProjection.cs +++ b/test/EntityDb.Common.Tests/Implementations/Projections/OneToOneProjection.cs @@ -1,3 +1,4 @@ +using System.Linq.Expressions; using EntityDb.Abstractions.Annotations; using EntityDb.Abstractions.Queries; using EntityDb.Abstractions.Reducers; @@ -6,19 +7,33 @@ using EntityDb.Common.Queries; using EntityDb.Common.Tests.Implementations.Entities; using EntityDb.Common.Tests.Implementations.Snapshots; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Pointer = EntityDb.Abstractions.ValueObjects.Pointer; namespace EntityDb.Common.Tests.Implementations.Projections; -public record OneToOneProjection -( - Id Id, - VersionNumber VersionNumber = default, - VersionNumber EntityVersionNumber = default -) : IProjection, ISnapshotWithTestLogic +public record OneToOneProjection : IProjection, ISnapshotWithTestLogic { + public required Id Id { get; init; } + public TimeStamp LastEventAt { get; init; } + public VersionNumber VersionNumber { get; init; } + public static OneToOneProjection Construct(Id projectionId) { - return new OneToOneProjection(projectionId); + return new OneToOneProjection + { + Id = projectionId, + }; + } + + public static void Configure(EntityTypeBuilder oneToOneProjectionBuilder) + { + oneToOneProjectionBuilder + .HasKey(oneToOneProjection => new + { + oneToOneProjection.Id, + oneToOneProjection.VersionNumber, + }); } public Id GetId() @@ -37,7 +52,8 @@ public OneToOneProjection Reduce(IEntityAnnotation annotatedCommand) { IEntityAnnotation> reducer => reducer.Data.Reduce(this) with { - EntityVersionNumber = reducer.EntityVersionNumber + LastEventAt = reducer.TransactionTimeStamp, + VersionNumber = reducer.EntityVersionNumber }, _ => throw new NotSupportedException() }; @@ -55,7 +71,7 @@ public bool ShouldRecordAsLatest(OneToOneProjection? previousSnapshot) public ICommandQuery GetCommandQuery(Pointer projectionPointer) { - return new GetEntityCommandsQuery(projectionPointer, EntityVersionNumber); + return new GetEntityCommandsQuery(projectionPointer, VersionNumber); } public static Id? GetProjectionIdOrDefault(object entity) @@ -76,4 +92,9 @@ public OneToOneProjection WithVersionNumber(VersionNumber versionNumber) public static AsyncLocal?> ShouldRecordAsLatestLogic { get; } = new(); + + public Expression> GetKeyPredicate() + { + return (oneToOneProjection) => oneToOneProjection.Id == Id && oneToOneProjection.VersionNumber == VersionNumber; + } } \ No newline at end of file diff --git a/test/EntityDb.Common.Tests/Implementations/Seeders/TransactionSeeder.cs b/test/EntityDb.Common.Tests/Implementations/Seeders/TransactionSeeder.cs index a99939b2..93c036b2 100644 --- a/test/EntityDb.Common.Tests/Implementations/Seeders/TransactionSeeder.cs +++ b/test/EntityDb.Common.Tests/Implementations/Seeders/TransactionSeeder.cs @@ -13,7 +13,7 @@ namespace EntityDb.Common.Tests.Implementations.Seeders; public static class TransactionStepSeeder { public static IEnumerable CreateFromCommands(Id entityId, uint numCommands) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { for (var previousVersionNumber = new VersionNumber(0); previousVersionNumber.Value < numCommands; @@ -47,7 +47,7 @@ public static ITransaction Create(params ITransactionStep[] transactionSteps) } public static ITransaction Create(Id entityId, uint numCommands) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { var transactionSteps = TransactionStepSeeder.CreateFromCommands(entityId, numCommands).ToArray(); diff --git a/test/EntityDb.Common.Tests/Implementations/Snapshots/ISnapshotWithTestLogic.cs b/test/EntityDb.Common.Tests/Implementations/Snapshots/ISnapshotWithTestLogic.cs index ac669003..72b2a309 100644 --- a/test/EntityDb.Common.Tests/Implementations/Snapshots/ISnapshotWithTestLogic.cs +++ b/test/EntityDb.Common.Tests/Implementations/Snapshots/ISnapshotWithTestLogic.cs @@ -1,13 +1,17 @@ using EntityDb.Abstractions.ValueObjects; using EntityDb.Common.Snapshots; +using EntityDb.EntityFramework.Snapshots; +using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace EntityDb.Common.Tests.Implementations.Snapshots; -public interface ISnapshotWithTestLogic : ISnapshot +public interface ISnapshotWithTestLogic : ISnapshot, IEntityFrameworkSnapshot + where TSnapshot : class { VersionNumber VersionNumber { get; } static abstract string RedisKeyNamespace { get; } static abstract AsyncLocal?> ShouldRecordLogic { get; } static abstract AsyncLocal?> ShouldRecordAsLatestLogic { get; } + static abstract void Configure(EntityTypeBuilder snapshotBuilder); TSnapshot WithVersionNumber(VersionNumber versionNumber); } \ No newline at end of file diff --git a/test/EntityDb.Common.Tests/Projections/ProjectionsTests.cs b/test/EntityDb.Common.Tests/Projections/ProjectionsTests.cs index fad30023..a405c8b5 100644 --- a/test/EntityDb.Common.Tests/Projections/ProjectionsTests.cs +++ b/test/EntityDb.Common.Tests/Projections/ProjectionsTests.cs @@ -47,8 +47,8 @@ private async Task Generic_GivenTransactionCommitted_WhenGettingProjection_ThenReturnExpectedProjection( TransactionsAdder transactionsAdder, SnapshotAdder entitySnapshotAdder, SnapshotAdder projectionSnapshotAdder) - where TEntity : IEntity, ISnapshotWithTestLogic - where TProjection : IProjection, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic + where TProjection : class, IProjection, ISnapshotWithTestLogic { // ARRANGE diff --git a/test/EntityDb.Common.Tests/Snapshots/SnapshotTests.cs b/test/EntityDb.Common.Tests/Snapshots/SnapshotTests.cs index f5fccc57..ec1523c6 100644 --- a/test/EntityDb.Common.Tests/Snapshots/SnapshotTests.cs +++ b/test/EntityDb.Common.Tests/Snapshots/SnapshotTests.cs @@ -21,7 +21,7 @@ public SnapshotTests(IServiceProvider startupServiceProvider, DatabaseContainerF private async Task Generic_GivenEmptySnapshotRepository_WhenSnapshotInsertedAndFetched_ThenInsertedMatchesFetched( SnapshotAdder snapshotAdder) - where TSnapshot : ISnapshotWithTestLogic + where TSnapshot : class, ISnapshotWithTestLogic { // ARRANGE @@ -68,7 +68,7 @@ public Task GivenEmptySnapshotRepository_WhenSnapshotInsertedAndFetched_ThenInse private async Task Generic_GivenEmptySnapshotRepository_WhenPuttingSnapshotInReadOnlyMode_ThenCannotWriteInReadOnlyModeExceptionIsLogged< TSnapshot>(SnapshotAdder snapshotAdder) - where TSnapshot : ISnapshotWithTestLogic + where TSnapshot : class, ISnapshotWithTestLogic { // ARRANGE @@ -114,9 +114,62 @@ public Task ); } + private async Task + Generic_GivenInsertedSnapshotAsLatest_WhenSnapshotDeleted_ThenReturnNoSnapshot( + SnapshotAdder snapshotAdder) + where TSnapshot : class, ISnapshotWithTestLogic + { + // ARRANGE + + Pointer latestSnapshotPointer = Id.NewId(); + + var snapshot = TSnapshot.Construct(latestSnapshotPointer.Id).WithVersionNumber(new VersionNumber(5000)); + + using var serviceScope = CreateServiceScope(serviceCollection => + { + snapshotAdder.AddDependencies.Invoke(serviceCollection); + + TSnapshot.ShouldRecordAsLatestLogic.Value = (_, _) => true; + }); + + await using var writeSnapshotRepository = await serviceScope.ServiceProvider + .GetRequiredService>() + .CreateRepository(TestSessionOptions.Write); + + var inserted = await writeSnapshotRepository.PutSnapshot(latestSnapshotPointer, snapshot); + + // ARRANGE ASSERTIONS + + inserted.ShouldBeTrue(); + + // ACT + + var deleted = await writeSnapshotRepository.DeleteSnapshots(new[] { latestSnapshotPointer }); + + var finalSnapshot = await writeSnapshotRepository.GetSnapshotOrDefault(latestSnapshotPointer); + + // ASSERT + + deleted.ShouldBeTrue(); + + finalSnapshot.ShouldBe(default); + } + + [Theory] + [MemberData(nameof(AddEntitySnapshots))] + [MemberData(nameof(AddProjectionSnapshots))] + public Task GivenInsertedSnapshotAsLatest_WhenSnapshotDeleted_ThenReturnNoSnapshot(SnapshotAdder snapshotAdder) + { + return RunGenericTestAsync + ( + new[] { snapshotAdder.SnapshotType }, + new object?[] { snapshotAdder } + ); + } + private async Task Generic_GivenInsertedSnapshot_WhenReadInVariousReadModes_ThenReturnSameSnapshot( SnapshotAdder snapshotAdder) - where TSnapshot : ISnapshotWithTestLogic + where TSnapshot : class, ISnapshotWithTestLogic { // ARRANGE diff --git a/test/EntityDb.Common.Tests/TestsBase.cs b/test/EntityDb.Common.Tests/TestsBase.cs index b9d04b78..ef397161 100644 --- a/test/EntityDb.Common.Tests/TestsBase.cs +++ b/test/EntityDb.Common.Tests/TestsBase.cs @@ -8,9 +8,12 @@ using EntityDb.Common.Extensions; using EntityDb.Common.Polyfills; using EntityDb.Common.Projections; +using EntityDb.Common.Tests.Implementations.DbContexts; using EntityDb.Common.Tests.Implementations.Entities; using EntityDb.Common.Tests.Implementations.Projections; using EntityDb.Common.Tests.Implementations.Snapshots; +using EntityDb.EntityFramework.Extensions; +using EntityDb.EntityFramework.Sessions; using EntityDb.InMemory.Extensions; using EntityDb.InMemory.Sessions; using EntityDb.MongoDb.Extensions; @@ -21,6 +24,7 @@ using EntityDb.Redis.Extensions; using EntityDb.Redis.Sessions; using EntityDb.SqlDb.Sessions; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -150,8 +154,41 @@ protected Task RunGenericTestAsync(Type[] typeArguments, object?[] invokeParamet .ShouldNotBeNull(); } + private static SnapshotAdder EntityFrameworkSnapshotAdder() + where TSnapshot : class, ISnapshotWithTestLogic + { + return new SnapshotAdder($"EntityFramework<{typeof(TSnapshot).Name}>", typeof(TSnapshot), serviceCollection => + { + var databaseContainerFixture = serviceCollection + .Single(descriptor => descriptor.ServiceType == typeof(DatabaseContainerFixture)) + .ImplementationInstance as DatabaseContainerFixture; + + serviceCollection.AddEntityFrameworkSnapshots>(testMode: true); + + serviceCollection.ConfigureAll(options => + { + options.ConnectionString = databaseContainerFixture!.PostgreSqlContainer.ConnectionString; + }); + + serviceCollection.Configure(TestSessionOptions.Write, options => + { + options.ReadOnly = false; + }); + + serviceCollection.Configure(TestSessionOptions.ReadOnly, options => + { + options.ReadOnly = true; + }); + + serviceCollection.Configure(TestSessionOptions.ReadOnlySecondaryPreferred, options => + { + options.ReadOnly = true; + }); + }); + } + private static SnapshotAdder RedisSnapshotAdder() - where TSnapshot : ISnapshotWithTestLogic + where TSnapshot : class, ISnapshotWithTestLogic { return new SnapshotAdder($"Redis<{typeof(TSnapshot).Name}>", typeof(TSnapshot), serviceCollection => { @@ -187,7 +224,7 @@ private static SnapshotAdder RedisSnapshotAdder() } private static SnapshotAdder InMemorySnapshotAdder() - where TSnapshot : ISnapshotWithTestLogic + where TSnapshot : class, ISnapshotWithTestLogic { return new SnapshotAdder($"InMemory<{typeof(TSnapshot).Name}>", typeof(TSnapshot), serviceCollection => { @@ -211,8 +248,9 @@ private static SnapshotAdder InMemorySnapshotAdder() } private static IEnumerable AllSnapshotAdders() - where TSnapshot : ISnapshotWithTestLogic + where TSnapshot : class, ISnapshotWithTestLogic { + yield return EntityFrameworkSnapshotAdder(); yield return RedisSnapshotAdder(); yield return InMemorySnapshotAdder(); } @@ -225,7 +263,7 @@ private static EntityAdder GetEntityAdder() } private static IEnumerable AllEntitySnapshotAdders() - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { return from snapshotAdder in AllSnapshotAdders() @@ -250,7 +288,7 @@ private static IEnumerable AllEntitySnapshotAdders() } private static IEnumerable AllProjectionAdders() - where TProjection : IProjection, ISnapshotWithTestLogic + where TProjection : class, IProjection, ISnapshotWithTestLogic { return AllSnapshotAdders() .Select(snapshotAdder => new SnapshotAdder(snapshotAdder.Name, snapshotAdder.SnapshotType, @@ -320,11 +358,16 @@ protected IServiceScope CreateServiceScope(Action? configure serviceCollection.AddLogging(loggingBuilder => { - loggingBuilder.AddProvider(new XunitTestOutputLoggerProvider(_testOutputHelperAccessor)); + loggingBuilder.AddProvider(new XunitTestOutputLoggerProvider(_testOutputHelperAccessor, (_,_) => true)); loggingBuilder.AddDebug(); loggingBuilder.AddSimpleConsole(options => { options.IncludeScopes = true; }); }); + serviceCollection.Configure(x => + { + x.MinLevel = LogLevel.Debug; + }); + startup.AddServices(serviceCollection); if (_databaseContainerFixture != null) @@ -362,17 +405,21 @@ protected static (ILoggerFactory Logger, Action LoggerVerifier) GetMocked It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), + It.IsAny(), It.IsAny>() )); + loggerMock + .Setup(logger => logger.IsEnabled(It.IsAny())) + .Returns((LogLevel logLevel) => logLevel == LogLevel.Error); + loggerMock .Setup(logger => logger.Log ( - LogLevel.Error, + It.Is(logLevel => logLevel == LogLevel.Error), It.IsAny(), It.IsAny(), - It.IsAny(), + It.Is(exception => exception is TException), It.IsAny>() )) .Verifiable(); @@ -393,10 +440,10 @@ void Verifier(Times times) ( logger => logger.Log ( - LogLevel.Error, + It.Is(logLevel => logLevel == LogLevel.Error), It.IsAny(), It.IsAny(), - It.IsAny(), + It.Is(exception => exception is TException), It.IsAny>() ), times diff --git a/test/EntityDb.Common.Tests/Transactions/EntitySnapshotTransactionSubscriberTests.cs b/test/EntityDb.Common.Tests/Transactions/EntitySnapshotTransactionSubscriberTests.cs index 9edfa1ca..f4f8b5e4 100644 --- a/test/EntityDb.Common.Tests/Transactions/EntitySnapshotTransactionSubscriberTests.cs +++ b/test/EntityDb.Common.Tests/Transactions/EntitySnapshotTransactionSubscriberTests.cs @@ -21,7 +21,7 @@ private async Task Generic_GivenSnapshotShouldRecordAsMostRecentAlwaysReturnsTrue_WhenRunningEntitySnapshotTransactionSubscriber_ThenAlwaysWriteSnapshot< TEntity>( TransactionsAdder transactionsAdder, SnapshotAdder entitySnapshotAdder) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { // ARRANGE @@ -75,7 +75,7 @@ private Task private async Task Generic_GivenSnapshotShouldRecordAsMostRecentAlwaysReturnsFalse_WhenRunningEntitySnapshotTransactionSubscriber_ThenNeverWriteSnapshot< TEntity>(TransactionsAdder transactionsAdder, SnapshotAdder snapshotAdder) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { // ARRANGE diff --git a/test/EntityDb.Common.Tests/Transactions/TransactionTests.cs b/test/EntityDb.Common.Tests/Transactions/TransactionTests.cs index c1a3c191..bb86ac11 100644 --- a/test/EntityDb.Common.Tests/Transactions/TransactionTests.cs +++ b/test/EntityDb.Common.Tests/Transactions/TransactionTests.cs @@ -894,7 +894,7 @@ private async Task Generic_GivenCommandInserted_WhenGettingAnnotatedCommand_Then private async Task Generic_GivenEntityInserted_WhenGettingEntity_ThenReturnEntity( TransactionsAdder transactionsAdder, EntityAdder entityAdder) - where TEntity : IEntity, ISnapshotWithTestLogic + where TEntity : class, IEntity, ISnapshotWithTestLogic { // ARRANGE diff --git a/test/EntityDb.Common.Tests/packages.lock.json b/test/EntityDb.Common.Tests/packages.lock.json index 1b7f9f5f..48a2f446 100644 --- a/test/EntityDb.Common.Tests/packages.lock.json +++ b/test/EntityDb.Common.Tests/packages.lock.json @@ -33,6 +33,18 @@ "Castle.Core": "5.1.0" } }, + "Npgsql.EntityFrameworkCore.PostgreSQL": { + "type": "Direct", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "CyUNlFZmtX2Kmw8XK5Tlx5eVUCzWJ+zJHErxZiMo2Y8zCRuH9+/OMGwG+9Mmp5zD5p3Ifbi5Pp3btsqoDDkSZQ==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "[7.0.0, 8.0.0)", + "Microsoft.EntityFrameworkCore.Abstractions": "[7.0.0, 8.0.0)", + "Microsoft.EntityFrameworkCore.Relational": "[7.0.0, 8.0.0)", + "Npgsql": "7.0.0" + } + }, "Shouldly": { "type": "Direct", "requested": "[4.1.0, )", @@ -169,6 +181,57 @@ "resolved": "4.7.0", "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" }, + "Microsoft.EntityFrameworkCore": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "9W+IfmAzMrp2ZpKZLhgTlWljSBM9Erldis1us61DAGi+L7Q6vilTbe1G2zDxtYO8F2H0I0Qnupdx5Cp4s2xoZw==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "7.0.0", + "Microsoft.EntityFrameworkCore.Analyzers": "7.0.0", + "Microsoft.Extensions.Caching.Memory": "7.0.0", + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.Logging": "7.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Pfu3Zjj5+d2Gt27oE9dpGiF/VobBB+s5ogrfI9sBsXQE1SG49RqVz5+IyeNnzhyejFrPIQsPDRMchhcojy4Hbw==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Qkd2H+jLe37o5ku+LjT6qf7kAHY75Yfn2bBDQgqr13DTOLYpEy1Mt93KPFjaZvIu/srEcbfGGMRL7urKm5zN8Q==" + }, + "Microsoft.EntityFrameworkCore.Relational": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "eQiYygtR2xZ0Uy7KtiFRHpoEx/U8xNwbNRgu1pEJgSxbJLtg6tDL1y2YcIbSuIRSNEljXIIHq/apEhGm1QL70g==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "7.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "xpidBs2KCE2gw1JrD0quHE72kvCaI3xFql5/Peb2GRtUuZX+dYPoK/NTdVMiM67Svym0M0Df9A3xyU0FbMQhHw==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, "Microsoft.Extensions.Configuration": { "type": "Transitive", "resolved": "6.0.0", @@ -180,10 +243,10 @@ }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "qWzV9o+ZRWq+pGm+1dF+R7qTgTYoXvbyowRoBxQJGfqTpqDun2eteerjRQhq5PQ/14S+lqto3Ft4gYaRyl4rdQ==", + "resolved": "7.0.0", + "contentHash": "f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "6.0.0" + "Microsoft.Extensions.Primitives": "7.0.0" } }, "Microsoft.Extensions.Configuration.Binder": { @@ -249,17 +312,16 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "7.0.0", + "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" + "resolved": "7.0.0", + "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", @@ -324,20 +386,19 @@ }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "resolved": "7.0.0", + "contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "6.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Logging.Abstractions": "6.0.0", - "Microsoft.Extensions.Options": "6.0.0", - "System.Diagnostics.DiagnosticSource": "6.0.0" + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + "resolved": "7.0.0", + "contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", @@ -405,11 +466,11 @@ }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + "resolved": "7.0.0", + "contentHash": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Primitives": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { @@ -426,11 +487,8 @@ }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } + "resolved": "7.0.0", + "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", @@ -1636,6 +1694,14 @@ "System.Linq.Async": "[6.0.1, )" } }, + "entitydb.entityframework": { + "type": "Project", + "dependencies": { + "EntityDb.Common": "[1.0.0, )", + "Microsoft.EntityFrameworkCore.Relational": "[7.0.0, )", + "System.Linq.Async": "[6.0.1, )" + } + }, "entitydb.inmemory": { "type": "Project", "dependencies": { diff --git a/test/EntityDb.MongoDb.Tests/packages.lock.json b/test/EntityDb.MongoDb.Tests/packages.lock.json index bd797557..ddbc98af 100644 --- a/test/EntityDb.MongoDb.Tests/packages.lock.json +++ b/test/EntityDb.MongoDb.Tests/packages.lock.json @@ -154,6 +154,57 @@ "resolved": "4.7.0", "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" }, + "Microsoft.EntityFrameworkCore": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "9W+IfmAzMrp2ZpKZLhgTlWljSBM9Erldis1us61DAGi+L7Q6vilTbe1G2zDxtYO8F2H0I0Qnupdx5Cp4s2xoZw==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "7.0.0", + "Microsoft.EntityFrameworkCore.Analyzers": "7.0.0", + "Microsoft.Extensions.Caching.Memory": "7.0.0", + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.Logging": "7.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Pfu3Zjj5+d2Gt27oE9dpGiF/VobBB+s5ogrfI9sBsXQE1SG49RqVz5+IyeNnzhyejFrPIQsPDRMchhcojy4Hbw==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Qkd2H+jLe37o5ku+LjT6qf7kAHY75Yfn2bBDQgqr13DTOLYpEy1Mt93KPFjaZvIu/srEcbfGGMRL7urKm5zN8Q==" + }, + "Microsoft.EntityFrameworkCore.Relational": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "eQiYygtR2xZ0Uy7KtiFRHpoEx/U8xNwbNRgu1pEJgSxbJLtg6tDL1y2YcIbSuIRSNEljXIIHq/apEhGm1QL70g==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "7.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "xpidBs2KCE2gw1JrD0quHE72kvCaI3xFql5/Peb2GRtUuZX+dYPoK/NTdVMiM67Svym0M0Df9A3xyU0FbMQhHw==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, "Microsoft.Extensions.Configuration": { "type": "Transitive", "resolved": "6.0.0", @@ -165,10 +216,10 @@ }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "qWzV9o+ZRWq+pGm+1dF+R7qTgTYoXvbyowRoBxQJGfqTpqDun2eteerjRQhq5PQ/14S+lqto3Ft4gYaRyl4rdQ==", + "resolved": "7.0.0", + "contentHash": "f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "6.0.0" + "Microsoft.Extensions.Primitives": "7.0.0" } }, "Microsoft.Extensions.Configuration.Binder": { @@ -234,17 +285,16 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "7.0.0", + "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" + "resolved": "7.0.0", + "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", @@ -309,20 +359,19 @@ }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "resolved": "7.0.0", + "contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "6.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Logging.Abstractions": "6.0.0", - "Microsoft.Extensions.Options": "6.0.0", - "System.Diagnostics.DiagnosticSource": "6.0.0" + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + "resolved": "7.0.0", + "contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", @@ -390,11 +439,11 @@ }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + "resolved": "7.0.0", + "contentHash": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Primitives": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { @@ -411,11 +460,8 @@ }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } + "resolved": "7.0.0", + "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", @@ -576,6 +622,17 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "Npgsql.EntityFrameworkCore.PostgreSQL": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "CyUNlFZmtX2Kmw8XK5Tlx5eVUCzWJ+zJHErxZiMo2Y8zCRuH9+/OMGwG+9Mmp5zD5p3Ifbi5Pp3btsqoDDkSZQ==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "[7.0.0, 8.0.0)", + "Microsoft.EntityFrameworkCore.Abstractions": "[7.0.0, 8.0.0)", + "Microsoft.EntityFrameworkCore.Relational": "[7.0.0, 8.0.0)", + "Npgsql": "7.0.0" + } + }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "5.11.0", @@ -1640,11 +1697,13 @@ "dependencies": { "Bogus": "[34.0.2, )", "EntityDb.Common": "[1.0.0, )", + "EntityDb.EntityFramework": "[1.0.0, )", "EntityDb.InMemory": "[1.0.0, )", "EntityDb.Provisioner": "[1.0.0, )", "EntityDb.Redis": "[1.0.0, )", "Microsoft.NET.Test.Sdk": "[17.4.0, )", "Moq": "[4.18.2, )", + "Npgsql.EntityFrameworkCore.PostgreSQL": "[7.0.0, )", "Shouldly": "[4.1.0, )", "System.Linq.Async": "[6.0.1, )", "Testcontainers": "[2.2.0, )", @@ -1653,6 +1712,14 @@ "xunit": "[2.4.2, )" } }, + "entitydb.entityframework": { + "type": "Project", + "dependencies": { + "EntityDb.Common": "[1.0.0, )", + "Microsoft.EntityFrameworkCore.Relational": "[7.0.0, )", + "System.Linq.Async": "[6.0.1, )" + } + }, "entitydb.inmemory": { "type": "Project", "dependencies": { diff --git a/test/EntityDb.Mvc.Tests/packages.lock.json b/test/EntityDb.Mvc.Tests/packages.lock.json index fbecc27c..866d07aa 100644 --- a/test/EntityDb.Mvc.Tests/packages.lock.json +++ b/test/EntityDb.Mvc.Tests/packages.lock.json @@ -154,6 +154,57 @@ "resolved": "4.7.0", "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" }, + "Microsoft.EntityFrameworkCore": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "9W+IfmAzMrp2ZpKZLhgTlWljSBM9Erldis1us61DAGi+L7Q6vilTbe1G2zDxtYO8F2H0I0Qnupdx5Cp4s2xoZw==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "7.0.0", + "Microsoft.EntityFrameworkCore.Analyzers": "7.0.0", + "Microsoft.Extensions.Caching.Memory": "7.0.0", + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.Logging": "7.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Pfu3Zjj5+d2Gt27oE9dpGiF/VobBB+s5ogrfI9sBsXQE1SG49RqVz5+IyeNnzhyejFrPIQsPDRMchhcojy4Hbw==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Qkd2H+jLe37o5ku+LjT6qf7kAHY75Yfn2bBDQgqr13DTOLYpEy1Mt93KPFjaZvIu/srEcbfGGMRL7urKm5zN8Q==" + }, + "Microsoft.EntityFrameworkCore.Relational": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "eQiYygtR2xZ0Uy7KtiFRHpoEx/U8xNwbNRgu1pEJgSxbJLtg6tDL1y2YcIbSuIRSNEljXIIHq/apEhGm1QL70g==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "7.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "xpidBs2KCE2gw1JrD0quHE72kvCaI3xFql5/Peb2GRtUuZX+dYPoK/NTdVMiM67Svym0M0Df9A3xyU0FbMQhHw==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, "Microsoft.Extensions.Configuration": { "type": "Transitive", "resolved": "6.0.0", @@ -165,10 +216,10 @@ }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "qWzV9o+ZRWq+pGm+1dF+R7qTgTYoXvbyowRoBxQJGfqTpqDun2eteerjRQhq5PQ/14S+lqto3Ft4gYaRyl4rdQ==", + "resolved": "7.0.0", + "contentHash": "f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "6.0.0" + "Microsoft.Extensions.Primitives": "7.0.0" } }, "Microsoft.Extensions.Configuration.Binder": { @@ -234,17 +285,16 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "7.0.0", + "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" + "resolved": "7.0.0", + "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", @@ -309,20 +359,19 @@ }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "resolved": "7.0.0", + "contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "6.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Logging.Abstractions": "6.0.0", - "Microsoft.Extensions.Options": "6.0.0", - "System.Diagnostics.DiagnosticSource": "6.0.0" + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + "resolved": "7.0.0", + "contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", @@ -390,11 +439,11 @@ }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + "resolved": "7.0.0", + "contentHash": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Primitives": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { @@ -411,11 +460,8 @@ }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } + "resolved": "7.0.0", + "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", @@ -576,6 +622,17 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "Npgsql.EntityFrameworkCore.PostgreSQL": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "CyUNlFZmtX2Kmw8XK5Tlx5eVUCzWJ+zJHErxZiMo2Y8zCRuH9+/OMGwG+9Mmp5zD5p3Ifbi5Pp3btsqoDDkSZQ==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "[7.0.0, 8.0.0)", + "Microsoft.EntityFrameworkCore.Abstractions": "[7.0.0, 8.0.0)", + "Microsoft.EntityFrameworkCore.Relational": "[7.0.0, 8.0.0)", + "Npgsql": "7.0.0" + } + }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "5.11.0", @@ -1640,11 +1697,13 @@ "dependencies": { "Bogus": "[34.0.2, )", "EntityDb.Common": "[1.0.0, )", + "EntityDb.EntityFramework": "[1.0.0, )", "EntityDb.InMemory": "[1.0.0, )", "EntityDb.Provisioner": "[1.0.0, )", "EntityDb.Redis": "[1.0.0, )", "Microsoft.NET.Test.Sdk": "[17.4.0, )", "Moq": "[4.18.2, )", + "Npgsql.EntityFrameworkCore.PostgreSQL": "[7.0.0, )", "Shouldly": "[4.1.0, )", "System.Linq.Async": "[6.0.1, )", "Testcontainers": "[2.2.0, )", @@ -1653,6 +1712,14 @@ "xunit": "[2.4.2, )" } }, + "entitydb.entityframework": { + "type": "Project", + "dependencies": { + "EntityDb.Common": "[1.0.0, )", + "Microsoft.EntityFrameworkCore.Relational": "[7.0.0, )", + "System.Linq.Async": "[6.0.1, )" + } + }, "entitydb.inmemory": { "type": "Project", "dependencies": { diff --git a/test/EntityDb.Redis.Tests/packages.lock.json b/test/EntityDb.Redis.Tests/packages.lock.json index bd797557..ddbc98af 100644 --- a/test/EntityDb.Redis.Tests/packages.lock.json +++ b/test/EntityDb.Redis.Tests/packages.lock.json @@ -154,6 +154,57 @@ "resolved": "4.7.0", "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" }, + "Microsoft.EntityFrameworkCore": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "9W+IfmAzMrp2ZpKZLhgTlWljSBM9Erldis1us61DAGi+L7Q6vilTbe1G2zDxtYO8F2H0I0Qnupdx5Cp4s2xoZw==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "7.0.0", + "Microsoft.EntityFrameworkCore.Analyzers": "7.0.0", + "Microsoft.Extensions.Caching.Memory": "7.0.0", + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.Logging": "7.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Pfu3Zjj5+d2Gt27oE9dpGiF/VobBB+s5ogrfI9sBsXQE1SG49RqVz5+IyeNnzhyejFrPIQsPDRMchhcojy4Hbw==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Qkd2H+jLe37o5ku+LjT6qf7kAHY75Yfn2bBDQgqr13DTOLYpEy1Mt93KPFjaZvIu/srEcbfGGMRL7urKm5zN8Q==" + }, + "Microsoft.EntityFrameworkCore.Relational": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "eQiYygtR2xZ0Uy7KtiFRHpoEx/U8xNwbNRgu1pEJgSxbJLtg6tDL1y2YcIbSuIRSNEljXIIHq/apEhGm1QL70g==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "7.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "xpidBs2KCE2gw1JrD0quHE72kvCaI3xFql5/Peb2GRtUuZX+dYPoK/NTdVMiM67Svym0M0Df9A3xyU0FbMQhHw==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, "Microsoft.Extensions.Configuration": { "type": "Transitive", "resolved": "6.0.0", @@ -165,10 +216,10 @@ }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "qWzV9o+ZRWq+pGm+1dF+R7qTgTYoXvbyowRoBxQJGfqTpqDun2eteerjRQhq5PQ/14S+lqto3Ft4gYaRyl4rdQ==", + "resolved": "7.0.0", + "contentHash": "f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "6.0.0" + "Microsoft.Extensions.Primitives": "7.0.0" } }, "Microsoft.Extensions.Configuration.Binder": { @@ -234,17 +285,16 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "7.0.0", + "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" + "resolved": "7.0.0", + "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", @@ -309,20 +359,19 @@ }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "resolved": "7.0.0", + "contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "6.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Logging.Abstractions": "6.0.0", - "Microsoft.Extensions.Options": "6.0.0", - "System.Diagnostics.DiagnosticSource": "6.0.0" + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + "resolved": "7.0.0", + "contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", @@ -390,11 +439,11 @@ }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + "resolved": "7.0.0", + "contentHash": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Primitives": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { @@ -411,11 +460,8 @@ }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } + "resolved": "7.0.0", + "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", @@ -576,6 +622,17 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "Npgsql.EntityFrameworkCore.PostgreSQL": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "CyUNlFZmtX2Kmw8XK5Tlx5eVUCzWJ+zJHErxZiMo2Y8zCRuH9+/OMGwG+9Mmp5zD5p3Ifbi5Pp3btsqoDDkSZQ==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "[7.0.0, 8.0.0)", + "Microsoft.EntityFrameworkCore.Abstractions": "[7.0.0, 8.0.0)", + "Microsoft.EntityFrameworkCore.Relational": "[7.0.0, 8.0.0)", + "Npgsql": "7.0.0" + } + }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "5.11.0", @@ -1640,11 +1697,13 @@ "dependencies": { "Bogus": "[34.0.2, )", "EntityDb.Common": "[1.0.0, )", + "EntityDb.EntityFramework": "[1.0.0, )", "EntityDb.InMemory": "[1.0.0, )", "EntityDb.Provisioner": "[1.0.0, )", "EntityDb.Redis": "[1.0.0, )", "Microsoft.NET.Test.Sdk": "[17.4.0, )", "Moq": "[4.18.2, )", + "Npgsql.EntityFrameworkCore.PostgreSQL": "[7.0.0, )", "Shouldly": "[4.1.0, )", "System.Linq.Async": "[6.0.1, )", "Testcontainers": "[2.2.0, )", @@ -1653,6 +1712,14 @@ "xunit": "[2.4.2, )" } }, + "entitydb.entityframework": { + "type": "Project", + "dependencies": { + "EntityDb.Common": "[1.0.0, )", + "Microsoft.EntityFrameworkCore.Relational": "[7.0.0, )", + "System.Linq.Async": "[6.0.1, )" + } + }, "entitydb.inmemory": { "type": "Project", "dependencies": {