@ -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.Metadata.Builders; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
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 | |||
{ | |||
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 | |||
{ | |||
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.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 | |||
{ | |||
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 | |||
{ | |||
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(); | |||
} | |||
}); | |||
} | |||
} | |||
} |