@ -1,13 +1,9 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF | |||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF | |||||
{ | { | ||||
public enum EventStateEnum | |||||
{ | |||||
NotPublished = 0, | |||||
Published = 1, | |||||
PublishedFailed = 2 | |||||
} | |||||
public enum EventStateEnum | |||||
{ | |||||
NotPublished = 0, | |||||
Published = 1, | |||||
PublishedFailed = 2 | |||||
} | |||||
} | } |
@ -1,48 +1,45 @@ | |||||
using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||
using Microsoft.EntityFrameworkCore.Metadata.Builders; | using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF | namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF | ||||
{ | { | ||||
public class IntegrationEventLogContext : DbContext | |||||
{ | |||||
public IntegrationEventLogContext(DbContextOptions<IntegrationEventLogContext> options) : base(options) | |||||
{ | |||||
} | |||||
public class IntegrationEventLogContext : DbContext | |||||
{ | |||||
public IntegrationEventLogContext(DbContextOptions<IntegrationEventLogContext> options) : base(options) | |||||
{ | |||||
} | |||||
public DbSet<IntegrationEventLogEntry> IntegrationEventLogs { get; set; } | |||||
public DbSet<IntegrationEventLogEntry> IntegrationEventLogs { get; set; } | |||||
protected override void OnModelCreating(ModelBuilder builder) | |||||
{ | |||||
builder.Entity<IntegrationEventLogEntry>(ConfigureIntegrationEventLogEntry); | |||||
} | |||||
protected override void OnModelCreating(ModelBuilder builder) | |||||
{ | |||||
builder.Entity<IntegrationEventLogEntry>(ConfigureIntegrationEventLogEntry); | |||||
} | |||||
void ConfigureIntegrationEventLogEntry(EntityTypeBuilder<IntegrationEventLogEntry> builder) | |||||
{ | |||||
builder.ToTable("IntegrationEventLog"); | |||||
void ConfigureIntegrationEventLogEntry(EntityTypeBuilder<IntegrationEventLogEntry> builder) | |||||
{ | |||||
builder.ToTable("IntegrationEventLog"); | |||||
builder.HasKey(e => e.EventId); | |||||
builder.HasKey(e => e.EventId); | |||||
builder.Property(e => e.EventId) | |||||
.IsRequired(); | |||||
builder.Property(e => e.EventId) | |||||
.IsRequired(); | |||||
builder.Property(e => e.Content) | |||||
.IsRequired(); | |||||
builder.Property(e => e.Content) | |||||
.IsRequired(); | |||||
builder.Property(e => e.CreationTime) | |||||
.IsRequired(); | |||||
builder.Property(e => e.CreationTime) | |||||
.IsRequired(); | |||||
builder.Property(e => e.State) | |||||
.IsRequired(); | |||||
builder.Property(e => e.State) | |||||
.IsRequired(); | |||||
builder.Property(e => e.TimesSent) | |||||
.IsRequired(); | |||||
builder.Property(e => e.TimesSent) | |||||
.IsRequired(); | |||||
builder.Property(e => e.EventTypeName) | |||||
.IsRequired(); | |||||
builder.Property(e => e.EventTypeName) | |||||
.IsRequired(); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | } |
@ -1,28 +1,28 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using Newtonsoft.Json; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using DateTime = System.DateTime; | |||||
using Guid = System.Guid; | |||||
using JsonConvert = Newtonsoft.Json.JsonConvert; | |||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF | namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF | ||||
{ | { | ||||
public class IntegrationEventLogEntry | |||||
{ | |||||
private IntegrationEventLogEntry() { } | |||||
public IntegrationEventLogEntry(IntegrationEvent @event) | |||||
{ | |||||
EventId = @event.Id; | |||||
CreationTime = @event.CreationDate; | |||||
EventTypeName = @event.GetType().FullName; | |||||
Content = JsonConvert.SerializeObject(@event); | |||||
State = EventStateEnum.NotPublished; | |||||
TimesSent = 0; | |||||
} | |||||
public Guid EventId { get; private set; } | |||||
public string EventTypeName { get; private set; } | |||||
public EventStateEnum State { get; set; } | |||||
public int TimesSent { get; set; } | |||||
public DateTime CreationTime { get; private set; } | |||||
public string Content { get; private set; } | |||||
} | |||||
using IntegrationEvent = EventBus.Events.IntegrationEvent; | |||||
public class IntegrationEventLogEntry | |||||
{ | |||||
private IntegrationEventLogEntry() { } | |||||
public IntegrationEventLogEntry(IntegrationEvent @event) | |||||
{ | |||||
EventId = @event.Id; | |||||
CreationTime = @event.CreationDate; | |||||
EventTypeName = @event.GetType().FullName; | |||||
Content = JsonConvert.SerializeObject(@event); | |||||
State = EventStateEnum.NotPublished; | |||||
TimesSent = 0; | |||||
} | |||||
public Guid EventId { get; private set; } | |||||
public string EventTypeName { get; private set; } | |||||
public EventStateEnum State { get; set; } | |||||
public int TimesSent { get; set; } | |||||
public DateTime CreationTime { get; private set; } | |||||
public string Content { get; private set; } | |||||
} | |||||
} | } |
@ -1,15 +1,13 @@ | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Data.Common; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using DbTransaction = System.Data.Common.DbTransaction; | |||||
using Task = System.Threading.Tasks.Task; | |||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services | namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services | ||||
{ | { | ||||
public interface IIntegrationEventLogService | |||||
{ | |||||
Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction); | |||||
Task MarkEventAsPublishedAsync(IntegrationEvent @event); | |||||
} | |||||
using IntegrationEvent = EventBus.Events.IntegrationEvent; | |||||
public interface IIntegrationEventLogService | |||||
{ | |||||
Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction); | |||||
Task MarkEventAsPublishedAsync(IntegrationEvent @event); | |||||
} | |||||
} | } |
@ -1,52 +1,54 @@ | |||||
using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||
using Microsoft.EntityFrameworkCore.Diagnostics; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using System; | |||||
using System.Data.Common; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using static System.Linq.Enumerable; | |||||
using ArgumentNullException = System.ArgumentNullException; | |||||
using DbConnection = System.Data.Common.DbConnection; | |||||
using DbTransaction = System.Data.Common.DbTransaction; | |||||
using RelationalEventId = Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId; | |||||
using Task = System.Threading.Tasks.Task; | |||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services | namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services | ||||
{ | { | ||||
public class IntegrationEventLogService : IIntegrationEventLogService | |||||
{ | |||||
private readonly IntegrationEventLogContext _integrationEventLogContext; | |||||
private readonly DbConnection _dbConnection; | |||||
public IntegrationEventLogService(DbConnection dbConnection) | |||||
{ | |||||
_dbConnection = dbConnection ?? throw new ArgumentNullException(nameof(dbConnection)); | |||||
_integrationEventLogContext = new IntegrationEventLogContext( | |||||
new DbContextOptionsBuilder<IntegrationEventLogContext>() | |||||
.UseSqlServer(_dbConnection) | |||||
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)) | |||||
.Options); | |||||
} | |||||
public Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction) | |||||
{ | |||||
if (transaction == null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(transaction), $"A {typeof(DbTransaction).FullName} is required as a pre-requisite to save the event."); | |||||
} | |||||
var eventLogEntry = new IntegrationEventLogEntry(@event); | |||||
_integrationEventLogContext.Database.UseTransaction(transaction); | |||||
_integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry); | |||||
return _integrationEventLogContext.SaveChangesAsync(); | |||||
} | |||||
public Task MarkEventAsPublishedAsync(IntegrationEvent @event) | |||||
{ | |||||
var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id); | |||||
eventLogEntry.TimesSent++; | |||||
eventLogEntry.State = EventStateEnum.Published; | |||||
_integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry); | |||||
return _integrationEventLogContext.SaveChangesAsync(); | |||||
} | |||||
} | |||||
using IntegrationEvent = EventBus.Events.IntegrationEvent; | |||||
public class IntegrationEventLogService : IIntegrationEventLogService | |||||
{ | |||||
private readonly IntegrationEventLogContext _integrationEventLogContext; | |||||
private readonly DbConnection _dbConnection; | |||||
public IntegrationEventLogService(DbConnection dbConnection) | |||||
{ | |||||
_dbConnection = dbConnection ?? throw new ArgumentNullException(nameof(dbConnection)); | |||||
_integrationEventLogContext = new IntegrationEventLogContext( | |||||
new DbContextOptionsBuilder<IntegrationEventLogContext>() | |||||
.UseSqlServer(_dbConnection) | |||||
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)) | |||||
.Options); | |||||
} | |||||
public Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction) | |||||
{ | |||||
if (transaction == null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(transaction), $"A {typeof(DbTransaction).FullName} is required as a pre-requisite to save the event."); | |||||
} | |||||
var eventLogEntry = new IntegrationEventLogEntry(@event); | |||||
_integrationEventLogContext.Database.UseTransaction(transaction); | |||||
_integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry); | |||||
return _integrationEventLogContext.SaveChangesAsync(); | |||||
} | |||||
public Task MarkEventAsPublishedAsync(IntegrationEvent @event) | |||||
{ | |||||
var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id); | |||||
eventLogEntry.TimesSent++; | |||||
eventLogEntry.State = EventStateEnum.Published; | |||||
_integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry); | |||||
return _integrationEventLogContext.SaveChangesAsync(); | |||||
} | |||||
} | |||||
} | } |
@ -1,37 +1,35 @@ | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.EntityFrameworkCore.Storage; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Data.Common; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using static Microsoft.EntityFrameworkCore.ExecutionStrategyExtensions; | |||||
using ArgumentNullException = System.ArgumentNullException; | |||||
using DbContext = Microsoft.EntityFrameworkCore.DbContext; | |||||
using Task = System.Threading.Tasks.Task; | |||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities | namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities | ||||
{ | { | ||||
public class ResilientTransaction | |||||
{ | |||||
private DbContext _context; | |||||
private ResilientTransaction(DbContext context) => | |||||
_context = context ?? throw new ArgumentNullException(nameof(context)); | |||||
using FuncOfTask = System.Func<Task>; | |||||
public class ResilientTransaction | |||||
{ | |||||
private DbContext _context; | |||||
private ResilientTransaction(DbContext context) => | |||||
_context = context ?? throw new ArgumentNullException(nameof(context)); | |||||
public static ResilientTransaction New (DbContext context) => | |||||
new ResilientTransaction(context); | |||||
public static ResilientTransaction New(DbContext context) => | |||||
new ResilientTransaction(context); | |||||
public async Task ExecuteAsync(Func<Task> action) | |||||
{ | |||||
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): | |||||
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency | |||||
var strategy = _context.Database.CreateExecutionStrategy(); | |||||
await strategy.ExecuteAsync(async () => | |||||
{ | |||||
using (var transaction = _context.Database.BeginTransaction()) | |||||
{ | |||||
await action(); | |||||
transaction.Commit(); | |||||
} | |||||
}); | |||||
} | |||||
} | |||||
public async Task ExecuteAsync(FuncOfTask action) | |||||
{ | |||||
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): | |||||
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency | |||||
var strategy = _context.Database.CreateExecutionStrategy(); | |||||
await strategy.ExecuteAsync(async () => | |||||
{ | |||||
using (var transaction = _context.Database.BeginTransaction()) | |||||
{ | |||||
await action(); | |||||
transaction.Commit(); | |||||
} | |||||
}); | |||||
} | |||||
} | |||||
} | } |