-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
b8cddec
commit 1f686b7
Showing
45 changed files
with
1,670 additions
and
157 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"sdk": { | ||
"version": "7.0.100", | ||
"version": "7.0.201", | ||
"allowPrerelease": false, | ||
"rollForward": "disable" | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Id, Guid> | ||
{ | ||
private static readonly Expression<Func<Id, Guid>> IdToGuid = (id) => id.Value; | ||
private static readonly Expression<Func<Guid, Id>> GuidToId = (guid) => new Id(guid); | ||
|
||
public IdConverter() : base(IdToGuid, GuidToId, null) | ||
{ | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
src/EntityDb.EntityFramework/Converters/TimeStampConverter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<TimeStamp, DateTime> | ||
{ | ||
private static readonly Expression<Func<TimeStamp, DateTime>> TimeStampToDateTime = (timeStamp) => timeStamp.Value; | ||
private static readonly Expression<Func<DateTime, TimeStamp>> DateTimeToTimeStamp = (dateTime) => new TimeStamp(dateTime); | ||
|
||
public TimeStampConverter() : base(TimeStampToDateTime, DateTimeToTimeStamp, null) | ||
{ | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
src/EntityDb.EntityFramework/Converters/VersionNumberConverter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<VersionNumber, ulong> | ||
{ | ||
private static readonly Expression<Func<VersionNumber, ulong>> VersionNumberToUlong = (versionNumber) => versionNumber.Value; | ||
private static readonly Expression<Func<ulong, VersionNumber>> UlongToVersionNumber = (@ulong) => new VersionNumber(@ulong); | ||
|
||
public VersionNumberConverter() : base(VersionNumberToUlong, UlongToVersionNumber, null) | ||
{ | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
src/EntityDb.EntityFramework/DbContexts/EntityDbContextBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using EntityDb.Abstractions.ValueObjects; | ||
using EntityDb.EntityFramework.Converters; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace EntityDb.EntityFramework.DbContexts; | ||
|
||
/// <summary> | ||
/// A DbContext that adds basic converters for types defined in <see cref="Abstractions.ValueObjects"/> | ||
/// </summary> | ||
public abstract class EntityDbContextBase : DbContext | ||
{ | ||
/// <inheritdoc /> | ||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) | ||
{ | ||
configurationBuilder | ||
.Properties<Id>() | ||
.HaveConversion<IdConverter>(); | ||
|
||
configurationBuilder | ||
.Properties<VersionNumber>() | ||
.HaveConversion<VersionNumberConverter>(); | ||
|
||
configurationBuilder | ||
.Properties<TimeStamp>() | ||
.HaveConversion<TimeStampConverter>(); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
src/EntityDb.EntityFramework/DbContexts/EntityDbContextFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using EntityDb.EntityFramework.Sessions; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace EntityDb.EntityFramework.DbContexts; | ||
|
||
internal class EntityDbContextFactory<TDbContext> : IEntityDbContextFactory<TDbContext> | ||
where TDbContext : DbContext, IEntityDbContext<TDbContext> | ||
{ | ||
private readonly IOptionsFactory<EntityFrameworkSnapshotSessionOptions> _optionsFactory; | ||
|
||
public EntityDbContextFactory(IOptionsFactory<EntityFrameworkSnapshotSessionOptions> optionsFactory) | ||
{ | ||
_optionsFactory = optionsFactory; | ||
} | ||
|
||
public TDbContext Create(string snapshotSessionOptionsName) | ||
{ | ||
return TDbContext.Construct(_optionsFactory.Create(snapshotSessionOptionsName)); | ||
} | ||
|
||
TDbContext IEntityDbContextFactory<TDbContext>.Create(EntityFrameworkSnapshotSessionOptions snapshotSessionOptions) | ||
{ | ||
return TDbContext.Construct(snapshotSessionOptions); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/EntityDb.EntityFramework/DbContexts/IEntityDbContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using EntityDb.EntityFramework.Sessions; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace EntityDb.EntityFramework.DbContexts; | ||
|
||
/// <summary> | ||
/// A type of a <see cref="DbContext"/> that can be used for EntityDb purposes. | ||
/// </summary> | ||
/// <typeparam name="TDbContext">The type of the <see cref="DbContext"/></typeparam> | ||
public interface IEntityDbContext<TDbContext> | ||
where TDbContext : DbContext, IEntityDbContext<TDbContext> | ||
{ | ||
/// <summary> | ||
/// Returns a new <typeparamref name="TDbContext"/> that will be configured using <paramref name="entityFrameworkSnapshotSessionOptions"/>. | ||
/// </summary> | ||
/// <param name="entityFrameworkSnapshotSessionOptions">The options for the database</param> | ||
/// <returns>A new <typeparamref name="TDbContext"/> that will be configured using <paramref name="entityFrameworkSnapshotSessionOptions"/>.</returns> | ||
static abstract TDbContext Construct(EntityFrameworkSnapshotSessionOptions entityFrameworkSnapshotSessionOptions); | ||
} |
20 changes: 20 additions & 0 deletions
20
src/EntityDb.EntityFramework/DbContexts/IEntityDbContextFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using EntityDb.EntityFramework.Sessions; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace EntityDb.EntityFramework.DbContexts; | ||
|
||
/// <summary> | ||
/// Represents a type used to create instances of <typeparamref name="TDbContext"/>. | ||
/// </summary> | ||
/// <typeparam name="TDbContext">The type of the <see cref="DbContext"/>.</typeparam> | ||
public interface IEntityDbContextFactory<TDbContext> | ||
{ | ||
internal TDbContext Create(EntityFrameworkSnapshotSessionOptions snapshotSessionOptions); | ||
|
||
/// <summary> | ||
/// Create a new instance of <typeparamref name="TDbContext"/>. | ||
/// </summary> | ||
/// <param name="snapshotSessionOptionsName">The agent's use case for the <see cref="DbContext"/>.</param> | ||
/// <returns>A new instance of <typeparamref name="TDbContext"/>.</returns> | ||
TDbContext Create(string snapshotSessionOptionsName); | ||
} |
17 changes: 17 additions & 0 deletions
17
src/EntityDb.EntityFramework/EntityDb.EntityFramework.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<!--Pack--> | ||
<PropertyGroup> | ||
<PackageTags>EntityDb EventSourcing DDD CQRS</PackageTags> | ||
<Description>An implementation of the EntityDb Snapshot Repository interface, specifically for Entity Framework.</Description> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\EntityDb.Common\EntityDb.Common.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
48 changes: 48 additions & 0 deletions
48
src/EntityDb.EntityFramework/Extensions/ServiceCollectionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
/// <summary> | ||
/// Extensions for service collections. | ||
/// </summary> | ||
[ExcludeFromCodeCoverage(Justification = "Don't need coverage for non-test mode.")] | ||
public static class ServiceCollectionExtensions | ||
{ | ||
/// <summary> | ||
/// Adds a production-ready implementation of <see cref="ISnapshotRepositoryFactory{TSnapshot}" /> to a service | ||
/// collection. | ||
/// </summary> | ||
/// <typeparam name="TSnapshot">The type of the snapshot stored in the repository.</typeparam> | ||
/// <typeparam name="TDbContext">The type of the snapshot stored in the repository.</typeparam> | ||
/// <param name="serviceCollection">The service collection.</param> | ||
/// <param name="testMode">Modifies the behavior of the repository to accomodate tests.</param> | ||
public static void AddEntityFrameworkSnapshots<TSnapshot, TDbContext>(this IServiceCollection serviceCollection, bool testMode = false) | ||
where TSnapshot : class, IEntityFrameworkSnapshot<TSnapshot> | ||
where TDbContext : DbContext, IEntityDbContext<TDbContext> | ||
{ | ||
serviceCollection.Add<IEntityDbContextFactory<TDbContext>, EntityDbContextFactory<TDbContext>> | ||
( | ||
testMode ? ServiceLifetime.Singleton : ServiceLifetime.Transient | ||
); | ||
|
||
serviceCollection.Add<EntityFrameworkSnapshotRepositoryFactory<TSnapshot, TDbContext>> | ||
( | ||
testMode ? ServiceLifetime.Singleton : ServiceLifetime.Transient | ||
); | ||
|
||
serviceCollection.Add<ISnapshotRepositoryFactory<TSnapshot>> | ||
( | ||
testMode ? ServiceLifetime.Singleton : ServiceLifetime.Transient, | ||
serviceProvider => serviceProvider | ||
.GetRequiredService<EntityFrameworkSnapshotRepositoryFactory<TSnapshot, TDbContext>>() | ||
.UseTestMode(serviceProvider, testMode) | ||
); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
...ityDb.EntityFramework/Extensions/TestModeMongoDbTransactionRepositoryFactoryExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<TSnapshot> UseTestMode<TSnapshot>( | ||
this IEntityFrameworkSnapshotRepositoryFactory<TSnapshot> entityFrameworkSnapshotRepositoyFactory, | ||
IServiceProvider serviceProvider, | ||
bool testMode) | ||
{ | ||
return testMode | ||
? TestModeEntityFrameworkSnapshotRepositoryFactory<TSnapshot>.Create(serviceProvider, entityFrameworkSnapshotRepositoyFactory) | ||
: entityFrameworkSnapshotRepositoyFactory; | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
src/EntityDb.EntityFramework/Predicates/PredicateBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using EntityDb.EntityFramework.Snapshots; | ||
using System.Linq.Expressions; | ||
|
||
namespace EntityDb.EntityFramework.Predicates; | ||
|
||
/// <summary> | ||
/// Based on http://www.albahari.com/nutshell/predicatebuilder.aspx | ||
/// </summary> | ||
internal static class PredicateExpressionBuilder | ||
{ | ||
private static Expression<Func<T, bool>> False<T>() | ||
{ | ||
return _ => false; | ||
} | ||
|
||
private static Expression<Func<T, bool>> Or<T> | ||
( | ||
Expression<Func<T, bool>> left, | ||
Expression<Func<T, bool>> right | ||
) | ||
{ | ||
return Expression.Lambda<Func<T, bool>> | ||
( | ||
Expression.OrElse | ||
( | ||
left.Body, | ||
Expression.Invoke(right, left.Parameters) | ||
), | ||
left.Parameters | ||
); | ||
} | ||
|
||
public static Expression<Func<T, bool>> Or<I, T> | ||
( | ||
IEnumerable<I> inputs, | ||
Func<I, Expression<Func<T, bool>>> mapper | ||
) | ||
{ | ||
return inputs.Aggregate(False<T>(), (predicate, input) => Or(predicate, mapper.Invoke(input))); | ||
} | ||
} |
Oops, something went wrong.