-
-
Notifications
You must be signed in to change notification settings - Fork 506
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Core.EntityFramework project with EntityFrameworkProjection
Implementation is made accordingly to MartenElasticSearchProjection
- Loading branch information
1 parent
7333c8f
commit 5f39e5c
Showing
6 changed files
with
129 additions
and
5 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" /> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.4"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Core\Core.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
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,98 @@ | ||
using Core.Events; | ||
using Core.Reflection; | ||
using Microsoft.EntityFrameworkCore; | ||
using Polly; | ||
|
||
namespace Core.EntityFramework; | ||
|
||
public class EntityFrameworkProjection<TDbContext>: IEventBatchHandler | ||
where TDbContext : DbContext | ||
{ | ||
protected readonly TDbContext DBContext; | ||
protected IAsyncPolicy RetryPolicy { get; } | ||
private readonly HashSet<Type> handledEventTypes = []; | ||
|
||
public EntityFrameworkProjection(TDbContext dbContext, IAsyncPolicy? retryPolicy = null) | ||
{ | ||
DBContext = dbContext; | ||
RetryPolicy = retryPolicy ?? Policy.NoOpAsync(); | ||
} | ||
|
||
protected void Projects<TEvent>() => | ||
handledEventTypes.Add(typeof(TEvent)); | ||
|
||
public async Task Handle(IEventEnvelope[] eventInEnvelopes, CancellationToken ct) | ||
{ | ||
var events = eventInEnvelopes | ||
.Where(@event => handledEventTypes.Contains(@event.Data.GetType())) | ||
.ToArray(); | ||
|
||
await ApplyAsync(events, ct); | ||
} | ||
|
||
protected virtual Task ApplyAsync(IEventEnvelope[] events, CancellationToken ct) => | ||
ApplyAsync(events.Select(@event => @event.Data).ToArray(), ct); | ||
|
||
protected virtual Task ApplyAsync(object[] events, CancellationToken ct) => | ||
Task.CompletedTask; | ||
} | ||
|
||
public abstract class EntityFrameworkProjection<TDocument, TDbContext>( | ||
TDbContext context, | ||
IAsyncPolicy retryPolicy | ||
): EntityFrameworkProjection<TDbContext>(context, retryPolicy) | ||
where TDocument : class | ||
where TDbContext : DbContext | ||
{ | ||
private record ProjectEvent( | ||
Func<object, string> GetId, | ||
Func<TDocument, object, TDocument> Apply | ||
); | ||
|
||
private readonly Dictionary<Type, ProjectEvent> projectors = new(); | ||
private Func<TDocument, object> getDocumentId = default!; | ||
|
||
protected void Projects<TEvent>( | ||
Func<TEvent, string> getId, | ||
Func<TDocument, TEvent, TDocument> apply | ||
) | ||
{ | ||
projectors.Add( | ||
typeof(TEvent), | ||
new ProjectEvent( | ||
@event => getId((TEvent)@event), | ||
(document, @event) => apply(document, (TEvent)@event) | ||
) | ||
); | ||
Projects<TEvent>(); | ||
} | ||
|
||
protected void DocumentId(Func<TDocument, object> documentId) => | ||
getDocumentId = documentId; | ||
|
||
protected override Task ApplyAsync(object[] events, CancellationToken token) => | ||
RetryPolicy.ExecuteAsync(async ct => | ||
{ | ||
var ids = events.Select(GetDocumentId).ToList(); | ||
var entities = await DBContext.Set<TDocument>() | ||
.Where(x => ids.Contains(getDocumentId(x))) | ||
.ToListAsync(cancellationToken: ct); | ||
var existingDocuments = entities.ToDictionary(ks => getDocumentId(ks), vs => vs); | ||
for(var i = 0; i < events.Length; i++) | ||
{ | ||
Apply(existingDocuments.GetValueOrDefault(ids[i], GetDefault(events[i])), events[i]); | ||
} | ||
}, token); | ||
|
||
protected virtual TDocument GetDefault(object @event) => | ||
ObjectFactory<TDocument>.GetDefaultOrUninitialized(); | ||
|
||
private TDocument Apply(TDocument document, object @event) => | ||
projectors[@event.GetType()].Apply(document, @event); | ||
|
||
private object GetDocumentId(object @event) => | ||
projectors[@event.GetType()].GetId(@event); | ||
} |
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
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