Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Entity Framework #500

Closed
wants to merge 11 commits into from
17 changes: 17 additions & 0 deletions EventFlow.sln
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventFlow.DependencyInjecti
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DependencyInjection", "DependencyInjection", "{E2DD51BC-974B-4526-961B-825913911FFB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EntityFramework", "EntityFramework", "{57948D64-F072-4FB7-858E-A63366C55645}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventFlow.EntityFramework.Tests", "Source\EventFlow.EntityFramework.Tests\EventFlow.EntityFramework.Tests.csproj", "{DB2BF39F-9225-4697-89AF-A6A95629A109}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventFlow.EntityFramework", "Source\EventFlow.EntityFramework\EventFlow.EntityFramework.csproj", "{5B9A10E8-E6AF-4A70-9594-187123293396}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -193,6 +199,14 @@ Global
{C16B0B27-6E6C-439C-8419-488B2D4D798F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C16B0B27-6E6C-439C-8419-488B2D4D798F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C16B0B27-6E6C-439C-8419-488B2D4D798F}.Release|Any CPU.Build.0 = Release|Any CPU
{DB2BF39F-9225-4697-89AF-A6A95629A109}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DB2BF39F-9225-4697-89AF-A6A95629A109}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB2BF39F-9225-4697-89AF-A6A95629A109}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DB2BF39F-9225-4697-89AF-A6A95629A109}.Release|Any CPU.Build.0 = Release|Any CPU
{5B9A10E8-E6AF-4A70-9594-187123293396}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B9A10E8-E6AF-4A70-9594-187123293396}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B9A10E8-E6AF-4A70-9594-187123293396}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B9A10E8-E6AF-4A70-9594-187123293396}.Release|Any CPU.Build.0 = Release|Any CPU
{7870461F-BA5B-4198-A85E-03BA555198AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7870461F-BA5B-4198-A85E-03BA555198AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7870461F-BA5B-4198-A85E-03BA555198AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -231,6 +245,9 @@ Global
{EFDD4B74-E69A-4F4F-80F4-DC8F32CD76A5} = {980EEDAA-1FEF-4D7C-8811-5EF1D9729773}
{9D57E096-AA8E-40E5-A1CC-32C07B425850} = {3A34F6A2-64B0-4730-A0EB-D324BC755538}
{C16B0B27-6E6C-439C-8419-488B2D4D798F} = {3A34F6A2-64B0-4730-A0EB-D324BC755538}
{57948D64-F072-4FB7-858E-A63366C55645} = {92F3C263-8C0C-4D12-B41A-452E48D2E5E8}
{DB2BF39F-9225-4697-89AF-A6A95629A109} = {57948D64-F072-4FB7-858E-A63366C55645}
{5B9A10E8-E6AF-4A70-9594-187123293396} = {57948D64-F072-4FB7-858E-A63366C55645}
{7870461F-BA5B-4198-A85E-03BA555198AD} = {E2DD51BC-974B-4526-961B-825913911FFB}
{28CA7AD8-6A73-454C-ABF4-26B24D986C85} = {E2DD51BC-974B-4526-961B-825913911FFB}
EndGlobalSection
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using EventFlow.Configuration;
using EventFlow.EntityFramework.Extensions;
using EventFlow.EntityFramework.Tests.InMemory;
using EventFlow.EntityFramework.Tests.Model;
using EventFlow.Extensions;
using EventFlow.TestHelpers.Aggregates.Entities;

namespace EventFlow.EntityFramework.Tests
{
public static class EntityFrameworkTestExtensions
{
public static IEventFlowOptions ConfigureForEventStoreTest<TDbContextProvider>(this IEventFlowOptions options)
where TDbContextProvider : class, IDbContextProvider<TestDbContext>
{
return options
.ConfigureEntityFramework()
.AddDbContextProvider<TestDbContext, TDbContextProvider>(Lifetime.Singleton)
.UseEntityFrameworkEventStore<TestDbContext>();
}

public static IEventFlowOptions ConfigureForSnapshotStoreTest<TDbContextProvider>(this IEventFlowOptions options)
where TDbContextProvider : class, IDbContextProvider<TestDbContext>
{
return options
.ConfigureEntityFramework()
.AddDbContextProvider<TestDbContext, TDbContextProvider>(Lifetime.Singleton)
.UseEntityFrameworkSnapshotStore<TestDbContext>();
}

public static IEventFlowOptions ConfigureForReadStoreTest<TDbContextProvider>(this IEventFlowOptions options)
where TDbContextProvider : class, IDbContextProvider<TestDbContext>
{
return options
.RegisterServices(sr => sr.RegisterType(typeof(ThingyMessageLocator)))
.ConfigureEntityFramework()
.AddDbContextProvider<TestDbContext, TDbContextProvider>(Lifetime.Singleton)
.UseEntityFrameworkReadModel<ThingyReadModelEntity, TestDbContext>()
.UseEntityFrameworkReadModel<ThingyMessageReadModelEntity, TestDbContext, ThingyMessageLocator>()
.AddQueryHandlers(
typeof(EfThingyGetQueryHandler),
typeof(EfThingyGetVersionQueryHandler),
typeof(EfThingyGetMessagesQueryHandler));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" />
<PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
<PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\EventFlow.EntityFramework\EventFlow.EntityFramework.csproj" />
<ProjectReference Include="..\EventFlow.TestHelpers\EventFlow.TestHelpers.csproj" />
<ProjectReference Include="..\EventFlow\EventFlow.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using EventFlow.Configuration;
using EventFlow.TestHelpers;
using EventFlow.TestHelpers.Suites;
using NUnit.Framework;

namespace EventFlow.EntityFramework.Tests.InMemory
{
[Category(Categories.Integration)]
public class EfInMemoryEventStoreTests : TestSuiteForEventStore
{
protected override IRootResolver CreateRootResolver(IEventFlowOptions eventFlowOptions)
{
return eventFlowOptions
.ConfigureForEventStoreTest<InMemoryDbContextProvider>()
.CreateResolver();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using EventFlow.Configuration;
using EventFlow.EntityFramework.Tests.Model;
using EventFlow.TestHelpers;
using EventFlow.TestHelpers.Suites;
using NUnit.Framework;

namespace EventFlow.EntityFramework.Tests.InMemory
{
[Category(Categories.Integration)]
public class EfInMemoryReadStoreTests : TestSuiteForReadModelStore
{
protected override Type ReadModelType => typeof(ThingyReadModelEntity);

protected override IRootResolver CreateRootResolver(IEventFlowOptions eventFlowOptions)
{
return eventFlowOptions
.ConfigureForReadStoreTest<InMemoryDbContextProvider>()
.CreateResolver();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using EventFlow.Configuration;
using EventFlow.TestHelpers;
using EventFlow.TestHelpers.Suites;
using NUnit.Framework;

namespace EventFlow.EntityFramework.Tests.InMemory
{
[Category(Categories.Integration)]
public class EfInMemorySnapshotTests : TestSuiteForSnapshotStore
{
protected override IRootResolver CreateRootResolver(IEventFlowOptions eventFlowOptions)
{
return eventFlowOptions
.ConfigureForSnapshotStoreTest<InMemoryDbContextProvider>()
.CreateResolver();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using EventFlow.EntityFramework.Tests.InMemory.Infrastructure;
using EventFlow.EntityFramework.Tests.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;

namespace EventFlow.EntityFramework.Tests.InMemory
{
public class InMemoryDbContextProvider : IDbContextProvider<TestDbContext>
{
private readonly DbContextOptions<TestDbContext> _options;

public InMemoryDbContextProvider()
{
_options = new DbContextOptionsBuilder<TestDbContext>()
.UseInMemoryDatabase("EventFlowTest")
.ReplaceService<IInMemoryTableFactory, IndexingInMemoryTableFactory>()
.Options;
}

public TestDbContext CreateContext()
{
var context = new TestDbContext(_options);
context.Database.EnsureCreated();
return context;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Update;

namespace EventFlow.EntityFramework.Tests.InMemory.Infrastructure
{
public class IndexingInMemoryTable : IInMemoryTable
{
private readonly IIndex[] _indexDefinitions;
private readonly HashSet<IndexEntry>[] _indexes;
private readonly IInMemoryTable _innerTable;

public IndexingInMemoryTable(IInMemoryTable innerTable, IIndex[] indexDefinitions)
{
_innerTable = innerTable;
_indexDefinitions = indexDefinitions;
_indexes = _indexDefinitions.Select(i => new HashSet<IndexEntry>()).ToArray();
}

public IReadOnlyList<object[]> SnapshotRows()
{
return _innerTable.SnapshotRows();
}

public void Create(IUpdateEntry entry)
{
var indexEntries = _indexDefinitions
.Select(d => d.Properties.Select(entry.GetCurrentValue).ToArray())
.Select(values => new IndexEntry(values))
.ToArray();

if (indexEntries.Select((item, i) => _indexes[i].Contains(item)).Any(contains => contains))
throw new DbUpdateException("Error while updating.", new Exception("Unique constraint violated."));

_innerTable.Create(entry);

indexEntries.Select((item, i) => _indexes[i].Add(item)).ToArray();
}

public void Delete(IUpdateEntry entry)
{
_innerTable.Delete(entry);
}

public void Update(IUpdateEntry entry)
{
_innerTable.Update(entry);
}

private struct IndexEntry
{
private readonly object[] _values;

public IndexEntry(object[] values)
{
_values = values;
}

public bool Equals(IndexEntry other)
{
return StructuralComparisons.StructuralEqualityComparer.Equals(_values, other._values);
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
return obj is IndexEntry entry && Equals(entry);
}

public override int GetHashCode()
{
return StructuralComparisons.StructuralEqualityComparer.GetHashCode(_values);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Linq;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
using Microsoft.EntityFrameworkCore.Metadata;

namespace EventFlow.EntityFramework.Tests.InMemory.Infrastructure
{
public class IndexingInMemoryTableFactory : InMemoryTableFactory
{
public IndexingInMemoryTableFactory(ILoggingOptions loggingOptions) : base(loggingOptions)
{
}

public override IInMemoryTable Create(IEntityType entityType)
{
var innerTable = base.Create(entityType);
var uniqueIndexes = entityType.GetIndexes().Where(i => i.IsUnique).ToArray();

return uniqueIndexes.Any()
? new IndexingInMemoryTable(innerTable, uniqueIndexes)
: innerTable;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EventFlow.EntityFramework.Tests.Model;
using EventFlow.Queries;
using EventFlow.TestHelpers.Aggregates.Entities;
using EventFlow.TestHelpers.Aggregates.Queries;
using Microsoft.EntityFrameworkCore;

namespace EventFlow.EntityFramework.Tests
{
public class
EfThingyGetMessagesQueryHandler : IQueryHandler<ThingyGetMessagesQuery, IReadOnlyCollection<ThingyMessage>>
{
private readonly IDbContextProvider<TestDbContext> _dbContextProvider;

public EfThingyGetMessagesQueryHandler(IDbContextProvider<TestDbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
}

public async Task<IReadOnlyCollection<ThingyMessage>> ExecuteQueryAsync(ThingyGetMessagesQuery query,
CancellationToken cancellationToken)
{
using (var context = _dbContextProvider.CreateContext())
{
var entities = await context.ThingyMessages
.Where(m => m.ThingyId == query.ThingyId.Value)
.Select(m => m.ToThingyMessage())
.ToArrayAsync(cancellationToken);
return entities;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Threading;
using System.Threading.Tasks;
using EventFlow.Queries;
using EventFlow.TestHelpers.Aggregates;
using EventFlow.TestHelpers.Aggregates.Queries;

namespace EventFlow.EntityFramework.Tests.Model
{
public class EfThingyGetQueryHandler : IQueryHandler<ThingyGetQuery, Thingy>
{
private readonly IDbContextProvider<TestDbContext> _dbContextProvider;

public EfThingyGetQueryHandler(IDbContextProvider<TestDbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
}

public async Task<Thingy> ExecuteQueryAsync(ThingyGetQuery query, CancellationToken cancellationToken)
{
using (var context = _dbContextProvider.CreateContext())
{
var entity = await context.Thingys.FindAsync(query.ThingyId.Value);
return entity?.ToThingy();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Threading;
using System.Threading.Tasks;
using EventFlow.Queries;
using EventFlow.TestHelpers.Aggregates.Queries;

namespace EventFlow.EntityFramework.Tests.Model
{
public class EfThingyGetVersionQueryHandler : IQueryHandler<ThingyGetVersionQuery, long?>
{
private readonly IDbContextProvider<TestDbContext> _dbContextProvider;

public EfThingyGetVersionQueryHandler(IDbContextProvider<TestDbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
}

public async Task<long?> ExecuteQueryAsync(ThingyGetVersionQuery query, CancellationToken cancellationToken)
{
using (var context = _dbContextProvider.CreateContext())
{
var entity = await context.Thingys.FindAsync(query.ThingyId.Value);
return entity?.Version;
}
}
}
}
Loading