From efeb4d1637c0b95ab13db0d5cad56af264d2a1f6 Mon Sep 17 00:00:00 2001 From: Oguzhan Soykan Date: Fri, 13 Oct 2017 15:21:19 +0300 Subject: [PATCH 01/11] Hangfire obsoleted job activator scope ported to new one. --- src/Stove.HangFire/Hangfire/AutofacJobActivator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stove.HangFire/Hangfire/AutofacJobActivator.cs b/src/Stove.HangFire/Hangfire/AutofacJobActivator.cs index e1c03fd..aa93c1b 100644 --- a/src/Stove.HangFire/Hangfire/AutofacJobActivator.cs +++ b/src/Stove.HangFire/Hangfire/AutofacJobActivator.cs @@ -46,7 +46,7 @@ public override object ActivateJob(Type jobType) return _lifetimeScope.Resolve(jobType); } - public override JobActivatorScope BeginScope() + public override JobActivatorScope BeginScope(JobActivatorContext context) { return new AutofacScope(_useTaggedLifetimeScope ? _lifetimeScope.BeginLifetimeScope(LifetimeScopeTag) From 65ffa205dc6cd0ef521b119dc0f6cd565549b448 Mon Sep 17 00:00:00 2001 From: osoykan Date: Sat, 14 Oct 2017 21:29:10 +0300 Subject: [PATCH 02/11] nuget packes updated --- src/Stove.RabbitMQ/Stove.RabbitMQ.csproj | 6 +++--- test/Stove.Demo.WebApi.Core/Stove.Demo.WebApi.Core.csproj | 4 ++-- .../Stove.EntityFrameworkCore.Dapper.Tests.csproj | 2 +- .../Stove.EntityFrameworkCore.Tests.csproj | 2 +- test/Stove.Hangfire.Tests/Stove.Hangfire.Tests.csproj | 2 +- test/Stove.Mapster.Tests/Stove.Mapster.Tests.csproj | 2 +- test/Stove.NLog.Tests/Stove.NLog.Tests.csproj | 2 +- test/Stove.RabbitMQ.Tests/Stove.RabbitMQ.Tests.csproj | 2 +- test/Stove.Redis.Tests/Stove.Redis.Tests.csproj | 2 +- test/Stove.Serilog.Tests/Stove.Serilog.Tests.csproj | 2 +- test/Stove.Tests/Stove.Tests.csproj | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Stove.RabbitMQ/Stove.RabbitMQ.csproj b/src/Stove.RabbitMQ/Stove.RabbitMQ.csproj index 20281c8..8729d89 100644 --- a/src/Stove.RabbitMQ/Stove.RabbitMQ.csproj +++ b/src/Stove.RabbitMQ/Stove.RabbitMQ.csproj @@ -27,9 +27,9 @@ - - - + + + diff --git a/test/Stove.Demo.WebApi.Core/Stove.Demo.WebApi.Core.csproj b/test/Stove.Demo.WebApi.Core/Stove.Demo.WebApi.Core.csproj index ee70e24..5bae8a0 100644 --- a/test/Stove.Demo.WebApi.Core/Stove.Demo.WebApi.Core.csproj +++ b/test/Stove.Demo.WebApi.Core/Stove.Demo.WebApi.Core.csproj @@ -12,13 +12,13 @@ - + - + diff --git a/test/Stove.EntityFrameworkCore.Dapper.Tests/Stove.EntityFrameworkCore.Dapper.Tests.csproj b/test/Stove.EntityFrameworkCore.Dapper.Tests/Stove.EntityFrameworkCore.Dapper.Tests.csproj index a32d47c..3ecd778 100644 --- a/test/Stove.EntityFrameworkCore.Dapper.Tests/Stove.EntityFrameworkCore.Dapper.Tests.csproj +++ b/test/Stove.EntityFrameworkCore.Dapper.Tests/Stove.EntityFrameworkCore.Dapper.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/test/Stove.EntityFrameworkCore.Tests/Stove.EntityFrameworkCore.Tests.csproj b/test/Stove.EntityFrameworkCore.Tests/Stove.EntityFrameworkCore.Tests.csproj index 14d9f94..8bb117a 100644 --- a/test/Stove.EntityFrameworkCore.Tests/Stove.EntityFrameworkCore.Tests.csproj +++ b/test/Stove.EntityFrameworkCore.Tests/Stove.EntityFrameworkCore.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/test/Stove.Hangfire.Tests/Stove.Hangfire.Tests.csproj b/test/Stove.Hangfire.Tests/Stove.Hangfire.Tests.csproj index 1bb7192..2425ef4 100644 --- a/test/Stove.Hangfire.Tests/Stove.Hangfire.Tests.csproj +++ b/test/Stove.Hangfire.Tests/Stove.Hangfire.Tests.csproj @@ -22,7 +22,7 @@ - + diff --git a/test/Stove.Mapster.Tests/Stove.Mapster.Tests.csproj b/test/Stove.Mapster.Tests/Stove.Mapster.Tests.csproj index 94b37fb..cb7235a 100644 --- a/test/Stove.Mapster.Tests/Stove.Mapster.Tests.csproj +++ b/test/Stove.Mapster.Tests/Stove.Mapster.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/test/Stove.NLog.Tests/Stove.NLog.Tests.csproj b/test/Stove.NLog.Tests/Stove.NLog.Tests.csproj index ab9ec8e..048a0cd 100644 --- a/test/Stove.NLog.Tests/Stove.NLog.Tests.csproj +++ b/test/Stove.NLog.Tests/Stove.NLog.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/test/Stove.RabbitMQ.Tests/Stove.RabbitMQ.Tests.csproj b/test/Stove.RabbitMQ.Tests/Stove.RabbitMQ.Tests.csproj index db8ffcc..f48604c 100644 --- a/test/Stove.RabbitMQ.Tests/Stove.RabbitMQ.Tests.csproj +++ b/test/Stove.RabbitMQ.Tests/Stove.RabbitMQ.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/test/Stove.Redis.Tests/Stove.Redis.Tests.csproj b/test/Stove.Redis.Tests/Stove.Redis.Tests.csproj index 2076281..34334e4 100644 --- a/test/Stove.Redis.Tests/Stove.Redis.Tests.csproj +++ b/test/Stove.Redis.Tests/Stove.Redis.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/test/Stove.Serilog.Tests/Stove.Serilog.Tests.csproj b/test/Stove.Serilog.Tests/Stove.Serilog.Tests.csproj index eb74c81..a457c6d 100644 --- a/test/Stove.Serilog.Tests/Stove.Serilog.Tests.csproj +++ b/test/Stove.Serilog.Tests/Stove.Serilog.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/test/Stove.Tests/Stove.Tests.csproj b/test/Stove.Tests/Stove.Tests.csproj index e26feea..4d84893 100644 --- a/test/Stove.Tests/Stove.Tests.csproj +++ b/test/Stove.Tests/Stove.Tests.csproj @@ -11,7 +11,7 @@ - + From eb3d74da9482bc8fe6edb163d47ae2b43788b78d Mon Sep 17 00:00:00 2001 From: osoykan Date: Sun, 15 Oct 2017 01:57:17 +0300 Subject: [PATCH 03/11] CancellationToken support added for UnitOfWork async calls #96 (fixed) NHibernate updated to 5.0.0 #95 (fixed) --- .../EntityFramework/StoveDbContext.cs | 676 ++++++++-------- .../EntityFramework/Uow/EfUnitOfWork.cs | 13 +- .../EntityFrameworkCore/StoveDbContext.cs | 614 +++++++------- .../Uow/EfCoreUnitOfWork.cs | 336 ++++---- .../NHibernate/SessionExtensions.cs | 19 + .../NHibernate/Uow/NhUnitOfWork.cs | 22 +- src/Stove.NHibernate/Stove.NHibernate.csproj | 2 +- .../RavenDB/Uow/RavenDBUnitOfWork.cs | 7 +- src/Stove/Domain/Uow/IActiveUnitOfWork.cs | 3 +- .../Domain/Uow/IUnitOfWorkCompleteHandle.cs | 19 +- .../Uow/InnerUnitOfWorkCompleteHandle.cs | 3 +- src/Stove/Domain/Uow/NullUnitOfWork.cs | 7 +- src/Stove/Domain/Uow/UnitOfWorkBase.cs | 751 +++++++++--------- .../Bus/Entities/EntityChangeEventHelper.cs | 38 +- .../Bus/Entities/IEntityChangeEventHelper.cs | 11 +- .../Entities/NullEntityChangeEventHelper.cs | 95 +-- .../Tests/Repository_Tests.cs | 66 +- .../General_Repository_Tests.cs | 46 +- .../StoveNHibernateTestBase.cs | 10 +- .../Uow/Uow_Events_Tests.cs | 53 +- 20 files changed, 1460 insertions(+), 1331 deletions(-) create mode 100644 src/Stove.NHibernate/NHibernate/SessionExtensions.cs diff --git a/src/Stove.EntityFramework/EntityFramework/StoveDbContext.cs b/src/Stove.EntityFramework/EntityFramework/StoveDbContext.cs index 02f54cc..acd7ca3 100644 --- a/src/Stove.EntityFramework/EntityFramework/StoveDbContext.cs +++ b/src/Stove.EntityFramework/EntityFramework/StoveDbContext.cs @@ -31,342 +31,342 @@ namespace Stove.EntityFramework { - /// - /// - /// Base class for all DbContext classes in the application. - /// - public abstract class StoveDbContext : DbContext, ITransientDependency, IStartable - { - /// - /// - /// Constructor. - /// Uses as connection - /// string. - /// - protected StoveDbContext() - { - InitializeDbContext(); - } - - /// - /// - /// Constructor. - /// - protected StoveDbContext(string nameOrConnectionString) - : base(nameOrConnectionString) - { - InitializeDbContext(); - } - - /// - /// - /// Constructor. - /// - protected StoveDbContext(DbCompiledModel model) - : base(model) - { - InitializeDbContext(); - } - - /// - /// - /// Constructor. - /// - protected StoveDbContext(DbConnection existingConnection, bool contextOwnsConnection) - : base(existingConnection, contextOwnsConnection) - { - InitializeDbContext(); - } - - /// - /// - /// Constructor. - /// - protected StoveDbContext(string nameOrConnectionString, DbCompiledModel model) - : base(nameOrConnectionString, model) - { - InitializeDbContext(); - } - - /// - /// - /// Constructor. - /// - protected StoveDbContext(ObjectContext objectContext, bool dbContextOwnsObjectContext) - : base(objectContext, dbContextOwnsObjectContext) - { - InitializeDbContext(); - } - - /// - /// - /// Constructor. - /// - protected StoveDbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection) - : base(existingConnection, model, contextOwnsConnection) - { - InitializeDbContext(); - } - - /// - /// Used to get current session values. - /// - public IStoveSession StoveSession { get; set; } - - /// - /// Used to trigger entity change events. - /// - public IEntityChangeEventHelper EntityChangeEventHelper { get; set; } - - /// - /// Reference to the logger. - /// - public ILogger Logger { get; set; } - - /// - /// Reference to the event bus. - /// - public IEventBus EventBus { get; set; } - - /// - /// Reference to GUID generator. - /// - public IGuidGenerator GuidGenerator { get; set; } - - /// - /// Reference to the current UOW provider. - /// - public ICurrentUnitOfWorkProvider CurrentUnitOfWorkProvider { get; set; } - - public virtual void Start() - { - Database.Initialize(false); - } - - private void InitializeDbContext() - { - SetNullsForInjectedProperties(); - RegisterToChanges(); - } - - private void RegisterToChanges() - { - ((IObjectContextAdapter)this) - .ObjectContext - .ObjectStateManager - .ObjectStateManagerChanged += ObjectStateManager_ObjectStateManagerChanged; - } - - protected virtual void ObjectStateManager_ObjectStateManagerChanged(object sender, CollectionChangeEventArgs e) - { - var contextAdapter = (IObjectContextAdapter)this; - if (e.Action != CollectionChangeAction.Add) - { - return; - } - - ObjectStateEntry entry = contextAdapter.ObjectContext.ObjectStateManager.GetObjectStateEntry(e.Element); - switch (entry.State) - { - case EntityState.Added: - CheckAndSetId(entry.Entity); - SetCreationAuditProperties(entry.Entity, GetAuditUserId()); - break; - } - } - - private void SetNullsForInjectedProperties() - { - Logger = NullLogger.Instance; - StoveSession = NullStoveSession.Instance; - EntityChangeEventHelper = NullEntityChangeEventHelper.Instance; - GuidGenerator = SequentialGuidGenerator.Instance; - EventBus = NullEventBus.Instance; - } - - protected override void OnModelCreating(DbModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - modelBuilder.Filter(StoveDataFilters.SoftDelete, (ISoftDelete d) => d.IsDeleted, false); - } - - public override int SaveChanges() - { - try - { - EntityChangeReport changedEntities = ApplyStoveConcepts(); - int result = base.SaveChanges(); - EntityChangeEventHelper.TriggerEvents(changedEntities); - return result; - } - catch (DbEntityValidationException ex) - { - LogDbEntityValidationException(ex); - throw; - } - } - - public override async Task SaveChangesAsync(CancellationToken cancellationToken) - { - try - { - EntityChangeReport changeReport = ApplyStoveConcepts(); - int result = await base.SaveChangesAsync(cancellationToken); - await EntityChangeEventHelper.TriggerEventsAsync(changeReport); - return result; - } - catch (DbEntityValidationException ex) - { - LogDbEntityValidationException(ex); - throw; - } - } - - protected virtual EntityChangeReport ApplyStoveConcepts() - { - var changeReport = new EntityChangeReport(); - - long? userId = GetAuditUserId(); - - List entries = ChangeTracker.Entries().ToList(); - foreach (DbEntityEntry entry in entries) - { - switch (entry.State) - { - case EntityState.Added: - CheckAndSetId(entry.Entity); - SetCreationAuditProperties(entry.Entity, userId); - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Created)); - break; - case EntityState.Modified: - SetModificationAuditProperties(entry, userId); - if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) - { - SetDeletionAuditProperties(entry.Entity, userId); - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); - } - else - { - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Updated)); - } - - break; - case EntityState.Deleted: - CancelDeletionForSoftDelete(entry); - SetDeletionAuditProperties(entry.Entity, userId); - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); - break; - } - - AddDomainEvents(changeReport.DomainEvents, entry.Entity); - } - - return changeReport; - } - - protected virtual void AddDomainEvents(List domainEvents, object entityAsObj) - { - if (!(entityAsObj is IGeneratesDomainEvents generatesDomainEventsEntity)) - { - return; - } - - if (generatesDomainEventsEntity.DomainEvents.IsNullOrEmpty()) - { - return; - } - - domainEvents.AddRange(generatesDomainEventsEntity.DomainEvents.Select(eventData => new DomainEventEntry(entityAsObj, eventData))); - generatesDomainEventsEntity.DomainEvents.Clear(); - } - - protected virtual void CheckAndSetId(object entityAsObj) - { - if (entityAsObj is IEntity entity && entity.Id == Guid.Empty) - { - Type entityType = ObjectContext.GetObjectType(entityAsObj.GetType()); - PropertyInfo idProperty = entityType.GetProperty("Id"); - var dbGeneratedAttr = ReflectionHelper.GetSingleAttributeOrDefault(idProperty); - if (dbGeneratedAttr == null || dbGeneratedAttr.DatabaseGeneratedOption == DatabaseGeneratedOption.None) - { - entity.Id = GuidGenerator.Create(); - } - } - } - - protected virtual void SetCreationAuditProperties(object entityAsObj, long? userId) - { - EntityAuditingHelper.SetCreationAuditProperties(entityAsObj, userId); - } - - protected virtual void SetModificationAuditProperties(DbEntityEntry entry, long? userId) - { - EntityAuditingHelper.SetModificationAuditProperties(entry.Entity, userId); - } - - protected virtual void CancelDeletionForSoftDelete(DbEntityEntry entry) - { - if (!(entry.Entity is ISoftDelete)) - { - return; - } - - DbEntityEntry softDeleteEntry = entry.Cast(); - softDeleteEntry.Reload(); - softDeleteEntry.State = EntityState.Modified; - softDeleteEntry.Entity.IsDeleted = true; - } - - protected virtual void SetDeletionAuditProperties(object entityAsObj, long? userId) - { - if (entityAsObj is IHasDeletionTime) - { - var entity = entityAsObj.As(); - - if (entity.DeletionTime == null) - { - entity.DeletionTime = Clock.Now; - } - } - - if (entityAsObj is IDeletionAudited) - { - var entity = entityAsObj.As(); - - if (entity.DeleterUserId != null) - { - return; - } - - if (userId == null) - { - entity.DeleterUserId = null; - return; - } - - entity.DeleterUserId = userId; - } - } - - protected virtual void LogDbEntityValidationException(DbEntityValidationException exception) - { - Logger.Error("There are some validation errors while saving changes in EntityFramework:"); - foreach (DbValidationError ve in exception.EntityValidationErrors.SelectMany(eve => eve.ValidationErrors)) - { - Logger.Error(" - " + ve.PropertyName + ": " + ve.ErrorMessage); - } - } - - protected virtual long? GetAuditUserId() - { - if (StoveSession.UserId.HasValue && - CurrentUnitOfWorkProvider != null && - CurrentUnitOfWorkProvider.Current != null) - { - return StoveSession.UserId; - } - - return null; - } - } + /// + /// + /// Base class for all DbContext classes in the application. + /// + public abstract class StoveDbContext : DbContext, ITransientDependency, IStartable + { + /// + /// + /// Constructor. + /// Uses as connection + /// string. + /// + protected StoveDbContext() + { + InitializeDbContext(); + } + + /// + /// + /// Constructor. + /// + protected StoveDbContext(string nameOrConnectionString) + : base(nameOrConnectionString) + { + InitializeDbContext(); + } + + /// + /// + /// Constructor. + /// + protected StoveDbContext(DbCompiledModel model) + : base(model) + { + InitializeDbContext(); + } + + /// + /// + /// Constructor. + /// + protected StoveDbContext(DbConnection existingConnection, bool contextOwnsConnection) + : base(existingConnection, contextOwnsConnection) + { + InitializeDbContext(); + } + + /// + /// + /// Constructor. + /// + protected StoveDbContext(string nameOrConnectionString, DbCompiledModel model) + : base(nameOrConnectionString, model) + { + InitializeDbContext(); + } + + /// + /// + /// Constructor. + /// + protected StoveDbContext(ObjectContext objectContext, bool dbContextOwnsObjectContext) + : base(objectContext, dbContextOwnsObjectContext) + { + InitializeDbContext(); + } + + /// + /// + /// Constructor. + /// + protected StoveDbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection) + : base(existingConnection, model, contextOwnsConnection) + { + InitializeDbContext(); + } + + /// + /// Used to get current session values. + /// + public IStoveSession StoveSession { get; set; } + + /// + /// Used to trigger entity change events. + /// + public IEntityChangeEventHelper EntityChangeEventHelper { get; set; } + + /// + /// Reference to the logger. + /// + public ILogger Logger { get; set; } + + /// + /// Reference to the event bus. + /// + public IEventBus EventBus { get; set; } + + /// + /// Reference to GUID generator. + /// + public IGuidGenerator GuidGenerator { get; set; } + + /// + /// Reference to the current UOW provider. + /// + public ICurrentUnitOfWorkProvider CurrentUnitOfWorkProvider { get; set; } + + public virtual void Start() + { + Database.Initialize(false); + } + + private void InitializeDbContext() + { + SetNullsForInjectedProperties(); + RegisterToChanges(); + } + + private void RegisterToChanges() + { + ((IObjectContextAdapter)this) + .ObjectContext + .ObjectStateManager + .ObjectStateManagerChanged += ObjectStateManager_ObjectStateManagerChanged; + } + + protected virtual void ObjectStateManager_ObjectStateManagerChanged(object sender, CollectionChangeEventArgs e) + { + var contextAdapter = (IObjectContextAdapter)this; + if (e.Action != CollectionChangeAction.Add) + { + return; + } + + ObjectStateEntry entry = contextAdapter.ObjectContext.ObjectStateManager.GetObjectStateEntry(e.Element); + switch (entry.State) + { + case EntityState.Added: + CheckAndSetId(entry.Entity); + SetCreationAuditProperties(entry.Entity, GetAuditUserId()); + break; + } + } + + private void SetNullsForInjectedProperties() + { + Logger = NullLogger.Instance; + StoveSession = NullStoveSession.Instance; + EntityChangeEventHelper = NullEntityChangeEventHelper.Instance; + GuidGenerator = SequentialGuidGenerator.Instance; + EventBus = NullEventBus.Instance; + } + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Filter(StoveDataFilters.SoftDelete, (ISoftDelete d) => d.IsDeleted, false); + } + + public override int SaveChanges() + { + try + { + EntityChangeReport changedEntities = ApplyStoveConcepts(); + int result = base.SaveChanges(); + EntityChangeEventHelper.TriggerEvents(changedEntities); + return result; + } + catch (DbEntityValidationException ex) + { + LogDbEntityValidationException(ex); + throw; + } + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken) + { + try + { + EntityChangeReport changeReport = ApplyStoveConcepts(); + int result = await base.SaveChangesAsync(cancellationToken); + await EntityChangeEventHelper.TriggerEventsAsync(changeReport, cancellationToken); + return result; + } + catch (DbEntityValidationException ex) + { + LogDbEntityValidationException(ex); + throw; + } + } + + protected virtual EntityChangeReport ApplyStoveConcepts() + { + var changeReport = new EntityChangeReport(); + + long? userId = GetAuditUserId(); + + List entries = ChangeTracker.Entries().ToList(); + foreach (DbEntityEntry entry in entries) + { + switch (entry.State) + { + case EntityState.Added: + CheckAndSetId(entry.Entity); + SetCreationAuditProperties(entry.Entity, userId); + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Created)); + break; + case EntityState.Modified: + SetModificationAuditProperties(entry, userId); + if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) + { + SetDeletionAuditProperties(entry.Entity, userId); + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); + } + else + { + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Updated)); + } + + break; + case EntityState.Deleted: + CancelDeletionForSoftDelete(entry); + SetDeletionAuditProperties(entry.Entity, userId); + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); + break; + } + + AddDomainEvents(changeReport.DomainEvents, entry.Entity); + } + + return changeReport; + } + + protected virtual void AddDomainEvents(List domainEvents, object entityAsObj) + { + if (!(entityAsObj is IGeneratesDomainEvents generatesDomainEventsEntity)) + { + return; + } + + if (generatesDomainEventsEntity.DomainEvents.IsNullOrEmpty()) + { + return; + } + + domainEvents.AddRange(generatesDomainEventsEntity.DomainEvents.Select(eventData => new DomainEventEntry(entityAsObj, eventData))); + generatesDomainEventsEntity.DomainEvents.Clear(); + } + + protected virtual void CheckAndSetId(object entityAsObj) + { + if (entityAsObj is IEntity entity && entity.Id == Guid.Empty) + { + Type entityType = ObjectContext.GetObjectType(entityAsObj.GetType()); + PropertyInfo idProperty = entityType.GetProperty("Id"); + var dbGeneratedAttr = ReflectionHelper.GetSingleAttributeOrDefault(idProperty); + if (dbGeneratedAttr == null || dbGeneratedAttr.DatabaseGeneratedOption == DatabaseGeneratedOption.None) + { + entity.Id = GuidGenerator.Create(); + } + } + } + + protected virtual void SetCreationAuditProperties(object entityAsObj, long? userId) + { + EntityAuditingHelper.SetCreationAuditProperties(entityAsObj, userId); + } + + protected virtual void SetModificationAuditProperties(DbEntityEntry entry, long? userId) + { + EntityAuditingHelper.SetModificationAuditProperties(entry.Entity, userId); + } + + protected virtual void CancelDeletionForSoftDelete(DbEntityEntry entry) + { + if (!(entry.Entity is ISoftDelete)) + { + return; + } + + DbEntityEntry softDeleteEntry = entry.Cast(); + softDeleteEntry.Reload(); + softDeleteEntry.State = EntityState.Modified; + softDeleteEntry.Entity.IsDeleted = true; + } + + protected virtual void SetDeletionAuditProperties(object entityAsObj, long? userId) + { + if (entityAsObj is IHasDeletionTime) + { + var entity = entityAsObj.As(); + + if (entity.DeletionTime == null) + { + entity.DeletionTime = Clock.Now; + } + } + + if (entityAsObj is IDeletionAudited) + { + var entity = entityAsObj.As(); + + if (entity.DeleterUserId != null) + { + return; + } + + if (userId == null) + { + entity.DeleterUserId = null; + return; + } + + entity.DeleterUserId = userId; + } + } + + protected virtual void LogDbEntityValidationException(DbEntityValidationException exception) + { + Logger.Error("There are some validation errors while saving changes in EntityFramework:"); + foreach (DbValidationError ve in exception.EntityValidationErrors.SelectMany(eve => eve.ValidationErrors)) + { + Logger.Error(" - " + ve.PropertyName + ": " + ve.ErrorMessage); + } + } + + protected virtual long? GetAuditUserId() + { + if (StoveSession.UserId.HasValue && + CurrentUnitOfWorkProvider != null && + CurrentUnitOfWorkProvider.Current != null) + { + return StoveSession.UserId; + } + + return null; + } + } } diff --git a/src/Stove.EntityFramework/EntityFramework/Uow/EfUnitOfWork.cs b/src/Stove.EntityFramework/EntityFramework/Uow/EfUnitOfWork.cs index 5c41d58..ff62b77 100644 --- a/src/Stove.EntityFramework/EntityFramework/Uow/EfUnitOfWork.cs +++ b/src/Stove.EntityFramework/EntityFramework/Uow/EfUnitOfWork.cs @@ -4,6 +4,7 @@ using System.Data.Entity; using System.Data.Entity.Core.Objects; using System.Data.Entity.Infrastructure; +using System.Threading; using System.Threading.Tasks; using Autofac.Extras.IocManager; @@ -60,11 +61,11 @@ public override void SaveChanges() GetAllActiveDbContexts().ForEach(SaveChangesInDbContext); } - public override async Task SaveChangesAsync() + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) { foreach (DbContext dbContext in GetAllActiveDbContexts()) { - await SaveChangesInDbContextAsync(dbContext); + await SaveChangesInDbContextAsync(dbContext, cancellationToken); } } @@ -83,9 +84,9 @@ protected override void CompleteUow() } } - protected override async Task CompleteUowAsync() + protected override async Task CompleteUowAsync(CancellationToken cancellationToken = default(CancellationToken)) { - await SaveChangesAsync(); + await SaveChangesAsync(cancellationToken); if (Options.IsTransactional == true) { @@ -158,9 +159,9 @@ protected virtual void SaveChangesInDbContext(DbContext dbContext) dbContext.SaveChanges(); } - protected virtual async Task SaveChangesInDbContextAsync(DbContext dbContext) + protected virtual async Task SaveChangesInDbContextAsync(DbContext dbContext, CancellationToken cancellationToken = default(CancellationToken)) { - await dbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(cancellationToken); } protected virtual void Release(DbContext dbContext) diff --git a/src/Stove.EntityFrameworkCore/EntityFrameworkCore/StoveDbContext.cs b/src/Stove.EntityFrameworkCore/EntityFrameworkCore/StoveDbContext.cs index 6ed689a..c806474 100644 --- a/src/Stove.EntityFrameworkCore/EntityFrameworkCore/StoveDbContext.cs +++ b/src/Stove.EntityFrameworkCore/EntityFrameworkCore/StoveDbContext.cs @@ -28,317 +28,317 @@ namespace Stove.EntityFrameworkCore { - /// - /// - /// Base class for all DbContext classes in the application. - /// - public abstract class StoveDbContext : DbContext, ITransientDependency - { - private static readonly MethodInfo ConfigureGlobalFiltersMethodInfo = typeof(StoveDbContext).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.Instance | BindingFlags.NonPublic); - - /// - /// - /// Constructor. - /// - protected StoveDbContext(DbContextOptions options) - : base(options) - { - InitializeDbContext(); - } - - /// - /// Used to get current session values. - /// - public IStoveSession StoveSession { get; set; } - - /// - /// Used to trigger entity change events. - /// - public IEntityChangeEventHelper EntityChangeEventHelper { get; set; } - - /// - /// Reference to the logger. - /// - public ILogger Logger { get; set; } - - /// - /// Reference to the event bus. - /// - public IEventBus EventBus { get; set; } - - /// - /// Reference to GUID generator. - /// - public IGuidGenerator GuidGenerator { get; set; } - - /// - /// Reference to the current UOW provider. - /// - public ICurrentUnitOfWorkProvider CurrentUnitOfWorkProvider { get; set; } - - /// - /// Can be used to suppress automatically setting TenantId on SaveChanges. - /// Default: false. - /// - public bool SuppressAutoSetTenantId { get; set; } - - protected virtual bool IsSoftDeleteFilterEnabled => CurrentUnitOfWorkProvider?.Current?.IsFilterEnabled(StoveDataFilters.SoftDelete) == true; - - private void InitializeDbContext() - { - SetNullsForInjectedProperties(); - } - - private void SetNullsForInjectedProperties() - { - Logger = NullLogger.Instance; - StoveSession = NullStoveSession.Instance; - EntityChangeEventHelper = NullEntityChangeEventHelper.Instance; - GuidGenerator = SequentialGuidGenerator.Instance; - EventBus = NullEventBus.Instance; - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) - { - ConfigureGlobalFiltersMethodInfo - .MakeGenericMethod(entityType.ClrType) - .Invoke(this, new object[] { modelBuilder, entityType }); - } - } - - public override int SaveChanges() - { - try - { - EntityChangeReport changeReport = ApplyStoveConcepts(); - int result = base.SaveChanges(); - EntityChangeEventHelper.TriggerEvents(changeReport); - return result; - } - catch (DbUpdateConcurrencyException ex) - { - throw new StoveDbConcurrencyException(ex.Message, ex); - } - } - - public override async Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - try - { - EntityChangeReport changeReport = ApplyStoveConcepts(); - int result = await base.SaveChangesAsync(cancellationToken); - await EntityChangeEventHelper.TriggerEventsAsync(changeReport); - return result; - } - catch (DbUpdateConcurrencyException ex) - { - throw new StoveDbConcurrencyException(ex.Message, ex); - } - } - - protected virtual EntityChangeReport ApplyStoveConcepts() - { - var changeReport = new EntityChangeReport(); - - long? userId = GetAuditUserId(); - - foreach (EntityEntry entry in ChangeTracker.Entries().ToList()) - { - ApplyStoveConcepts(entry, userId, changeReport); - } - - return changeReport; - } - - protected virtual void ApplyStoveConcepts(EntityEntry entry, long? userId, EntityChangeReport changeReport) - { - switch (entry.State) - { - case EntityState.Added: - ApplyStoveConceptsForAddedEntity(entry, userId, changeReport); - break; - case EntityState.Modified: - ApplyStoveConceptsForModifiedEntity(entry, userId, changeReport); - break; - case EntityState.Deleted: - ApplyStoveConceptsForDeletedEntity(entry, userId, changeReport); - break; - } - - AddDomainEvents(changeReport.DomainEvents, entry.Entity); - } - - protected virtual void ApplyStoveConceptsForAddedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport) - { - CheckAndSetId(entry); - SetCreationAuditProperties(entry.Entity, userId); - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Created)); - } - - protected virtual void ApplyStoveConceptsForModifiedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport) - { - SetModificationAuditProperties(entry.Entity, userId); - if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) - { - SetDeletionAuditProperties(entry.Entity, userId); - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); - } - else - { - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Updated)); - } - } - - protected virtual void ApplyStoveConceptsForDeletedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport) - { - CancelDeletionForSoftDelete(entry); - SetDeletionAuditProperties(entry.Entity, userId); - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); - } - - protected virtual void AddDomainEvents(List domainEvents, object entityAsObj) - { - var generatesDomainEventsEntity = entityAsObj as IGeneratesDomainEvents; - if (generatesDomainEventsEntity == null) - { - return; - } - - if (generatesDomainEventsEntity.DomainEvents.IsNullOrEmpty()) - { - return; - } - - domainEvents.AddRange(generatesDomainEventsEntity.DomainEvents.Select(eventData => new DomainEventEntry(entityAsObj, eventData))); - generatesDomainEventsEntity.DomainEvents.Clear(); - } - - protected virtual void CheckAndSetId(EntityEntry entry) - { - if (entry.Entity is IEntity entity && entity.Id == Guid.Empty) - { - var dbGeneratedAttr = ReflectionHelper - .GetSingleAttributeOrDefault( - entry.Property("Id").Metadata.PropertyInfo - ); - - if (dbGeneratedAttr == null || dbGeneratedAttr.DatabaseGeneratedOption == DatabaseGeneratedOption.None) - { - entity.Id = GuidGenerator.Create(); - } - } - } - - protected virtual void SetCreationAuditProperties(object entityAsObj, long? userId) - { - EntityAuditingHelper.SetCreationAuditProperties(entityAsObj, userId); - } - - protected virtual void SetModificationAuditProperties(object entityAsObj, long? userId) - { - EntityAuditingHelper.SetModificationAuditProperties(entityAsObj, userId); - } - - protected virtual void CancelDeletionForSoftDelete(EntityEntry entry) - { - if (!(entry.Entity is ISoftDelete)) - { - return; - } - - entry.Reload(); - entry.State = EntityState.Modified; - entry.Entity.As().IsDeleted = true; - } - - protected virtual void SetDeletionAuditProperties(object entityAsObj, long? userId) - { - if (entityAsObj is IHasDeletionTime) - { - var entity = entityAsObj.As(); - - if (entity.DeletionTime == null) - { - entity.DeletionTime = Clock.Now; - } - } - - if (entityAsObj is IDeletionAudited) - { - var entity = entityAsObj.As(); - - if (entity.DeleterUserId != null) - { - return; - } - - if (userId == null) - { - entity.DeleterUserId = null; - return; - } - - entity.DeleterUserId = userId; - } - } - - protected virtual long? GetAuditUserId() - { - if (StoveSession.UserId.HasValue && - CurrentUnitOfWorkProvider != null && - CurrentUnitOfWorkProvider.Current != null) - { - return StoveSession.UserId; - } - - return null; - } - - protected virtual bool ShouldFilterEntity(IMutableEntityType entityType) where TEntity : class - { - if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) - { - return true; - } - - return false; - } - - protected void ConfigureGlobalFilters(ModelBuilder modelBuilder, IMutableEntityType entityType) - where TEntity : class - { - if (entityType.BaseType == null && ShouldFilterEntity(entityType)) - { - Expression> filterExpression = CreateFilterExpression(); - if (filterExpression != null) - { - modelBuilder.Entity().HasQueryFilter(filterExpression); - } - } - } - - protected virtual Expression> CreateFilterExpression() - where TEntity : class - { - Expression> expression = null; - - if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) - { - /* This condition should normally be defined as below: + /// + /// + /// Base class for all DbContext classes in the application. + /// + public abstract class StoveDbContext : DbContext, ITransientDependency + { + private static readonly MethodInfo ConfigureGlobalFiltersMethodInfo = typeof(StoveDbContext).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.Instance | BindingFlags.NonPublic); + + /// + /// + /// Constructor. + /// + protected StoveDbContext(DbContextOptions options) + : base(options) + { + InitializeDbContext(); + } + + /// + /// Used to get current session values. + /// + public IStoveSession StoveSession { get; set; } + + /// + /// Used to trigger entity change events. + /// + public IEntityChangeEventHelper EntityChangeEventHelper { get; set; } + + /// + /// Reference to the logger. + /// + public ILogger Logger { get; set; } + + /// + /// Reference to the event bus. + /// + public IEventBus EventBus { get; set; } + + /// + /// Reference to GUID generator. + /// + public IGuidGenerator GuidGenerator { get; set; } + + /// + /// Reference to the current UOW provider. + /// + public ICurrentUnitOfWorkProvider CurrentUnitOfWorkProvider { get; set; } + + /// + /// Can be used to suppress automatically setting TenantId on SaveChanges. + /// Default: false. + /// + public bool SuppressAutoSetTenantId { get; set; } + + protected virtual bool IsSoftDeleteFilterEnabled => CurrentUnitOfWorkProvider?.Current?.IsFilterEnabled(StoveDataFilters.SoftDelete) == true; + + private void InitializeDbContext() + { + SetNullsForInjectedProperties(); + } + + private void SetNullsForInjectedProperties() + { + Logger = NullLogger.Instance; + StoveSession = NullStoveSession.Instance; + EntityChangeEventHelper = NullEntityChangeEventHelper.Instance; + GuidGenerator = SequentialGuidGenerator.Instance; + EventBus = NullEventBus.Instance; + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) + { + ConfigureGlobalFiltersMethodInfo + .MakeGenericMethod(entityType.ClrType) + .Invoke(this, new object[] { modelBuilder, entityType }); + } + } + + public override int SaveChanges() + { + try + { + EntityChangeReport changeReport = ApplyStoveConcepts(); + int result = base.SaveChanges(); + EntityChangeEventHelper.TriggerEvents(changeReport); + return result; + } + catch (DbUpdateConcurrencyException ex) + { + throw new StoveDbConcurrencyException(ex.Message, ex); + } + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + EntityChangeReport changeReport = ApplyStoveConcepts(); + int result = await base.SaveChangesAsync(cancellationToken); + await EntityChangeEventHelper.TriggerEventsAsync(changeReport, cancellationToken); + return result; + } + catch (DbUpdateConcurrencyException ex) + { + throw new StoveDbConcurrencyException(ex.Message, ex); + } + } + + protected virtual EntityChangeReport ApplyStoveConcepts() + { + var changeReport = new EntityChangeReport(); + + long? userId = GetAuditUserId(); + + foreach (EntityEntry entry in ChangeTracker.Entries().ToList()) + { + ApplyStoveConcepts(entry, userId, changeReport); + } + + return changeReport; + } + + protected virtual void ApplyStoveConcepts(EntityEntry entry, long? userId, EntityChangeReport changeReport) + { + switch (entry.State) + { + case EntityState.Added: + ApplyStoveConceptsForAddedEntity(entry, userId, changeReport); + break; + case EntityState.Modified: + ApplyStoveConceptsForModifiedEntity(entry, userId, changeReport); + break; + case EntityState.Deleted: + ApplyStoveConceptsForDeletedEntity(entry, userId, changeReport); + break; + } + + AddDomainEvents(changeReport.DomainEvents, entry.Entity); + } + + protected virtual void ApplyStoveConceptsForAddedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport) + { + CheckAndSetId(entry); + SetCreationAuditProperties(entry.Entity, userId); + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Created)); + } + + protected virtual void ApplyStoveConceptsForModifiedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport) + { + SetModificationAuditProperties(entry.Entity, userId); + if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) + { + SetDeletionAuditProperties(entry.Entity, userId); + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); + } + else + { + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Updated)); + } + } + + protected virtual void ApplyStoveConceptsForDeletedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport) + { + CancelDeletionForSoftDelete(entry); + SetDeletionAuditProperties(entry.Entity, userId); + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); + } + + protected virtual void AddDomainEvents(List domainEvents, object entityAsObj) + { + var generatesDomainEventsEntity = entityAsObj as IGeneratesDomainEvents; + if (generatesDomainEventsEntity == null) + { + return; + } + + if (generatesDomainEventsEntity.DomainEvents.IsNullOrEmpty()) + { + return; + } + + domainEvents.AddRange(generatesDomainEventsEntity.DomainEvents.Select(eventData => new DomainEventEntry(entityAsObj, eventData))); + generatesDomainEventsEntity.DomainEvents.Clear(); + } + + protected virtual void CheckAndSetId(EntityEntry entry) + { + if (entry.Entity is IEntity entity && entity.Id == Guid.Empty) + { + var dbGeneratedAttr = ReflectionHelper + .GetSingleAttributeOrDefault( + entry.Property("Id").Metadata.PropertyInfo + ); + + if (dbGeneratedAttr == null || dbGeneratedAttr.DatabaseGeneratedOption == DatabaseGeneratedOption.None) + { + entity.Id = GuidGenerator.Create(); + } + } + } + + protected virtual void SetCreationAuditProperties(object entityAsObj, long? userId) + { + EntityAuditingHelper.SetCreationAuditProperties(entityAsObj, userId); + } + + protected virtual void SetModificationAuditProperties(object entityAsObj, long? userId) + { + EntityAuditingHelper.SetModificationAuditProperties(entityAsObj, userId); + } + + protected virtual void CancelDeletionForSoftDelete(EntityEntry entry) + { + if (!(entry.Entity is ISoftDelete)) + { + return; + } + + entry.Reload(); + entry.State = EntityState.Modified; + entry.Entity.As().IsDeleted = true; + } + + protected virtual void SetDeletionAuditProperties(object entityAsObj, long? userId) + { + if (entityAsObj is IHasDeletionTime) + { + var entity = entityAsObj.As(); + + if (entity.DeletionTime == null) + { + entity.DeletionTime = Clock.Now; + } + } + + if (entityAsObj is IDeletionAudited) + { + var entity = entityAsObj.As(); + + if (entity.DeleterUserId != null) + { + return; + } + + if (userId == null) + { + entity.DeleterUserId = null; + return; + } + + entity.DeleterUserId = userId; + } + } + + protected virtual long? GetAuditUserId() + { + if (StoveSession.UserId.HasValue && + CurrentUnitOfWorkProvider != null && + CurrentUnitOfWorkProvider.Current != null) + { + return StoveSession.UserId; + } + + return null; + } + + protected virtual bool ShouldFilterEntity(IMutableEntityType entityType) where TEntity : class + { + if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) + { + return true; + } + + return false; + } + + protected void ConfigureGlobalFilters(ModelBuilder modelBuilder, IMutableEntityType entityType) + where TEntity : class + { + if (entityType.BaseType == null && ShouldFilterEntity(entityType)) + { + Expression> filterExpression = CreateFilterExpression(); + if (filterExpression != null) + { + modelBuilder.Entity().HasQueryFilter(filterExpression); + } + } + } + + protected virtual Expression> CreateFilterExpression() + where TEntity : class + { + Expression> expression = null; + + if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) + { + /* This condition should normally be defined as below: * !IsSoftDeleteFilterEnabled || !((ISoftDelete) e).IsDeleted * But this causes a problem with EF Core (see https://github.com/aspnet/EntityFrameworkCore/issues/9502) * So, we made a workaround to make it working. It works same as above. */ - Expression> softDeleteFilter = e => !((ISoftDelete)e).IsDeleted || ((ISoftDelete)e).IsDeleted != IsSoftDeleteFilterEnabled; - expression = expression == null ? softDeleteFilter : expression.And(softDeleteFilter); - } + Expression> softDeleteFilter = e => !((ISoftDelete)e).IsDeleted || ((ISoftDelete)e).IsDeleted != IsSoftDeleteFilterEnabled; + expression = expression == null ? softDeleteFilter : expression.And(softDeleteFilter); + } - return expression; - } - } + return expression; + } + } } diff --git a/src/Stove.EntityFrameworkCore/EntityFrameworkCore/Uow/EfCoreUnitOfWork.cs b/src/Stove.EntityFrameworkCore/EntityFrameworkCore/Uow/EfCoreUnitOfWork.cs index 469df35..f239c29 100644 --- a/src/Stove.EntityFrameworkCore/EntityFrameworkCore/Uow/EfCoreUnitOfWork.cs +++ b/src/Stove.EntityFrameworkCore/EntityFrameworkCore/Uow/EfCoreUnitOfWork.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Threading; using System.Threading.Tasks; using Autofac.Extras.IocManager; @@ -13,172 +14,171 @@ namespace Stove.EntityFrameworkCore.Uow { - /// - /// - /// Implements Unit of work for Entity Framework. - /// - public class EfCoreUnitOfWork : UnitOfWorkBase, ITransientDependency - { - private readonly IDbContextResolver _dbContextResolver; - private readonly IDbContextTypeMatcher _dbContextTypeMatcher; - private readonly IEfCoreTransactionStrategy _transactionStrategy; - - /// - /// - /// Creates a new . - /// - public EfCoreUnitOfWork( - IConnectionStringResolver connectionStringResolver, - IUnitOfWorkFilterExecuter filterExecuter, - IDbContextResolver dbContextResolver, - IUnitOfWorkDefaultOptions defaultOptions, - IDbContextTypeMatcher dbContextTypeMatcher, - IEfCoreTransactionStrategy transactionStrategy) - : base( - connectionStringResolver, - defaultOptions, - filterExecuter) - { - _dbContextResolver = dbContextResolver; - _dbContextTypeMatcher = dbContextTypeMatcher; - _transactionStrategy = transactionStrategy; - - ActiveDbContexts = new Dictionary(); - } - - protected IDictionary ActiveDbContexts { get; } - - protected override void BeginUow() - { - if (Options.IsTransactional == true) - { - _transactionStrategy.InitOptions(Options); - } - } - - public override void SaveChanges() - { - foreach (DbContext dbContext in GetAllActiveDbContexts()) - { - SaveChangesInDbContext(dbContext); - } - } - - public override async Task SaveChangesAsync() - { - foreach (DbContext dbContext in GetAllActiveDbContexts()) - { - await SaveChangesInDbContextAsync(dbContext); - } - } - - protected override void CompleteUow() - { - SaveChanges(); - CommitTransaction(); - } - - protected override async Task CompleteUowAsync() - { - await SaveChangesAsync(); - CommitTransaction(); - } - - private void CommitTransaction() - { - if (Options.IsTransactional == true) - { - _transactionStrategy.Commit(); - } - } - - public IReadOnlyList GetAllActiveDbContexts() - { - return ActiveDbContexts.Values.ToImmutableList(); - } - - public virtual TDbContext GetOrCreateDbContext() - where TDbContext : DbContext - { - Type concreteDbContextType = _dbContextTypeMatcher.GetConcreteType(typeof(TDbContext)); - - var connectionStringResolveArgs = new ConnectionStringResolveArgs(); - connectionStringResolveArgs["DbContextType"] = typeof(TDbContext); - connectionStringResolveArgs["DbContextConcreteType"] = concreteDbContextType; - string connectionString = ResolveConnectionString(connectionStringResolveArgs); - - string dbContextKey = concreteDbContextType.FullName + "#" + connectionString; - - if (!ActiveDbContexts.TryGetValue(dbContextKey, out DbContext dbContext)) - { - if (Options.IsTransactional == true) - { - dbContext = _transactionStrategy.CreateDbContext(connectionString, _dbContextResolver); - } - else - { - dbContext = _dbContextResolver.Resolve(connectionString, null); - } - - if (Options.Timeout.HasValue && - dbContext.Database.IsRelational() && - !dbContext.Database.GetCommandTimeout().HasValue) - { - dbContext.Database.SetCommandTimeout(Options.Timeout.Value.TotalSeconds.To()); - } - - //TODO: Object materialize event - //TODO: Apply current filters to this dbcontext - - ActiveDbContexts[dbContextKey] = dbContext; - } - - return (TDbContext)dbContext; - } - - protected override void DisposeUow() - { - if (Options.IsTransactional == true) - { - _transactionStrategy.Dispose(); - } - else - { - foreach (DbContext context in GetAllActiveDbContexts()) - { - Release(context); - } - } - - ActiveDbContexts.Clear(); - } - - protected virtual void SaveChangesInDbContext(DbContext dbContext) - { - dbContext.SaveChanges(); - } - - protected virtual async Task SaveChangesInDbContextAsync(DbContext dbContext) - { - await dbContext.SaveChangesAsync(); - } - - protected virtual void Release(DbContext dbContext) - { - dbContext.Dispose(); - } - - //private static void ObjectContext_ObjectMaterialized(DbContext dbContext, ObjectMaterializedEventArgs e) - //{ - // dbContext.Configuration.AutoDetectChangesEnabled = true; - - // dbContext.Entry(e.Entity).State = previousState; - - // DateTimePropertyInfoHelper.NormalizeDatePropertyKinds(e.Entity, entityType); - // var previousState = dbContext.Entry(e.Entity).State; - - // dbContext.Configuration.AutoDetectChangesEnabled = false; - // var entityType = ObjectContext.GetObjectType(e.Entity.GetType()); - //} - } + /// + /// + /// Implements Unit of work for Entity Framework. + /// + public class EfCoreUnitOfWork : UnitOfWorkBase, ITransientDependency + { + private readonly IDbContextResolver _dbContextResolver; + private readonly IDbContextTypeMatcher _dbContextTypeMatcher; + private readonly IEfCoreTransactionStrategy _transactionStrategy; + + /// + /// + /// Creates a new . + /// + public EfCoreUnitOfWork( + IConnectionStringResolver connectionStringResolver, + IUnitOfWorkFilterExecuter filterExecuter, + IDbContextResolver dbContextResolver, + IUnitOfWorkDefaultOptions defaultOptions, + IDbContextTypeMatcher dbContextTypeMatcher, + IEfCoreTransactionStrategy transactionStrategy) + : base( + connectionStringResolver, + defaultOptions, + filterExecuter) + { + _dbContextResolver = dbContextResolver; + _dbContextTypeMatcher = dbContextTypeMatcher; + _transactionStrategy = transactionStrategy; + + ActiveDbContexts = new Dictionary(); + } + + protected IDictionary ActiveDbContexts { get; } + + protected override void BeginUow() + { + if (Options.IsTransactional == true) + { + _transactionStrategy.InitOptions(Options); + } + } + + public override void SaveChanges() + { + foreach (DbContext dbContext in GetAllActiveDbContexts()) + { + SaveChangesInDbContext(dbContext); + } + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + foreach (DbContext dbContext in GetAllActiveDbContexts()) + { + await SaveChangesInDbContextAsync(dbContext, cancellationToken); + } + } + + protected override void CompleteUow() + { + SaveChanges(); + CommitTransaction(); + } + + protected override Task CompleteUowAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + return SaveChangesAsync(cancellationToken).ContinueWith(task => CommitTransaction(), cancellationToken); + } + + private void CommitTransaction() + { + if (Options.IsTransactional == true) + { + _transactionStrategy.Commit(); + } + } + + public IReadOnlyList GetAllActiveDbContexts() + { + return ActiveDbContexts.Values.ToImmutableList(); + } + + public virtual TDbContext GetOrCreateDbContext() + where TDbContext : DbContext + { + Type concreteDbContextType = _dbContextTypeMatcher.GetConcreteType(typeof(TDbContext)); + + var connectionStringResolveArgs = new ConnectionStringResolveArgs(); + connectionStringResolveArgs["DbContextType"] = typeof(TDbContext); + connectionStringResolveArgs["DbContextConcreteType"] = concreteDbContextType; + string connectionString = ResolveConnectionString(connectionStringResolveArgs); + + string dbContextKey = concreteDbContextType.FullName + "#" + connectionString; + + if (!ActiveDbContexts.TryGetValue(dbContextKey, out DbContext dbContext)) + { + if (Options.IsTransactional == true) + { + dbContext = _transactionStrategy.CreateDbContext(connectionString, _dbContextResolver); + } + else + { + dbContext = _dbContextResolver.Resolve(connectionString, null); + } + + if (Options.Timeout.HasValue && + dbContext.Database.IsRelational() && + !dbContext.Database.GetCommandTimeout().HasValue) + { + dbContext.Database.SetCommandTimeout(Options.Timeout.Value.TotalSeconds.To()); + } + + //TODO: Object materialize event + //TODO: Apply current filters to this dbcontext + + ActiveDbContexts[dbContextKey] = dbContext; + } + + return (TDbContext)dbContext; + } + + protected override void DisposeUow() + { + if (Options.IsTransactional == true) + { + _transactionStrategy.Dispose(); + } + else + { + foreach (DbContext context in GetAllActiveDbContexts()) + { + Release(context); + } + } + + ActiveDbContexts.Clear(); + } + + protected virtual void SaveChangesInDbContext(DbContext dbContext) + { + dbContext.SaveChanges(); + } + + protected virtual async Task SaveChangesInDbContextAsync(DbContext dbContext, CancellationToken cancellationToken = default(CancellationToken)) + { + await dbContext.SaveChangesAsync(cancellationToken); + } + + protected virtual void Release(DbContext dbContext) + { + dbContext.Dispose(); + } + + //private static void ObjectContext_ObjectMaterialized(DbContext dbContext, ObjectMaterializedEventArgs e) + //{ + // dbContext.Configuration.AutoDetectChangesEnabled = true; + + // dbContext.Entry(e.Entity).State = previousState; + + // DateTimePropertyInfoHelper.NormalizeDatePropertyKinds(e.Entity, entityType); + // var previousState = dbContext.Entry(e.Entity).State; + + // dbContext.Configuration.AutoDetectChangesEnabled = false; + // var entityType = ObjectContext.GetObjectType(e.Entity.GetType()); + //} + } } diff --git a/src/Stove.NHibernate/NHibernate/SessionExtensions.cs b/src/Stove.NHibernate/NHibernate/SessionExtensions.cs new file mode 100644 index 0000000..3b70b54 --- /dev/null +++ b/src/Stove.NHibernate/NHibernate/SessionExtensions.cs @@ -0,0 +1,19 @@ +using System.Data.Common; + +using NHibernate; + +namespace Stove.NHibernate +{ + public static class SessionExtensions + { + public static ISession OpenSessionWithConnection(this ISession session, DbConnection connection) + { + return session.SessionWithOptions().Connection(connection).OpenSession(); + } + + public static ISession OpenSessionWithConnection(this ISessionFactory sessionFactory, DbConnection connection) + { + return sessionFactory.OpenSession().OpenSessionWithConnection(connection); + } + } +} diff --git a/src/Stove.NHibernate/NHibernate/Uow/NhUnitOfWork.cs b/src/Stove.NHibernate/NHibernate/Uow/NhUnitOfWork.cs index c8fa846..76125bb 100644 --- a/src/Stove.NHibernate/NHibernate/Uow/NhUnitOfWork.cs +++ b/src/Stove.NHibernate/NHibernate/Uow/NhUnitOfWork.cs @@ -1,4 +1,5 @@ -using System.Data; +using System.Data.Common; +using System.Threading; using System.Threading.Tasks; using Autofac.Extras.IocManager; @@ -43,12 +44,12 @@ public NhUnitOfWork( /// uses this DbConnection if it's set. /// This is usually set in tests. /// - public IDbConnection DbConnection { get; set; } + public DbConnection DbConnection { get; set; } protected override void BeginUow() { Session = DbConnection != null - ? _sessionFactory.OpenSession(DbConnection) + ? _sessionFactory.OpenSessionWithConnection(DbConnection) : _sessionFactory.OpenSession(); if (Options.IsTransactional == true) @@ -64,10 +65,9 @@ public override void SaveChanges() Session.Flush(); } - public override Task SaveChangesAsync() + public override Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) { - Session.Flush(); - return Task.FromResult(0); + return Session.FlushAsync(cancellationToken); } /// @@ -76,16 +76,12 @@ public override Task SaveChangesAsync() protected override void CompleteUow() { SaveChanges(); - if (_transaction != null) - { - _transaction.Commit(); - } + _transaction?.Commit(); } - protected override Task CompleteUowAsync() + protected override Task CompleteUowAsync(CancellationToken cancellationToken = default(CancellationToken)) { - CompleteUow(); - return Task.FromResult(0); + return SaveChangesAsync(cancellationToken).ContinueWith(task => _transaction?.CommitAsync(cancellationToken), cancellationToken); } /// diff --git a/src/Stove.NHibernate/Stove.NHibernate.csproj b/src/Stove.NHibernate/Stove.NHibernate.csproj index 5cdeb7f..5446561 100644 --- a/src/Stove.NHibernate/Stove.NHibernate.csproj +++ b/src/Stove.NHibernate/Stove.NHibernate.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Stove.RavenDB/RavenDB/Uow/RavenDBUnitOfWork.cs b/src/Stove.RavenDB/RavenDB/Uow/RavenDBUnitOfWork.cs index ee3647d..0f18ac1 100644 --- a/src/Stove.RavenDB/RavenDB/Uow/RavenDBUnitOfWork.cs +++ b/src/Stove.RavenDB/RavenDB/Uow/RavenDBUnitOfWork.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using Autofac.Extras.IocManager; @@ -36,7 +37,7 @@ public override void SaveChanges() Session.SaveChanges(); } - public override Task SaveChangesAsync() + public override Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) { SaveChanges(); return Task.FromResult(0); @@ -47,7 +48,7 @@ protected override void CompleteUow() SaveChanges(); } - protected override Task CompleteUowAsync() + protected override Task CompleteUowAsync(CancellationToken cancellationToken = default(CancellationToken)) { CompleteUow(); return Task.FromResult(0); diff --git a/src/Stove/Domain/Uow/IActiveUnitOfWork.cs b/src/Stove/Domain/Uow/IActiveUnitOfWork.cs index 1d8aef5..2235bfb 100644 --- a/src/Stove/Domain/Uow/IActiveUnitOfWork.cs +++ b/src/Stove/Domain/Uow/IActiveUnitOfWork.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Stove.Domain.Uow @@ -57,7 +58,7 @@ public interface IActiveUnitOfWork /// No explicit call is needed to SaveChanges generally, /// since all changes saved at end of a unit of work automatically. /// - Task SaveChangesAsync(); + Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); /// /// Disables one or more data filters. diff --git a/src/Stove/Domain/Uow/IUnitOfWorkCompleteHandle.cs b/src/Stove/Domain/Uow/IUnitOfWorkCompleteHandle.cs index 6a8c252..ec458f6 100644 --- a/src/Stove/Domain/Uow/IUnitOfWorkCompleteHandle.cs +++ b/src/Stove/Domain/Uow/IUnitOfWorkCompleteHandle.cs @@ -1,25 +1,26 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Stove.Domain.Uow { /// - /// Used to complete a unit of work. - /// This interface can not be injected or directly used. - /// Use instead. + /// Used to complete a unit of work. + /// This interface can not be injected or directly used. + /// Use instead. /// public interface IUnitOfWorkCompleteHandle : IDisposable { /// - /// Completes this unit of work. - /// It saves all changes and commit transaction if exists. + /// Completes this unit of work. + /// It saves all changes and commit transaction if exists. /// void Complete(); /// - /// Completes this unit of work. - /// It saves all changes and commit transaction if exists. + /// Completes this unit of work. + /// It saves all changes and commit transaction if exists. /// - Task CompleteAsync(); + Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken)); } -} \ No newline at end of file +} diff --git a/src/Stove/Domain/Uow/InnerUnitOfWorkCompleteHandle.cs b/src/Stove/Domain/Uow/InnerUnitOfWorkCompleteHandle.cs index 84b03df..2d55104 100644 --- a/src/Stove/Domain/Uow/InnerUnitOfWorkCompleteHandle.cs +++ b/src/Stove/Domain/Uow/InnerUnitOfWorkCompleteHandle.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; namespace Stove.Domain.Uow @@ -22,7 +23,7 @@ public void Complete() _isCompleteCalled = true; } - public Task CompleteAsync() + public Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken)) { _isCompleteCalled = true; return Task.FromResult(0); diff --git a/src/Stove/Domain/Uow/NullUnitOfWork.cs b/src/Stove/Domain/Uow/NullUnitOfWork.cs index 0e67f69..79ff683 100644 --- a/src/Stove/Domain/Uow/NullUnitOfWork.cs +++ b/src/Stove/Domain/Uow/NullUnitOfWork.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Stove.Domain.Uow { @@ -24,7 +25,7 @@ public override void SaveChanges() { } - public override Task SaveChangesAsync() + public override Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) { return Task.FromResult(0); } @@ -37,7 +38,7 @@ protected override void CompleteUow() { } - protected override Task CompleteUowAsync() + protected override Task CompleteUowAsync(CancellationToken cancellationToken = default(CancellationToken)) { return Task.FromResult(0); } diff --git a/src/Stove/Domain/Uow/UnitOfWorkBase.cs b/src/Stove/Domain/Uow/UnitOfWorkBase.cs index dfd195e..182a0c1 100644 --- a/src/Stove/Domain/Uow/UnitOfWorkBase.cs +++ b/src/Stove/Domain/Uow/UnitOfWorkBase.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Autofac.Extras.IocManager; @@ -11,391 +12,375 @@ namespace Stove.Domain.Uow { - /// - /// - /// Base for all Unit Of Work classes. - /// - public abstract class UnitOfWorkBase : IUnitOfWork - { - private readonly List _filters; + /// + /// + /// Base for all Unit Of Work classes. + /// + public abstract class UnitOfWorkBase : IUnitOfWork + { + private readonly List _filters; - /// - /// A reference to the exception if this unit of work failed. - /// - private Exception _exception; + /// + /// A reference to the exception if this unit of work failed. + /// + private Exception _exception; - /// - /// Is method called before? - /// - private bool _isBeginCalledBefore; - - /// - /// Is method called before? - /// - private bool _isCompleteCalledBefore; + /// + /// Is method called before? + /// + private bool _isBeginCalledBefore; + + /// + /// Is method called before? + /// + private bool _isCompleteCalledBefore; - /// - /// Is this unit of work successfully completed. - /// - private bool _succeed; - - /// - /// Constructor. - /// - protected UnitOfWorkBase( - IConnectionStringResolver connectionStringResolver, - IUnitOfWorkDefaultOptions defaultOptions, - IUnitOfWorkFilterExecuter filterExecuter) - { - FilterExecuter = filterExecuter; - DefaultOptions = defaultOptions; - ConnectionStringResolver = connectionStringResolver; - - Id = Guid.NewGuid().ToString("N"); - _filters = defaultOptions.Filters.ToList(); - - StoveSession = NullStoveSession.Instance; - } - - /// - /// Gets default UOW options. - /// - protected IUnitOfWorkDefaultOptions DefaultOptions { get; } - - /// - /// Gets the connection string resolver. - /// - protected IConnectionStringResolver ConnectionStringResolver { get; } - - /// - /// Reference to current Stove session. - /// - public IStoveSession StoveSession { protected get; set; } - - protected IUnitOfWorkFilterExecuter FilterExecuter { get; } - - public string Id { get; } - - [DoNotInject] - public IUnitOfWork Outer { get; set; } - - public event EventHandler Completed; - - public event EventHandler Failed; - - public event EventHandler Disposed; + /// + /// Is this unit of work successfully completed. + /// + private bool _succeed; + + /// + /// Constructor. + /// + protected UnitOfWorkBase( + IConnectionStringResolver connectionStringResolver, + IUnitOfWorkDefaultOptions defaultOptions, + IUnitOfWorkFilterExecuter filterExecuter) + { + FilterExecuter = filterExecuter; + DefaultOptions = defaultOptions; + ConnectionStringResolver = connectionStringResolver; + + Id = Guid.NewGuid().ToString("N"); + _filters = defaultOptions.Filters.ToList(); + + StoveSession = NullStoveSession.Instance; + } + + /// + /// Gets default UOW options. + /// + protected IUnitOfWorkDefaultOptions DefaultOptions { get; } + + /// + /// Gets the connection string resolver. + /// + protected IConnectionStringResolver ConnectionStringResolver { get; } + + /// + /// Reference to current Stove session. + /// + public IStoveSession StoveSession { protected get; set; } + + protected IUnitOfWorkFilterExecuter FilterExecuter { get; } + + public string Id { get; } + + [DoNotInject] + public IUnitOfWork Outer { get; set; } + + public event EventHandler Completed; + + public event EventHandler Failed; + + public event EventHandler Disposed; - public UnitOfWorkOptions Options { get; private set; } - - public IReadOnlyList Filters => _filters.ToImmutableList(); - - /// - /// - /// Gets a value indicates that this unit of work is disposed or not. - /// - public bool IsDisposed { get; private set; } - - /// - public void Begin(UnitOfWorkOptions options) - { - Check.NotNull(options, nameof(options)); - - PreventMultipleBegin(); - Options = options; //TODO: Do not set options like that, instead make a copy? - - SetFilters(options.FilterOverrides); - - BeginUow(); - } - - /// - public abstract void SaveChanges(); - - /// - public abstract Task SaveChangesAsync(); - - /// - public IDisposable DisableFilter(params string[] filterNames) - { - //TODO: Check if filters exists? - - var disabledFilters = new List(); - - foreach (string filterName in filterNames) - { - int filterIndex = GetFilterIndex(filterName); - if (_filters[filterIndex].IsEnabled) - { - disabledFilters.Add(filterName); - _filters[filterIndex] = new DataFilterConfiguration(_filters[filterIndex], false); - } - } - - disabledFilters.ForEach(ApplyDisableFilter); - - return new DisposeAction(() => EnableFilter(disabledFilters.ToArray())); - } - - /// - public IDisposable EnableFilter(params string[] filterNames) - { - //TODO: Check if filters exists? - - var enabledFilters = new List(); - - foreach (string filterName in filterNames) - { - int filterIndex = GetFilterIndex(filterName); - if (!_filters[filterIndex].IsEnabled) - { - enabledFilters.Add(filterName); - _filters[filterIndex] = new DataFilterConfiguration(_filters[filterIndex], true); - } - } - - enabledFilters.ForEach(ApplyEnableFilter); - - return new DisposeAction(() => DisableFilter(enabledFilters.ToArray())); - } - - /// - public bool IsFilterEnabled(string filterName) - { - return GetFilter(filterName).IsEnabled; - } - - /// - public IDisposable SetFilterParameter(string filterName, string parameterName, object value) - { - int filterIndex = GetFilterIndex(filterName); - - var newfilter = new DataFilterConfiguration(_filters[filterIndex]); - - //Store old value - object oldValue = null; - bool hasOldValue = newfilter.FilterParameters.ContainsKey(parameterName); - if (hasOldValue) - { - oldValue = newfilter.FilterParameters[parameterName]; - } - - newfilter.FilterParameters[parameterName] = value; - - _filters[filterIndex] = newfilter; - - ApplyFilterParameterValue(filterName, parameterName, value); - - return new DisposeAction(() => - { - //Restore old value - if (hasOldValue) - { - SetFilterParameter(filterName, parameterName, oldValue); - } - }); - } - - /// - public void Complete() - { - PreventMultipleComplete(); - try - { - CompleteUow(); - _succeed = true; - OnCompleted(); - } - catch (Exception ex) - { - _exception = ex; - throw; - } - } - - /// - public async Task CompleteAsync() - { - PreventMultipleComplete(); - try - { - await CompleteUowAsync(); - _succeed = true; - OnCompleted(); - } - catch (Exception ex) - { - _exception = ex; - throw; - } - } - - /// - public void Dispose() - { - if (!_isBeginCalledBefore || IsDisposed) - { - return; - } - - IsDisposed = true; - - if (!_succeed) - { - OnFailed(_exception); - } - - DisposeUow(); - OnDisposed(); - DisposeDelegates(); - } - - /// - /// Can be implemented by derived classes to start UOW. - /// - protected virtual void BeginUow() - { - } - - /// - /// Should be implemented by derived classes to complete UOW. - /// - protected abstract void CompleteUow(); - - /// - /// Should be implemented by derived classes to complete UOW. - /// - protected abstract Task CompleteUowAsync(); - - /// - /// Should be implemented by derived classes to dispose UOW. - /// - protected abstract void DisposeUow(); - - protected virtual void ApplyDisableFilter(string filterName) - { - FilterExecuter.ApplyDisableFilter(this, filterName); - } - - protected virtual void ApplyEnableFilter(string filterName) - { - FilterExecuter.ApplyEnableFilter(this, filterName); - } - - protected virtual void ApplyFilterParameterValue(string filterName, string parameterName, object value) - { - FilterExecuter.ApplyFilterParameterValue(this, filterName, parameterName, value); - } - - protected virtual string ResolveConnectionString(ConnectionStringResolveArgs args) - { - return ConnectionStringResolver.GetNameOrConnectionString(args); - } - - /// - /// Called to trigger event. - /// - protected virtual void OnCompleted() - { - Completed.InvokeSafely(this); - } - - /// - /// Called to trigger event. - /// - /// Exception that cause failure - protected virtual void OnFailed(Exception exception) - { - Failed.InvokeSafely(this, new UnitOfWorkFailedEventArgs(exception)); - } - - /// - /// Called to trigger event. - /// - protected virtual void OnDisposed() - { - Disposed.InvokeSafely(this); - } - - private void PreventMultipleBegin() - { - if (_isBeginCalledBefore) - { - throw new StoveException("This unit of work has started before. Can not call Start method more than once."); - } - - _isBeginCalledBefore = true; - } - - private void PreventMultipleComplete() - { - if (_isCompleteCalledBefore) - { - throw new StoveException("Complete is called before!"); - } - - _isCompleteCalledBefore = true; - } - - private void SetFilters(List filterOverrides) - { - for (var i = 0; i < _filters.Count; i++) - { - DataFilterConfiguration filterOverride = filterOverrides.FirstOrDefault(f => f.FilterName == _filters[i].FilterName); - if (filterOverride != null) - { - _filters[i] = filterOverride; - } - } - } - - private void ChangeFilterIsEnabledIfNotOverrided(List filterOverrides, string filterName, bool isEnabled) - { - if (filterOverrides.Any(f => f.FilterName == filterName)) - { - return; - } - - int index = _filters.FindIndex(f => f.FilterName == filterName); - if (index < 0) - { - return; - } - - if (_filters[index].IsEnabled == isEnabled) - { - return; - } - - _filters[index] = new DataFilterConfiguration(filterName, isEnabled); - } - - private DataFilterConfiguration GetFilter(string filterName) - { - DataFilterConfiguration filter = _filters.FirstOrDefault(f => f.FilterName == filterName); - if (filter == null) - { - throw new StoveException("Unknown filter name: " + filterName + ". Be sure this filter is registered before."); - } - - return filter; - } - - private int GetFilterIndex(string filterName) - { - int filterIndex = _filters.FindIndex(f => f.FilterName == filterName); - if (filterIndex < 0) - { - throw new StoveException("Unknown filter name: " + filterName + ". Be sure this filter is registered before."); - } - - return filterIndex; - } - - private void DisposeDelegates() - { - if (Failed != null) { Failed -= Failed; } - if (Completed != null) { Completed -= Completed; } - if (Disposed != null) { Disposed -= Disposed; } - } - } + public UnitOfWorkOptions Options { get; private set; } + + public IReadOnlyList Filters => _filters.ToImmutableList(); + + /// + /// + /// Gets a value indicates that this unit of work is disposed or not. + /// + public bool IsDisposed { get; private set; } + + /// + public void Begin(UnitOfWorkOptions options) + { + Check.NotNull(options, nameof(options)); + + PreventMultipleBegin(); + Options = options; //TODO: Do not set options like that, instead make a copy? + + SetFilters(options.FilterOverrides); + + BeginUow(); + } + + /// + public abstract void SaveChanges(); + + /// + public abstract Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); + + /// + public IDisposable DisableFilter(params string[] filterNames) + { + //TODO: Check if filters exists? + + var disabledFilters = new List(); + + foreach (string filterName in filterNames) + { + int filterIndex = GetFilterIndex(filterName); + if (_filters[filterIndex].IsEnabled) + { + disabledFilters.Add(filterName); + _filters[filterIndex] = new DataFilterConfiguration(_filters[filterIndex], false); + } + } + + disabledFilters.ForEach(ApplyDisableFilter); + + return new DisposeAction(() => EnableFilter(disabledFilters.ToArray())); + } + + /// + public IDisposable EnableFilter(params string[] filterNames) + { + //TODO: Check if filters exists? + + var enabledFilters = new List(); + + foreach (string filterName in filterNames) + { + int filterIndex = GetFilterIndex(filterName); + if (!_filters[filterIndex].IsEnabled) + { + enabledFilters.Add(filterName); + _filters[filterIndex] = new DataFilterConfiguration(_filters[filterIndex], true); + } + } + + enabledFilters.ForEach(ApplyEnableFilter); + + return new DisposeAction(() => DisableFilter(enabledFilters.ToArray())); + } + + /// + public bool IsFilterEnabled(string filterName) + { + return GetFilter(filterName).IsEnabled; + } + + /// + public IDisposable SetFilterParameter(string filterName, string parameterName, object value) + { + int filterIndex = GetFilterIndex(filterName); + + var newfilter = new DataFilterConfiguration(_filters[filterIndex]); + + //Store old value + object oldValue = null; + bool hasOldValue = newfilter.FilterParameters.ContainsKey(parameterName); + if (hasOldValue) + { + oldValue = newfilter.FilterParameters[parameterName]; + } + + newfilter.FilterParameters[parameterName] = value; + + _filters[filterIndex] = newfilter; + + ApplyFilterParameterValue(filterName, parameterName, value); + + return new DisposeAction(() => + { + //Restore old value + if (hasOldValue) + { + SetFilterParameter(filterName, parameterName, oldValue); + } + }); + } + + /// + public void Complete() + { + PreventMultipleComplete(); + try + { + CompleteUow(); + _succeed = true; + OnCompleted(); + } + catch (Exception ex) + { + _exception = ex; + throw; + } + } + + /// + public async Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + PreventMultipleComplete(); + try + { + await CompleteUowAsync(cancellationToken); + _succeed = true; + OnCompleted(); + } + catch (Exception ex) + { + _exception = ex; + throw; + } + } + + /// + public void Dispose() + { + if (!_isBeginCalledBefore || IsDisposed) + { + return; + } + + IsDisposed = true; + + if (!_succeed) + { + OnFailed(_exception); + } + + DisposeUow(); + OnDisposed(); + DisposeDelegates(); + } + + /// + /// Can be implemented by derived classes to start UOW. + /// + protected virtual void BeginUow() + { + } + + /// + /// Should be implemented by derived classes to complete UOW. + /// + protected abstract void CompleteUow(); + + /// + /// Should be implemented by derived classes to complete UOW. + /// + protected abstract Task CompleteUowAsync(CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Should be implemented by derived classes to dispose UOW. + /// + protected abstract void DisposeUow(); + + protected virtual void ApplyDisableFilter(string filterName) + { + FilterExecuter.ApplyDisableFilter(this, filterName); + } + + protected virtual void ApplyEnableFilter(string filterName) + { + FilterExecuter.ApplyEnableFilter(this, filterName); + } + + protected virtual void ApplyFilterParameterValue(string filterName, string parameterName, object value) + { + FilterExecuter.ApplyFilterParameterValue(this, filterName, parameterName, value); + } + + protected virtual string ResolveConnectionString(ConnectionStringResolveArgs args) + { + return ConnectionStringResolver.GetNameOrConnectionString(args); + } + + /// + /// Called to trigger event. + /// + protected virtual void OnCompleted() + { + Completed.InvokeSafely(this); + } + + /// + /// Called to trigger event. + /// + /// Exception that cause failure + protected virtual void OnFailed(Exception exception) + { + Failed.InvokeSafely(this, new UnitOfWorkFailedEventArgs(exception)); + } + + /// + /// Called to trigger event. + /// + protected virtual void OnDisposed() + { + Disposed.InvokeSafely(this); + } + + private void PreventMultipleBegin() + { + if (_isBeginCalledBefore) + { + throw new StoveException("This unit of work has started before. Can not call Start method more than once."); + } + + _isBeginCalledBefore = true; + } + + private void PreventMultipleComplete() + { + if (_isCompleteCalledBefore) + { + throw new StoveException("Complete is called before!"); + } + + _isCompleteCalledBefore = true; + } + + private void SetFilters(List filterOverrides) + { + for (var i = 0; i < _filters.Count; i++) + { + DataFilterConfiguration filterOverride = filterOverrides.FirstOrDefault(f => f.FilterName == _filters[i].FilterName); + if (filterOverride != null) + { + _filters[i] = filterOverride; + } + } + } + + private DataFilterConfiguration GetFilter(string filterName) + { + DataFilterConfiguration filter = _filters.FirstOrDefault(f => f.FilterName == filterName); + if (filter == null) + { + throw new StoveException("Unknown filter name: " + filterName + ". Be sure this filter is registered before."); + } + + return filter; + } + + private int GetFilterIndex(string filterName) + { + int filterIndex = _filters.FindIndex(f => f.FilterName == filterName); + if (filterIndex < 0) + { + throw new StoveException("Unknown filter name: " + filterName + ". Be sure this filter is registered before."); + } + + return filterIndex; + } + + public override string ToString() + { + return $"[UnitOfWork {Id}]"; + } + + private void DisposeDelegates() + { + if (Failed != null) { Failed -= Failed; } + if (Completed != null) { Completed -= Completed; } + if (Disposed != null) { Disposed -= Disposed; } + } + } } diff --git a/src/Stove/Events/Bus/Entities/EntityChangeEventHelper.cs b/src/Stove/Events/Bus/Entities/EntityChangeEventHelper.cs index fd413a7..ee2b5bb 100644 --- a/src/Stove/Events/Bus/Entities/EntityChangeEventHelper.cs +++ b/src/Stove/Events/Bus/Entities/EntityChangeEventHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Autofac.Extras.IocManager; @@ -9,12 +10,10 @@ namespace Stove.Events.Bus.Entities { /// - /// Used to trigger entity change events. + /// Used to trigger entity change events. /// public class EntityChangeEventHelper : IEntityChangeEventHelper, ITransientDependency { - public IEventBus EventBus { get; set; } - private readonly IUnitOfWorkManager _unitOfWorkManager; public EntityChangeEventHelper(IUnitOfWorkManager unitOfWorkManager) @@ -23,28 +22,24 @@ public EntityChangeEventHelper(IUnitOfWorkManager unitOfWorkManager) EventBus = NullEventBus.Instance; } + public IEventBus EventBus { get; set; } + public virtual void TriggerEvents(EntityChangeReport changeReport) { TriggerEventsInternal(changeReport); - if (changeReport.IsEmpty() || _unitOfWorkManager.Current == null) - { - return; - } + if (changeReport.IsEmpty() || _unitOfWorkManager.Current == null) return; _unitOfWorkManager.Current.SaveChanges(); } - public Task TriggerEventsAsync(EntityChangeReport changeReport) + public Task TriggerEventsAsync(EntityChangeReport changeReport, CancellationToken cancellationToken = default(CancellationToken)) { TriggerEventsInternal(changeReport); - if (changeReport.IsEmpty() || _unitOfWorkManager.Current == null) - { - return Task.FromResult(0); - } + if (changeReport.IsEmpty() || _unitOfWorkManager.Current == null) return Task.FromResult(0); - return _unitOfWorkManager.Current.SaveChangesAsync(); + return _unitOfWorkManager.Current.SaveChangesAsync(cancellationToken); } public virtual void TriggerEntityCreatingEvent(object entity) @@ -85,7 +80,7 @@ public virtual void TriggerEventsInternal(EntityChangeReport changeReport) protected virtual void TriggerEntityChangeEvents(List changedEntities) { - foreach (var changedEntity in changedEntities) + foreach (EntityChangeEntry changedEntity in changedEntities) { switch (changedEntity.ChangeType) { @@ -101,15 +96,14 @@ protected virtual void TriggerEntityChangeEvents(List changed TriggerEntityDeletingEvent(changedEntity.Entity); TriggerEntityDeletedEventOnUowCompleted(changedEntity.Entity); break; - default: - throw new StoveException("Unknown EntityChangeType: " + changedEntity.ChangeType); + default: throw new StoveException("Unknown EntityChangeType: " + changedEntity.ChangeType); } } } protected virtual void TriggerDomainEvents(List domainEvents) { - foreach (var domainEvent in domainEvents) + foreach (DomainEventEntry domainEvent in domainEvents) { EventBus.Trigger(domainEvent.EventData.GetType(), domainEvent.SourceEntity, domainEvent.EventData); } @@ -117,16 +111,16 @@ protected virtual void TriggerDomainEvents(List domainEvents) protected virtual void TriggerEventWithEntity(Type genericEventType, object entity, bool triggerInCurrentUnitOfWork) { - var entityType = entity.GetType(); - var eventType = genericEventType.MakeGenericType(entityType); + Type entityType = entity.GetType(); + Type eventType = genericEventType.MakeGenericType(entityType); if (triggerInCurrentUnitOfWork || _unitOfWorkManager.Current == null) { - EventBus.Trigger(eventType, (IEventData)Activator.CreateInstance(eventType, new[] { entity })); + EventBus.Trigger(eventType, (IEventData)Activator.CreateInstance(eventType, entity)); return; } - _unitOfWorkManager.Current.Completed += (sender, args) => EventBus.Trigger(eventType, (IEventData)Activator.CreateInstance(eventType, new[] { entity })); + _unitOfWorkManager.Current.Completed += (sender, args) => EventBus.Trigger(eventType, (IEventData)Activator.CreateInstance(eventType, entity)); } } -} \ No newline at end of file +} diff --git a/src/Stove/Events/Bus/Entities/IEntityChangeEventHelper.cs b/src/Stove/Events/Bus/Entities/IEntityChangeEventHelper.cs index ec0273f..e47e539 100644 --- a/src/Stove/Events/Bus/Entities/IEntityChangeEventHelper.cs +++ b/src/Stove/Events/Bus/Entities/IEntityChangeEventHelper.cs @@ -1,26 +1,27 @@ +using System.Threading; using System.Threading.Tasks; namespace Stove.Events.Bus.Entities { /// - /// Used to trigger entity change events. + /// Used to trigger entity change events. /// public interface IEntityChangeEventHelper { void TriggerEvents(EntityChangeReport changeReport); - Task TriggerEventsAsync(EntityChangeReport changeReport); + Task TriggerEventsAsync(EntityChangeReport changeReport, CancellationToken cancellationToken = default(CancellationToken)); void TriggerEntityCreatingEvent(object entity); void TriggerEntityCreatedEventOnUowCompleted(object entity); void TriggerEntityUpdatingEvent(object entity); - + void TriggerEntityUpdatedEventOnUowCompleted(object entity); void TriggerEntityDeletingEvent(object entity); - + void TriggerEntityDeletedEventOnUowCompleted(object entity); } -} \ No newline at end of file +} diff --git a/src/Stove/Events/Bus/Entities/NullEntityChangeEventHelper.cs b/src/Stove/Events/Bus/Entities/NullEntityChangeEventHelper.cs index a718d82..fe993da 100644 --- a/src/Stove/Events/Bus/Entities/NullEntityChangeEventHelper.cs +++ b/src/Stove/Events/Bus/Entities/NullEntityChangeEventHelper.cs @@ -1,52 +1,53 @@ +using System.Threading; using System.Threading.Tasks; namespace Stove.Events.Bus.Entities { - /// - /// Null-object implementation of . - /// - public class NullEntityChangeEventHelper : IEntityChangeEventHelper - { - private NullEntityChangeEventHelper() - { - } - - /// - /// Gets single instance of class. - /// - public static NullEntityChangeEventHelper Instance { get; } = new NullEntityChangeEventHelper(); - - public void TriggerEntityCreatingEvent(object entity) - { - } - - public void TriggerEntityCreatedEventOnUowCompleted(object entity) - { - } - - public void TriggerEntityUpdatingEvent(object entity) - { - } - - public void TriggerEntityUpdatedEventOnUowCompleted(object entity) - { - } - - public void TriggerEntityDeletingEvent(object entity) - { - } - - public void TriggerEntityDeletedEventOnUowCompleted(object entity) - { - } - - public void TriggerEvents(EntityChangeReport changeReport) - { - } - - public Task TriggerEventsAsync(EntityChangeReport changeReport) - { - return Task.FromResult(0); - } - } + /// + /// Null-object implementation of . + /// + public class NullEntityChangeEventHelper : IEntityChangeEventHelper + { + private NullEntityChangeEventHelper() + { + } + + /// + /// Gets single instance of class. + /// + public static NullEntityChangeEventHelper Instance { get; } = new NullEntityChangeEventHelper(); + + public void TriggerEntityCreatingEvent(object entity) + { + } + + public void TriggerEntityCreatedEventOnUowCompleted(object entity) + { + } + + public void TriggerEntityUpdatingEvent(object entity) + { + } + + public void TriggerEntityUpdatedEventOnUowCompleted(object entity) + { + } + + public void TriggerEntityDeletingEvent(object entity) + { + } + + public void TriggerEntityDeletedEventOnUowCompleted(object entity) + { + } + + public void TriggerEvents(EntityChangeReport changeReport) + { + } + + public Task TriggerEventsAsync(EntityChangeReport changeReport, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(0); + } + } } diff --git a/test/Stove.EntityFrameworkCore.Tests/Tests/Repository_Tests.cs b/test/Stove.EntityFrameworkCore.Tests/Tests/Repository_Tests.cs index 7cc3c17..3a2b7fd 100644 --- a/test/Stove.EntityFrameworkCore.Tests/Tests/Repository_Tests.cs +++ b/test/Stove.EntityFrameworkCore.Tests/Tests/Repository_Tests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -8,6 +10,8 @@ using Stove.Domain.Repositories; using Stove.Domain.Uow; using Stove.EntityFrameworkCore.Tests.Domain; +using Stove.Events.Bus; +using Stove.Events.Bus.Entities; using Xunit; @@ -21,7 +25,7 @@ public class Repository_Tests : EntityFrameworkCoreTestBase public Repository_Tests() { - Building(builder => { }).Ok(); + Building(builder => { }).Ok(); _uowManager = The(); _blogRepository = The>(); @@ -33,7 +37,7 @@ public void Should_Get_Initial_Blogs() { //Act - var blogs = _blogRepository.GetAllList(); + List blogs = _blogRepository.GetAllList(); //Assert @@ -47,9 +51,9 @@ public async Task Should_Automatically_Save_Changes_On_Uow() //Act - using (var uow = _uowManager.Begin()) + using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin()) { - var blog1 = await _blogRepository.SingleAsync(b => b.Name == "test-blog-1"); + Blog blog1 = await _blogRepository.SingleAsync(b => b.Name == "test-blog-1"); blog1Id = blog1.Id; blog1.Name = "test-blog-1-updated"; @@ -61,7 +65,7 @@ public async Task Should_Automatically_Save_Changes_On_Uow() await UsingDbContextAsync(async context => { - var blog1 = await context.Blogs.SingleAsync(b => b.Id == blog1Id); + Blog blog1 = await context.Blogs.SingleAsync(b => b.Id == blog1Id); blog1.Name.ShouldBe("test-blog-1-updated"); }); } @@ -71,9 +75,9 @@ public async Task Should_Not_Include_Navigation_Properties_If_Not_Requested() { //EF Core does not support lazy loading yet, so navigation properties will not be loaded if not included - using (var uow = _uowManager.Begin()) + using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin()) { - var post = await _postRepository.GetAll().FirstAsync(); + Post post = await _postRepository.GetAll().FirstAsync(); post.Blog.ShouldBeNull(); @@ -84,9 +88,9 @@ public async Task Should_Not_Include_Navigation_Properties_If_Not_Requested() [Fact] public async Task Should_Include_Navigation_Properties_If_Requested() { - using (var uow = _uowManager.Begin()) + using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin()) { - var post = await _postRepository.GetAllIncluding(p => p.Blog).FirstAsync(); + Post post = await _postRepository.GetAllIncluding(p => p.Blog).FirstAsync(); post.Blog.ShouldNotBeNull(); post.Blog.Name.ShouldBe("test-blog-1"); @@ -98,7 +102,7 @@ public async Task Should_Include_Navigation_Properties_If_Requested() [Fact] public async Task Should_Insert_New_Entity() { - using (var uow = _uowManager.Begin()) + using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin()) { var blog = new Blog("blog2", "http://myblog2.com"); blog.IsTransient().ShouldBeTrue(); @@ -111,9 +115,9 @@ public async Task Should_Insert_New_Entity() [Fact] public async Task Should_Insert_New_Entity_With_Guid_Id() { - using (var uow = _uowManager.Begin()) + using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin()) { - var blog1 = await _blogRepository.GetAsync(1); + Blog blog1 = await _blogRepository.GetAsync(1); var post = new Post(blog1, "a test title", "a test body"); post.IsTransient().ShouldBeTrue(); await _postRepository.InsertAsync(post); @@ -121,5 +125,41 @@ public async Task Should_Insert_New_Entity_With_Guid_Id() post.IsTransient().ShouldBeFalse(); } } + + [Fact] + public async Task should_rolled_back_when_CancellationToken_is_requested_as_cancel() + { + var ts = new CancellationTokenSource(); + + The().Register>(data => + { + ts.Cancel(true); + }); + + try + { + using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin()) + { + await _blogRepository.InsertAsync(new Blog("cancellationtoken", "cancellationtoken.com")); + + _blogRepository.FirstOrDefaultAsync(x=>x.Name == "cancellationtoken").ShouldNotBeNull(); + + await uow.CompleteAsync(ts.Token); + } + } + catch (Exception exception) + { + //Handle uow should be Rolled Back! + } + + using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin()) + { + Blog blog = await _blogRepository.FirstOrDefaultAsync(x => x.Name == "cancellationtoken"); + + blog.ShouldBeNull(); + + await uow.CompleteAsync(); + } + } } -} \ No newline at end of file +} diff --git a/test/Stove.NHibernate.Tests/General_Repository_Tests.cs b/test/Stove.NHibernate.Tests/General_Repository_Tests.cs index 712bb93..01b5030 100644 --- a/test/Stove.NHibernate.Tests/General_Repository_Tests.cs +++ b/test/Stove.NHibernate.Tests/General_Repository_Tests.cs @@ -1,6 +1,7 @@ -using System.Linq; - -using NHibernate.Linq; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Shouldly; @@ -143,5 +144,44 @@ public void Should_Trigger_Event_On_Delete() triggerCount.ShouldBe(1); } } + + [Fact] + public async Task Should_rollback_when_CancellationToken_cancel_is_requested() + { + var ts = new CancellationTokenSource(); + int updatingEventTriggerCount = 0; + try + { + using (IUnitOfWorkCompleteHandle uow = The().Begin()) + { + The().Register>( + eventData => + { + eventData.Entity.Name.ShouldBe("Pants"); + ts.Cancel(true); + updatingEventTriggerCount++; + }); + + Product product = The>().Single(p => p.Name == "TShirt"); + + product.Name = "Pants"; + + await uow.CompleteAsync(ts.Token); + } + } + catch (Exception exception) + { + //Handle + } + + using (IUnitOfWorkCompleteHandle uow = The().Begin()) + { + The>().FirstOrDefault(x => x.Name == "Pants").ShouldBeNull(); + + await uow.CompleteAsync(); + } + + updatingEventTriggerCount.ShouldBe(1); + } } } diff --git a/test/Stove.NHibernate.Tests/StoveNHibernateTestBase.cs b/test/Stove.NHibernate.Tests/StoveNHibernateTestBase.cs index ad733a6..3ea66e9 100644 --- a/test/Stove.NHibernate.Tests/StoveNHibernateTestBase.cs +++ b/test/Stove.NHibernate.Tests/StoveNHibernateTestBase.cs @@ -1,5 +1,5 @@ using System; -using System.Data; +using System.Data.Common; using System.Data.SQLite; using System.Reflection; @@ -31,7 +31,7 @@ protected StoveNHibernateTestBase(bool autoUowInterceptionEnabled = false) : bas nhConfiguration.FluentConfiguration .Database(SQLiteConfiguration.Standard.InMemory()) .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())) - .ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(true, true, false, The(), Console.Out)); + .ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(true, true, false, The(), Console.Out)); return nhConfiguration; }) @@ -40,14 +40,14 @@ protected StoveNHibernateTestBase(bool autoUowInterceptionEnabled = false) : bas .RegisterServices(r => { r.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); - r.Register(ctx => _connection, Lifetime.Singleton); + r.Register(ctx => _connection, Lifetime.Singleton); }); }); } public void UsingSession(Action action) { - using (ISession session = The().OpenSession(_connection)) + using (ISession session = The().OpenSessionWithConnection(_connection)) { using (ITransaction transaction = session.BeginTransaction()) { @@ -62,7 +62,7 @@ public T UsingSession(Func func) { T result; - using (ISession session = The().OpenSession(_connection)) + using (ISession session = The().OpenSessionWithConnection(_connection)) { using (ITransaction transaction = session.BeginTransaction()) { diff --git a/test/Stove.Tests.SampleApplication/Uow/Uow_Events_Tests.cs b/test/Stove.Tests.SampleApplication/Uow/Uow_Events_Tests.cs index 1915a64..2927e93 100644 --- a/test/Stove.Tests.SampleApplication/Uow/Uow_Events_Tests.cs +++ b/test/Stove.Tests.SampleApplication/Uow/Uow_Events_Tests.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Transactions; @@ -13,6 +15,7 @@ using Stove.Domain.Repositories; using Stove.Domain.Uow; using Stove.Events.Bus; +using Stove.Events.Bus.Entities; using Stove.Events.Bus.Handlers; using Stove.Tests.SampleApplication.Domain.Entities; @@ -100,13 +103,57 @@ public void Should_Trigger_events_with_proxied_objects() disposeCount++; }; - The>().FirstOrDefault(x=>x.Name == "Oğuzhan").ShouldNotBeNull(); + The>().FirstOrDefault(x => x.Name == "Oğuzhan").ShouldNotBeNull(); uow.Complete(); The().Trigger(new SomeUowEvent()); } - }); + }); + } + + [Fact] + public async Task should_rollback_when_CancellationToken_Cancel_is_requested() + { + var ts = new CancellationTokenSource(); + var uowManager = The(); + + try + { + The().Register>(data => + { + ts.Cancel(true); + }); + + using (IUnitOfWorkCompleteHandle uow = uowManager.Begin()) + { + The>().Insert(new User { Name = "CancellationToken", Email = "cancel@gmail", Surname = "token" }); + + uowManager.Current.Completed += (sender, args) => { }; + + uowManager.Current.Disposed += (sender, args) => + { + var provider = The(); + provider.ShouldNotBeNull(); + uowManager.Current.ShouldBe(null); + }; + + The>().FirstOrDefault(x => x.Name == "CancellationToken").ShouldNotBeNull(); + + await uow.CompleteAsync(ts.Token); + } + } + catch (Exception exception) + { + //Handled uow Rolled back! + } + + using (IUnitOfWorkCompleteHandle uow = uowManager.Begin()) + { + The>().FirstOrDefault(x => x.Name == "CancellationToken").ShouldBeNull(); + + await uow.CompleteAsync(); + } } } From d63b332ef6ffb6d7e23cfed1ac42ece1f97491b1 Mon Sep 17 00:00:00 2001 From: osoykan Date: Sun, 15 Oct 2017 11:59:41 +0300 Subject: [PATCH 04/11] DebuggerDisplay added to UnitOfWork --- src/Stove/Domain/Uow/UnitOfWorkBase.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Stove/Domain/Uow/UnitOfWorkBase.cs b/src/Stove/Domain/Uow/UnitOfWorkBase.cs index 182a0c1..e8cce7c 100644 --- a/src/Stove/Domain/Uow/UnitOfWorkBase.cs +++ b/src/Stove/Domain/Uow/UnitOfWorkBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -16,6 +17,7 @@ namespace Stove.Domain.Uow /// /// Base for all Unit Of Work classes. /// + [DebuggerDisplay("Id = {" + nameof(Id) + "}")] public abstract class UnitOfWorkBase : IUnitOfWork { private readonly List _filters; From 721204bf5dcc72a37e7e2a9c07c074ee542de169 Mon Sep 17 00:00:00 2001 From: osoykan Date: Sun, 15 Oct 2017 13:32:43 +0300 Subject: [PATCH 05/11] couchbase initial implementation #88 --- Stove.sln | 9 ++- .../IStoveCouchbaseConfiguration.cs | 7 ++ .../StoveCouchbaseConfiguration.cs | 8 +++ .../StoveCouchbaseConfigurationExtensions.cs | 12 ++++ .../Couchbase/ISessionProvider.cs | 9 +++ ...aseRepositoryBaseOfTEntityAndPrimaryKey.cs | 67 +++++++++++++++++++ .../StoveCouchbaseRegistrationExtensions.cs | 22 ++++++ .../Couchbase/Uow/CouchbaseUnitOfWork.cs | 62 +++++++++++++++++ .../Couchbase/Uow/UnitOfWorkExtensions.cs | 20 ++++++ .../Uow/UnitOfWorkSessionProvider.cs | 20 ++++++ src/Stove.Couchbase/Stove.Couchbase.csproj | 20 ++++++ .../RavenDB/Uow/UnitOfWorkExtensions.cs | 24 +++---- 12 files changed, 264 insertions(+), 16 deletions(-) create mode 100644 src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs create mode 100644 src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs create mode 100644 src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfigurationExtensions.cs create mode 100644 src/Stove.Couchbase/Couchbase/ISessionProvider.cs create mode 100644 src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntityAndPrimaryKey.cs create mode 100644 src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs create mode 100644 src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs create mode 100644 src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkExtensions.cs create mode 100644 src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkSessionProvider.cs create mode 100644 src/Stove.Couchbase/Stove.Couchbase.csproj diff --git a/Stove.sln b/Stove.sln index 3a30e0f..a7a6d50 100644 --- a/Stove.sln +++ b/Stove.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 +VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{4D2BAE52-1E23-4321-BBE6-2BAC28F7B389}" EndProject @@ -86,6 +86,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stove.Serilog", "src\Stove. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stove.Serilog.Tests", "test\Stove.Serilog.Tests\Stove.Serilog.Tests.csproj", "{38E61EC8-F69C-41B1-9356-F5FD6DA507F0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stove.Couchbase", "src\Stove.Couchbase\Stove.Couchbase.csproj", "{3AFF2B60-62F5-4547-9152-DD26E1A9918A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -224,6 +226,10 @@ Global {38E61EC8-F69C-41B1-9356-F5FD6DA507F0}.Debug|Any CPU.Build.0 = Debug|Any CPU {38E61EC8-F69C-41B1-9356-F5FD6DA507F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {38E61EC8-F69C-41B1-9356-F5FD6DA507F0}.Release|Any CPU.Build.0 = Release|Any CPU + {3AFF2B60-62F5-4547-9152-DD26E1A9918A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AFF2B60-62F5-4547-9152-DD26E1A9918A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AFF2B60-62F5-4547-9152-DD26E1A9918A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AFF2B60-62F5-4547-9152-DD26E1A9918A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -263,6 +269,7 @@ Global {03D9983F-C710-4765-8AE3-06C178E0C6BF} = {0EDEEA26-64C8-4391-9991-96E25131D4C6} {59590481-E87E-418F-B5D8-8C415FF4B027} = {23CB5044-8ECE-4DDC-89E0-FC1B8EC9DDDF} {38E61EC8-F69C-41B1-9356-F5FD6DA507F0} = {4D2BAE52-1E23-4321-BBE6-2BAC28F7B389} + {3AFF2B60-62F5-4547-9152-DD26E1A9918A} = {23CB5044-8ECE-4DDC-89E0-FC1B8EC9DDDF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FA9941D3-2A68-4501-B951-D2447D6EF432} diff --git a/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs b/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs new file mode 100644 index 0000000..43f8a05 --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs @@ -0,0 +1,7 @@ +namespace Stove.Couchbase.Couchbase.Configuration +{ + public interface IStoveCouchbaseConfiguration + { + + } +} diff --git a/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs b/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs new file mode 100644 index 0000000..bad930f --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs @@ -0,0 +1,8 @@ +using Autofac.Extras.IocManager; + +namespace Stove.Couchbase.Couchbase.Configuration +{ + public class StoveCouchbaseConfiguration : IStoveCouchbaseConfiguration, ISingletonDependency + { + } +} diff --git a/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfigurationExtensions.cs b/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfigurationExtensions.cs new file mode 100644 index 0000000..b91ecfe --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfigurationExtensions.cs @@ -0,0 +1,12 @@ +using Stove.Configuration; + +namespace Stove.Couchbase.Couchbase.Configuration +{ + public static class StoveCouchbaseConfigurationExtensions + { + public static IStoveCouchbaseConfiguration StoveCouchbase(this IModuleConfigurations modules) + { + return modules.StoveConfiguration.Get(); + } + } +} diff --git a/src/Stove.Couchbase/Couchbase/ISessionProvider.cs b/src/Stove.Couchbase/Couchbase/ISessionProvider.cs new file mode 100644 index 0000000..095caab --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/ISessionProvider.cs @@ -0,0 +1,9 @@ +using Couchbase.Linq; + +namespace Stove.Couchbase.Couchbase +{ + public interface ISessionProvider + { + IBucketContext Session { get; } + } +} diff --git a/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntityAndPrimaryKey.cs b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntityAndPrimaryKey.cs new file mode 100644 index 0000000..7f9bd4f --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntityAndPrimaryKey.cs @@ -0,0 +1,67 @@ +using System.Linq; + +using Couchbase; +using Couchbase.Linq; + +using Stove.Domain.Entities; +using Stove.Domain.Repositories; + +namespace Stove.Couchbase.Couchbase.Repositories +{ + public class CouchbaseRepositoryBase : StoveRepositoryBase where TEntity : class, IEntity + { + private readonly ISessionProvider _sessionProvider; + + public CouchbaseRepositoryBase(ISessionProvider sessionProvider) + { + _sessionProvider = sessionProvider; + } + + public IBucketContext Session => _sessionProvider.Session; + + public override IQueryable GetAll() + { + return Session.Query(); + } + + public override TEntity Insert(TEntity entity) + { + IDocumentResult result = Session.Bucket.Insert(new Document + { + Content = entity, + Id = entity.Id + }); + + result.EnsureSuccess(); + + return result.Content; + } + + public override TEntity Update(TEntity entity) + { + IDocumentResult result = Session.Bucket.Upsert(new Document + { + Content = entity, + Id = entity.Id + }); + + result.EnsureSuccess(); + + return result.Content; + } + + public override void Delete(TEntity entity) + { + Session.Bucket.Remove(new Document + { + Content = entity, + Id = entity.Id + }).EnsureSuccess(); + } + + public override void Delete(string id) + { + Session.Bucket.Remove(id).EnsureSuccess(); + } + } +} diff --git a/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs b/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs new file mode 100644 index 0000000..afb0304 --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs @@ -0,0 +1,22 @@ +using Autofac.Extras.IocManager; + +using Stove.Couchbase.Couchbase.Repositories; +using Stove.Domain.Repositories; +using Stove.Reflection.Extensions; + +namespace Stove.Couchbase.Couchbase +{ + public static class StoveCouchbaseRegistrationExtensions + { + public static IIocBuilder UseStoveCouchbase(IIocBuilder builder) + { + return builder + .RegisterServices(r => + { + r.RegisterGeneric(typeof(IRepository<>), typeof(CouchbaseRepositoryBase<>)); + + r.RegisterAssemblyByConvention(typeof(StoveCouchbaseRegistrationExtensions).GetAssembly()); + }); + } + } +} diff --git a/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs b/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs new file mode 100644 index 0000000..3488fef --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs @@ -0,0 +1,62 @@ +using System.Threading; +using System.Threading.Tasks; + +using Autofac.Extras.IocManager; + +using Couchbase.Core; +using Couchbase.Linq; + +using Stove.Domain.Uow; + +namespace Stove.Couchbase.Couchbase.Uow +{ + public class CouchbaseUnitOfWork : UnitOfWorkBase, ITransientDependency + { + private readonly ICluster _cluster; + + public CouchbaseUnitOfWork( + IConnectionStringResolver connectionStringResolver, + IUnitOfWorkDefaultOptions defaultOptions, + IUnitOfWorkFilterExecuter filterExecuter, + ICluster cluster) : base(connectionStringResolver, defaultOptions, filterExecuter) + { + _cluster = cluster; + } + + public IBucketContext Session { get; private set; } + + protected override void BeginUow() + { + Session = new BucketContext(_cluster.OpenBucket()); + Session.BeginChangeTracking(); + } + + public override void SaveChanges() + { + Session.SubmitChanges(); + } + + public override Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + SaveChanges(); + return Task.CompletedTask; + } + + protected override void CompleteUow() + { + SaveChanges(); + Session.EndChangeTracking(); + } + + protected override Task CompleteUowAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + CompleteUow(); + return Task.CompletedTask; + } + + protected override void DisposeUow() + { + Session = null; + } + } +} diff --git a/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkExtensions.cs b/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkExtensions.cs new file mode 100644 index 0000000..5b0e589 --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkExtensions.cs @@ -0,0 +1,20 @@ +using System; + +using Couchbase.Linq; + +using Stove.Domain.Uow; + +namespace Stove.Couchbase.Couchbase.Uow +{ + public static class UnitOfWorkExtensions + { + public static IBucketContext GetSession(this IActiveUnitOfWork unitOfWork) + { + if (unitOfWork == null) throw new ArgumentNullException(nameof(unitOfWork)); + + if (!(unitOfWork is CouchbaseUnitOfWork)) throw new ArgumentException("unitOfWork is not type of " + typeof(CouchbaseUnitOfWork).FullName, nameof(unitOfWork)); + + return ((CouchbaseUnitOfWork)unitOfWork).Session; + } + } +} diff --git a/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkSessionProvider.cs b/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkSessionProvider.cs new file mode 100644 index 0000000..a0885b1 --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkSessionProvider.cs @@ -0,0 +1,20 @@ +using Autofac.Extras.IocManager; + +using Couchbase.Linq; + +using Stove.Domain.Uow; + +namespace Stove.Couchbase.Couchbase.Uow +{ + public class UnitOfWorkSessionProvider : ISessionProvider, ITransientDependency + { + private readonly ICurrentUnitOfWorkProvider _unitOfWorkProvider; + + public UnitOfWorkSessionProvider(ICurrentUnitOfWorkProvider unitOfWorkProvider) + { + _unitOfWorkProvider = unitOfWorkProvider; + } + + public IBucketContext Session => _unitOfWorkProvider.Current.GetSession(); + } +} diff --git a/src/Stove.Couchbase/Stove.Couchbase.csproj b/src/Stove.Couchbase/Stove.Couchbase.csproj new file mode 100644 index 0000000..7d4b508 --- /dev/null +++ b/src/Stove.Couchbase/Stove.Couchbase.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + + + + + + + + + + + + + + + + diff --git a/src/Stove.RavenDB/RavenDB/Uow/UnitOfWorkExtensions.cs b/src/Stove.RavenDB/RavenDB/Uow/UnitOfWorkExtensions.cs index cd3e3ed..c96bab3 100644 --- a/src/Stove.RavenDB/RavenDB/Uow/UnitOfWorkExtensions.cs +++ b/src/Stove.RavenDB/RavenDB/Uow/UnitOfWorkExtensions.cs @@ -6,21 +6,15 @@ namespace Stove.RavenDB.Uow { - internal static class UnitOfWorkExtensions - { - public static IDocumentSession GetSession(this IActiveUnitOfWork unitOfWork) - { - if (unitOfWork == null) - { - throw new ArgumentNullException(nameof(unitOfWork)); - } + internal static class UnitOfWorkExtensions + { + public static IDocumentSession GetSession(this IActiveUnitOfWork unitOfWork) + { + if (unitOfWork == null) throw new ArgumentNullException(nameof(unitOfWork)); - if (!(unitOfWork is RavenDBUnitOfWork)) - { - throw new ArgumentException("unitOfWork is not type of " + typeof(RavenDBUnitOfWork).FullName, nameof(unitOfWork)); - } + if (!(unitOfWork is RavenDBUnitOfWork)) throw new ArgumentException("unitOfWork is not type of " + typeof(RavenDBUnitOfWork).FullName, nameof(unitOfWork)); - return (unitOfWork as RavenDBUnitOfWork).Session; - } - } + return ((RavenDBUnitOfWork)unitOfWork).Session; + } + } } From 22c2eb5dab041f48ec259b11abceed6b39d4986f Mon Sep 17 00:00:00 2001 From: osoykan Date: Sun, 15 Oct 2017 15:34:54 +0300 Subject: [PATCH 06/11] Couchbase Action filters added & refactors #88 --- Stove.sln | 9 +- .../IStoveCouchbaseConfiguration.cs | 6 +- .../StoveCouchbaseConfiguration.cs | 8 + .../Action/CouchbaseActionFilterExecuter.cs | 32 ++++ .../Action/ICouchbaseActionFilterExecuter.cs | 13 ++ .../NullCouchbaseActionFilterExecuter.cs | 21 +++ .../CouchbaseRepositoryBaseOfTEntity.cs | 95 +++++++++++ ...aseRepositoryBaseOfTEntityAndPrimaryKey.cs | 67 -------- .../Couchbase/StoveCouchbaseBootstrapper.cs | 20 +++ .../StoveCouchbaseRegistrationExtensions.cs | 18 ++- .../Couchbase/Uow/CouchbaseUnitOfWork.cs | 8 +- src/Stove.Couchbase/Stove.Couchbase.csproj | 4 - .../Uow/UnitOfWorkExtensions.cs | 12 +- .../Action/CreationAuditActionFilter.cs | 3 +- .../CouchbaseTestBase.cs | 49 ++++++ .../CouchbaseTestBootstrapper.cs | 12 ++ test/Stove.Couchbase.Tests/Entities/Order.cs | 21 +++ .../Stove.Couchbase.Tests/Entities/Product.cs | 18 +++ .../General_Repository_Tests.cs | 150 ++++++++++++++++++ .../Stove.Couchbase.Tests.csproj | 18 +++ 20 files changed, 494 insertions(+), 90 deletions(-) create mode 100644 src/Stove.Couchbase/Couchbase/Filters/Action/CouchbaseActionFilterExecuter.cs create mode 100644 src/Stove.Couchbase/Couchbase/Filters/Action/ICouchbaseActionFilterExecuter.cs create mode 100644 src/Stove.Couchbase/Couchbase/Filters/Action/NullCouchbaseActionFilterExecuter.cs create mode 100644 src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs delete mode 100644 src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntityAndPrimaryKey.cs create mode 100644 src/Stove.Couchbase/Couchbase/StoveCouchbaseBootstrapper.cs create mode 100644 test/Stove.Couchbase.Tests/CouchbaseTestBase.cs create mode 100644 test/Stove.Couchbase.Tests/CouchbaseTestBootstrapper.cs create mode 100644 test/Stove.Couchbase.Tests/Entities/Order.cs create mode 100644 test/Stove.Couchbase.Tests/Entities/Product.cs create mode 100644 test/Stove.Couchbase.Tests/General_Repository_Tests.cs create mode 100644 test/Stove.Couchbase.Tests/Stove.Couchbase.Tests.csproj diff --git a/Stove.sln b/Stove.sln index a7a6d50..6d43be4 100644 --- a/Stove.sln +++ b/Stove.sln @@ -86,7 +86,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stove.Serilog", "src\Stove. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stove.Serilog.Tests", "test\Stove.Serilog.Tests\Stove.Serilog.Tests.csproj", "{38E61EC8-F69C-41B1-9356-F5FD6DA507F0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stove.Couchbase", "src\Stove.Couchbase\Stove.Couchbase.csproj", "{3AFF2B60-62F5-4547-9152-DD26E1A9918A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stove.Couchbase", "src\Stove.Couchbase\Stove.Couchbase.csproj", "{3AFF2B60-62F5-4547-9152-DD26E1A9918A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stove.Couchbase.Tests", "test\Stove.Couchbase.Tests\Stove.Couchbase.Tests.csproj", "{46E3FC0A-20B8-4749-AA68-1D165249211C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -230,6 +232,10 @@ Global {3AFF2B60-62F5-4547-9152-DD26E1A9918A}.Debug|Any CPU.Build.0 = Debug|Any CPU {3AFF2B60-62F5-4547-9152-DD26E1A9918A}.Release|Any CPU.ActiveCfg = Release|Any CPU {3AFF2B60-62F5-4547-9152-DD26E1A9918A}.Release|Any CPU.Build.0 = Release|Any CPU + {46E3FC0A-20B8-4749-AA68-1D165249211C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46E3FC0A-20B8-4749-AA68-1D165249211C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46E3FC0A-20B8-4749-AA68-1D165249211C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46E3FC0A-20B8-4749-AA68-1D165249211C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -270,6 +276,7 @@ Global {59590481-E87E-418F-B5D8-8C415FF4B027} = {23CB5044-8ECE-4DDC-89E0-FC1B8EC9DDDF} {38E61EC8-F69C-41B1-9356-F5FD6DA507F0} = {4D2BAE52-1E23-4321-BBE6-2BAC28F7B389} {3AFF2B60-62F5-4547-9152-DD26E1A9918A} = {23CB5044-8ECE-4DDC-89E0-FC1B8EC9DDDF} + {46E3FC0A-20B8-4749-AA68-1D165249211C} = {4D2BAE52-1E23-4321-BBE6-2BAC28F7B389} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FA9941D3-2A68-4501-B951-D2447D6EF432} diff --git a/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs b/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs index 43f8a05..93ecc5f 100644 --- a/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs +++ b/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs @@ -1,7 +1,9 @@ -namespace Stove.Couchbase.Couchbase.Configuration +using Couchbase.Configuration.Client; + +namespace Stove.Couchbase.Couchbase.Configuration { public interface IStoveCouchbaseConfiguration { - + ClientConfiguration ClientConfiguration { get; set; } } } diff --git a/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs b/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs index bad930f..2f5bc00 100644 --- a/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs +++ b/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs @@ -1,8 +1,16 @@ using Autofac.Extras.IocManager; +using Couchbase.Configuration.Client; + namespace Stove.Couchbase.Couchbase.Configuration { public class StoveCouchbaseConfiguration : IStoveCouchbaseConfiguration, ISingletonDependency { + public StoveCouchbaseConfiguration() + { + ClientConfiguration = new ClientConfiguration(); + } + + public ClientConfiguration ClientConfiguration { get; set; } } } diff --git a/src/Stove.Couchbase/Couchbase/Filters/Action/CouchbaseActionFilterExecuter.cs b/src/Stove.Couchbase/Couchbase/Filters/Action/CouchbaseActionFilterExecuter.cs new file mode 100644 index 0000000..9239ec8 --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Filters/Action/CouchbaseActionFilterExecuter.cs @@ -0,0 +1,32 @@ +using Autofac.Extras.IocManager; + +using Stove.Domain.Entities; +using Stove.Domain.Uow.DynamicFilters.Action; + +namespace Stove.Couchbase.Couchbase.Filters.Action +{ + public class CouchbaseActionFilterExecuter : ICouchbaseActionFilterExecuter, ITransientDependency + { + private readonly IScopeResolver _scopeResolver; + + public CouchbaseActionFilterExecuter(IScopeResolver scopeResolver) + { + _scopeResolver = scopeResolver; + } + + public void ExecuteCreationAuditFilter(TEntity entity) where TEntity : class, IEntity + { + _scopeResolver.Resolve().ExecuteFilter(entity); + } + + public void ExecuteModificationAuditFilter(TEntity entity) where TEntity : class, IEntity + { + _scopeResolver.Resolve().ExecuteFilter(entity); + } + + public void ExecuteDeletionAuditFilter(TEntity entity) where TEntity : class, IEntity + { + _scopeResolver.Resolve().ExecuteFilter(entity); + } + } +} diff --git a/src/Stove.Couchbase/Couchbase/Filters/Action/ICouchbaseActionFilterExecuter.cs b/src/Stove.Couchbase/Couchbase/Filters/Action/ICouchbaseActionFilterExecuter.cs new file mode 100644 index 0000000..eb76f94 --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Filters/Action/ICouchbaseActionFilterExecuter.cs @@ -0,0 +1,13 @@ +using Stove.Domain.Entities; + +namespace Stove.Couchbase.Couchbase.Filters.Action +{ + public interface ICouchbaseActionFilterExecuter + { + void ExecuteCreationAuditFilter(TEntity entity) where TEntity : class, IEntity; + + void ExecuteModificationAuditFilter(TEntity entity) where TEntity : class, IEntity; + + void ExecuteDeletionAuditFilter(TEntity entity) where TEntity : class, IEntity; + } +} diff --git a/src/Stove.Couchbase/Couchbase/Filters/Action/NullCouchbaseActionFilterExecuter.cs b/src/Stove.Couchbase/Couchbase/Filters/Action/NullCouchbaseActionFilterExecuter.cs new file mode 100644 index 0000000..d5abd80 --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Filters/Action/NullCouchbaseActionFilterExecuter.cs @@ -0,0 +1,21 @@ +using Stove.Domain.Entities; + +namespace Stove.Couchbase.Couchbase.Filters.Action +{ + public class NullCouchbaseActionFilterExecuter : ICouchbaseActionFilterExecuter + { + public static NullCouchbaseActionFilterExecuter Instance = new NullCouchbaseActionFilterExecuter(); + + public void ExecuteCreationAuditFilter(TEntity entity) where TEntity : class, IEntity + { + } + + public void ExecuteModificationAuditFilter(TEntity entity) where TEntity : class, IEntity + { + } + + public void ExecuteDeletionAuditFilter(TEntity entity) where TEntity : class, IEntity + { + } + } +} diff --git a/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs new file mode 100644 index 0000000..34fbfed --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs @@ -0,0 +1,95 @@ +using System.Linq; + +using Couchbase; +using Couchbase.Linq; + +using Stove.Couchbase.Couchbase.Filters.Action; +using Stove.Domain.Entities; +using Stove.Domain.Repositories; +using Stove.Extensions; + +namespace Stove.Couchbase.Couchbase.Repositories +{ + public class CouchbaseRepositoryBase : StoveRepositoryBase where TEntity : class, IEntity + { + private readonly ICouchbaseActionFilterExecuter _actionFilterExecuter; + private readonly ISessionProvider _sessionProvider; + + public CouchbaseRepositoryBase( + ISessionProvider sessionProvider, + ICouchbaseActionFilterExecuter actionFilterExecuter) + { + _sessionProvider = sessionProvider; + _actionFilterExecuter = actionFilterExecuter; + + GuidGenerator = SequentialGuidGenerator.Instance; + } + + public IGuidGenerator GuidGenerator { get; set; } + + public IBucketContext Session => _sessionProvider.Session; + + public override IQueryable GetAll() + { + return Session.Query(); + } + + public override TEntity Insert(TEntity entity) + { + entity.Id = GuidGenerator.Create().ToString("N"); + _actionFilterExecuter.ExecuteCreationAuditFilter(entity); + IDocumentResult result = Session.Bucket.Insert(new Document + { + Content = entity, + Id = $"{typeof(TEntity).Name}:{entity.Id}" + }); + + result.EnsureSuccess(); + + return result.Content; + } + + public override TEntity Update(TEntity entity) + { + _actionFilterExecuter.ExecuteModificationAuditFilter(entity); + IDocumentResult result = Session.Bucket.Upsert(new Document + { + Content = entity, + Id = $"{typeof(TEntity).Name}:{entity.Id}" + }); + + result.EnsureSuccess(); + + return result.Content; + } + + public override void Delete(TEntity entity) + { + _actionFilterExecuter.ExecuteDeletionAuditFilter(entity); + + if (entity is ISoftDelete) + { + entity.As().IsDeleted = true; + + Session.Bucket.Upsert(new Document + { + Content = entity, + Id = $"{typeof(TEntity).Name}:{entity.Id}" + }).EnsureSuccess(); + } + else + { + Session.Bucket.Remove(new Document + { + Content = entity, + Id = $"{typeof(TEntity).Name}:{entity.Id}" + }).EnsureSuccess(); + } + } + + public override void Delete(string id) + { + Session.Bucket.Remove(id).EnsureSuccess(); + } + } +} diff --git a/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntityAndPrimaryKey.cs b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntityAndPrimaryKey.cs deleted file mode 100644 index 7f9bd4f..0000000 --- a/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntityAndPrimaryKey.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Linq; - -using Couchbase; -using Couchbase.Linq; - -using Stove.Domain.Entities; -using Stove.Domain.Repositories; - -namespace Stove.Couchbase.Couchbase.Repositories -{ - public class CouchbaseRepositoryBase : StoveRepositoryBase where TEntity : class, IEntity - { - private readonly ISessionProvider _sessionProvider; - - public CouchbaseRepositoryBase(ISessionProvider sessionProvider) - { - _sessionProvider = sessionProvider; - } - - public IBucketContext Session => _sessionProvider.Session; - - public override IQueryable GetAll() - { - return Session.Query(); - } - - public override TEntity Insert(TEntity entity) - { - IDocumentResult result = Session.Bucket.Insert(new Document - { - Content = entity, - Id = entity.Id - }); - - result.EnsureSuccess(); - - return result.Content; - } - - public override TEntity Update(TEntity entity) - { - IDocumentResult result = Session.Bucket.Upsert(new Document - { - Content = entity, - Id = entity.Id - }); - - result.EnsureSuccess(); - - return result.Content; - } - - public override void Delete(TEntity entity) - { - Session.Bucket.Remove(new Document - { - Content = entity, - Id = entity.Id - }).EnsureSuccess(); - } - - public override void Delete(string id) - { - Session.Bucket.Remove(id).EnsureSuccess(); - } - } -} diff --git a/src/Stove.Couchbase/Couchbase/StoveCouchbaseBootstrapper.cs b/src/Stove.Couchbase/Couchbase/StoveCouchbaseBootstrapper.cs new file mode 100644 index 0000000..a949e97 --- /dev/null +++ b/src/Stove.Couchbase/Couchbase/StoveCouchbaseBootstrapper.cs @@ -0,0 +1,20 @@ +using Couchbase.Core; + +using Stove.Bootstrapping; +using Stove.Couchbase.Couchbase.Configuration; + +namespace Stove.Couchbase.Couchbase +{ + public class StoveCouchbaseBootstrapper : StoveBootstrapper + { + public override void PreStart() + { + StoveConfiguration.GetConfigurerIfExists()(StoveConfiguration.Modules.StoveCouchbase()); + } + + public override void Shutdown() + { + Resolver.Resolve().Dispose(); + } + } +} diff --git a/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs b/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs index afb0304..c236a7e 100644 --- a/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs +++ b/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs @@ -1,5 +1,11 @@ -using Autofac.Extras.IocManager; +using System; +using Autofac.Extras.IocManager; + +using Couchbase; +using Couchbase.Core; + +using Stove.Couchbase.Couchbase.Configuration; using Stove.Couchbase.Couchbase.Repositories; using Stove.Domain.Repositories; using Stove.Reflection.Extensions; @@ -8,12 +14,18 @@ namespace Stove.Couchbase.Couchbase { public static class StoveCouchbaseRegistrationExtensions { - public static IIocBuilder UseStoveCouchbase(IIocBuilder builder) + public static IIocBuilder UseStoveCouchbase(this IIocBuilder builder, Func configurer) { return builder .RegisterServices(r => { - r.RegisterGeneric(typeof(IRepository<>), typeof(CouchbaseRepositoryBase<>)); + r.RegisterGeneric(typeof(IRepository<,>), typeof(CouchbaseRepositoryBase<>)); + r.Register(ctx => configurer); + r.Register(ctx => + { + var cfg = ctx.Resolver.Resolve(); + return new Cluster(cfg.ClientConfiguration); + }, Lifetime.Singleton); r.RegisterAssemblyByConvention(typeof(StoveCouchbaseRegistrationExtensions).GetAssembly()); }); diff --git a/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs b/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs index 3488fef..cbbd731 100644 --- a/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs +++ b/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs @@ -25,9 +25,12 @@ public CouchbaseUnitOfWork( public IBucketContext Session { get; private set; } + public IBucket Bucket { get; private set; } + protected override void BeginUow() { - Session = new BucketContext(_cluster.OpenBucket()); + Bucket = _cluster.OpenBucket(); + Session = new BucketContext(Bucket); Session.BeginChangeTracking(); } @@ -56,7 +59,8 @@ protected override void CompleteUow() protected override void DisposeUow() { - Session = null; + Bucket.Dispose(); + _cluster.CloseBucket(Bucket); } } } diff --git a/src/Stove.Couchbase/Stove.Couchbase.csproj b/src/Stove.Couchbase/Stove.Couchbase.csproj index 7d4b508..10b2d7c 100644 --- a/src/Stove.Couchbase/Stove.Couchbase.csproj +++ b/src/Stove.Couchbase/Stove.Couchbase.csproj @@ -4,10 +4,6 @@ netstandard2.0 - - - - diff --git a/src/Stove.EntityFramework/EntityFramework/Uow/UnitOfWorkExtensions.cs b/src/Stove.EntityFramework/EntityFramework/Uow/UnitOfWorkExtensions.cs index 354a606..1523eda 100644 --- a/src/Stove.EntityFramework/EntityFramework/Uow/UnitOfWorkExtensions.cs +++ b/src/Stove.EntityFramework/EntityFramework/Uow/UnitOfWorkExtensions.cs @@ -10,17 +10,11 @@ public static class UnitOfWorkExtensions public static TDbContext GetDbContext(this IActiveUnitOfWork unitOfWork) where TDbContext : DbContext { - if (unitOfWork == null) - { - throw new ArgumentNullException(nameof(unitOfWork)); - } + if (unitOfWork == null) throw new ArgumentNullException(nameof(unitOfWork)); - if (!(unitOfWork is EfUnitOfWork)) - { - throw new ArgumentException("unitOfWork is not type of " + typeof(EfUnitOfWork).FullName, nameof(unitOfWork)); - } + if (!(unitOfWork is EfUnitOfWork)) throw new ArgumentException("unitOfWork is not type of " + typeof(EfUnitOfWork).FullName, nameof(unitOfWork)); - return (unitOfWork as EfUnitOfWork).GetOrCreateDbContext(); + return ((EfUnitOfWork)unitOfWork).GetOrCreateDbContext(); } } } diff --git a/src/Stove/Domain/Uow/DynamicFilters/Action/CreationAuditActionFilter.cs b/src/Stove/Domain/Uow/DynamicFilters/Action/CreationAuditActionFilter.cs index 27d04ea..62154a8 100644 --- a/src/Stove/Domain/Uow/DynamicFilters/Action/CreationAuditActionFilter.cs +++ b/src/Stove/Domain/Uow/DynamicFilters/Action/CreationAuditActionFilter.cs @@ -12,8 +12,7 @@ public override void ExecuteFilter(TEntity entity) { long? userId = GetAuditUserId(); CheckAndSetId(entity); - var entityWithCreationTime = entity as IHasCreationTime; - if (entityWithCreationTime == null) + if (!(entity is IHasCreationTime entityWithCreationTime)) { return; } diff --git a/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs b/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs new file mode 100644 index 0000000..5090ebf --- /dev/null +++ b/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +using Couchbase.Configuration.Client; + +using Stove.Couchbase.Couchbase; +using Stove.TestBase; + +namespace Stove.Couchbase.Tests +{ + public abstract class CouchbaseTestBase : ApplicationTestBase + { + protected CouchbaseTestBase() : base(true) + { + Building(builder => + { + builder + .UseStoveCouchbase(configuration => + { + ClientConfiguration cfg = configuration.ClientConfiguration; + cfg.Servers.Add(new Uri("http://127.0.0.1:8091/pools")); + cfg.UseSsl = false; + cfg.BucketConfigs = new Dictionary + { + { + "default", new BucketConfiguration + { + BucketName = "default", + UseSsl = false, + Password = "", + DefaultOperationLifespan = 2000, + PoolConfiguration = new PoolConfiguration + { + MaxSize = 10, + MinSize = 5, + SendTimeout = 12000 + } + } + } + }; + + return configuration; + }) + .RegisterServices(r => { r.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); }); + }); + } + } +} diff --git a/test/Stove.Couchbase.Tests/CouchbaseTestBootstrapper.cs b/test/Stove.Couchbase.Tests/CouchbaseTestBootstrapper.cs new file mode 100644 index 0000000..4797e95 --- /dev/null +++ b/test/Stove.Couchbase.Tests/CouchbaseTestBootstrapper.cs @@ -0,0 +1,12 @@ +using Stove.Bootstrapping; +using Stove.Couchbase.Couchbase; + +namespace Stove.Couchbase.Tests +{ + [DependsOn( + typeof(StoveCouchbaseBootstrapper) + )] + public class CouchbaseTestBootstrapper : StoveBootstrapper + { + } +} diff --git a/test/Stove.Couchbase.Tests/Entities/Order.cs b/test/Stove.Couchbase.Tests/Entities/Order.cs new file mode 100644 index 0000000..fd590a0 --- /dev/null +++ b/test/Stove.Couchbase.Tests/Entities/Order.cs @@ -0,0 +1,21 @@ +using Stove.Domain.Entities.Auditing; + +namespace Stove.Couchbase.Tests.Entities +{ + public class Order : FullAuditedEntity + { + protected Order() + { + } + + public Order(string address, Product product) : this() + { + Address = address; + Product = product; + } + + public string Address { get; set; } + + public Product Product { get; set; } + } +} diff --git a/test/Stove.Couchbase.Tests/Entities/Product.cs b/test/Stove.Couchbase.Tests/Entities/Product.cs new file mode 100644 index 0000000..c1d74cd --- /dev/null +++ b/test/Stove.Couchbase.Tests/Entities/Product.cs @@ -0,0 +1,18 @@ +using Stove.Domain.Entities; + +namespace Stove.Couchbase.Tests.Entities +{ + public class Product : Entity + { + protected Product() + { + } + + public Product(string name) : this() + { + Name = name; + } + + public virtual string Name { get; set; } + } +} diff --git a/test/Stove.Couchbase.Tests/General_Repository_Tests.cs b/test/Stove.Couchbase.Tests/General_Repository_Tests.cs new file mode 100644 index 0000000..e18d9c6 --- /dev/null +++ b/test/Stove.Couchbase.Tests/General_Repository_Tests.cs @@ -0,0 +1,150 @@ +using System; + +using Shouldly; + +using Stove.Couchbase.Tests.Entities; +using Stove.Domain.Repositories; +using Stove.Domain.Uow; + +using Xunit; + +namespace Stove.Couchbase.Tests +{ + public class General_Repository_Tests : CouchbaseTestBase + { + public General_Repository_Tests() + { + Building(builder => { }).Ok(); + } + + [Fact] + public void Insert_should_work() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + string productName = Guid.NewGuid().ToString("N"); + var product = new Product(productName); + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Product inserted = The>().Insert(product); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + Product item = The>().FirstOrDefault(x => x.Id == inserted.Id); + item.ShouldNotBeNull(); + } + + [Fact] + public void Update_should_work() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + string productName = Guid.NewGuid().ToString("N"); + var product = new Product(productName); + + string updateName = Guid.NewGuid().ToString("N"); + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + The>().Insert(product); + + Product item = The>().FirstOrDefault(x => x.Name == productName); + item.Name = updateName; + The>().Update(item); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + Product pant = The>().FirstOrDefault(x => x.Name == updateName); + pant.Name.ShouldBe(updateName); + pant.ShouldNotBeNull(); + } + + [Fact] + public void Update_should_work_with_object_tracking_mechanism() + { + using (IUnitOfWorkCompleteHandle uow = The().Begin()) + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + string productName = Guid.NewGuid().ToString("N"); + var product = new Product(productName); + + string updateName = Guid.NewGuid().ToString("N"); + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + The>().Insert(product); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + Product item = The>().FirstOrDefault(x => x.Name == productName); + item.Name = updateName; + + The().Current.SaveChanges(); + + Product pant = The>().FirstOrDefault(x => x.Name == item.Name); + pant.ShouldNotBeNull(); + pant.Name.ShouldBe(updateName); + + uow.Complete(); + } + } + + [Fact] + public void Delete_should_work_on_hard_deletable_entities() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + string productName = Guid.NewGuid().ToString("N"); + var product = new Product(productName); + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Product inserted = The>().Insert(product); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + The>().Delete(inserted); + + Product deleted = The>().FirstOrDefault(x => x.Name == productName); + deleted.ShouldBeNull(); + } + + [Fact] + public void Delete_should_work_soft_deletable_entities() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + string productName = Guid.NewGuid().ToString("N"); + string address = Guid.NewGuid().ToString("N"); + var product = new Product(productName); + var order = new Order(address, product); + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Order insertedOrder = The>().Insert(order); + + The>().Delete(insertedOrder); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + insertedOrder.IsDeleted.ShouldBe(true); + } + } +} diff --git a/test/Stove.Couchbase.Tests/Stove.Couchbase.Tests.csproj b/test/Stove.Couchbase.Tests/Stove.Couchbase.Tests.csproj new file mode 100644 index 0000000..f5b5d6d --- /dev/null +++ b/test/Stove.Couchbase.Tests/Stove.Couchbase.Tests.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + + + From 94053555ec38cff33d6e290120226fca446e8ec1 Mon Sep 17 00:00:00 2001 From: osoykan Date: Sun, 15 Oct 2017 15:37:56 +0300 Subject: [PATCH 07/11] csproj edited & namespace refactors --- .../IStoveCouchbaseConfiguration.cs | 2 +- .../StoveCouchbaseConfiguration.cs | 2 +- .../StoveCouchbaseConfigurationExtensions.cs | 2 +- .../Action/CouchbaseActionFilterExecuter.cs | 2 +- .../Action/ICouchbaseActionFilterExecuter.cs | 2 +- .../NullCouchbaseActionFilterExecuter.cs | 2 +- .../Couchbase/ISessionProvider.cs | 2 +- .../CouchbaseRepositoryBaseOfTEntity.cs | 4 ++-- .../Couchbase/StoveCouchbaseBootstrapper.cs | 4 ++-- .../StoveCouchbaseRegistrationExtensions.cs | 6 +++--- .../Couchbase/Uow/CouchbaseUnitOfWork.cs | 2 +- .../Couchbase/Uow/UnitOfWorkExtensions.cs | 2 +- .../Couchbase/Uow/UnitOfWorkSessionProvider.cs | 2 +- src/Stove.Couchbase/Stove.Couchbase.csproj | 18 ++++++++++++++++++ .../Stove.Couchbase.Tests/CouchbaseTestBase.cs | 1 - .../CouchbaseTestBootstrapper.cs | 1 - 16 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs b/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs index 93ecc5f..4189d43 100644 --- a/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs +++ b/src/Stove.Couchbase/Couchbase/Configuration/IStoveCouchbaseConfiguration.cs @@ -1,6 +1,6 @@ using Couchbase.Configuration.Client; -namespace Stove.Couchbase.Couchbase.Configuration +namespace Stove.Couchbase.Configuration { public interface IStoveCouchbaseConfiguration { diff --git a/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs b/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs index 2f5bc00..fb4bc22 100644 --- a/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs +++ b/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfiguration.cs @@ -2,7 +2,7 @@ using Couchbase.Configuration.Client; -namespace Stove.Couchbase.Couchbase.Configuration +namespace Stove.Couchbase.Configuration { public class StoveCouchbaseConfiguration : IStoveCouchbaseConfiguration, ISingletonDependency { diff --git a/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfigurationExtensions.cs b/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfigurationExtensions.cs index b91ecfe..9e25b57 100644 --- a/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfigurationExtensions.cs +++ b/src/Stove.Couchbase/Couchbase/Configuration/StoveCouchbaseConfigurationExtensions.cs @@ -1,6 +1,6 @@ using Stove.Configuration; -namespace Stove.Couchbase.Couchbase.Configuration +namespace Stove.Couchbase.Configuration { public static class StoveCouchbaseConfigurationExtensions { diff --git a/src/Stove.Couchbase/Couchbase/Filters/Action/CouchbaseActionFilterExecuter.cs b/src/Stove.Couchbase/Couchbase/Filters/Action/CouchbaseActionFilterExecuter.cs index 9239ec8..65a8085 100644 --- a/src/Stove.Couchbase/Couchbase/Filters/Action/CouchbaseActionFilterExecuter.cs +++ b/src/Stove.Couchbase/Couchbase/Filters/Action/CouchbaseActionFilterExecuter.cs @@ -3,7 +3,7 @@ using Stove.Domain.Entities; using Stove.Domain.Uow.DynamicFilters.Action; -namespace Stove.Couchbase.Couchbase.Filters.Action +namespace Stove.Couchbase.Filters.Action { public class CouchbaseActionFilterExecuter : ICouchbaseActionFilterExecuter, ITransientDependency { diff --git a/src/Stove.Couchbase/Couchbase/Filters/Action/ICouchbaseActionFilterExecuter.cs b/src/Stove.Couchbase/Couchbase/Filters/Action/ICouchbaseActionFilterExecuter.cs index eb76f94..129efa5 100644 --- a/src/Stove.Couchbase/Couchbase/Filters/Action/ICouchbaseActionFilterExecuter.cs +++ b/src/Stove.Couchbase/Couchbase/Filters/Action/ICouchbaseActionFilterExecuter.cs @@ -1,6 +1,6 @@ using Stove.Domain.Entities; -namespace Stove.Couchbase.Couchbase.Filters.Action +namespace Stove.Couchbase.Filters.Action { public interface ICouchbaseActionFilterExecuter { diff --git a/src/Stove.Couchbase/Couchbase/Filters/Action/NullCouchbaseActionFilterExecuter.cs b/src/Stove.Couchbase/Couchbase/Filters/Action/NullCouchbaseActionFilterExecuter.cs index d5abd80..e07be66 100644 --- a/src/Stove.Couchbase/Couchbase/Filters/Action/NullCouchbaseActionFilterExecuter.cs +++ b/src/Stove.Couchbase/Couchbase/Filters/Action/NullCouchbaseActionFilterExecuter.cs @@ -1,6 +1,6 @@ using Stove.Domain.Entities; -namespace Stove.Couchbase.Couchbase.Filters.Action +namespace Stove.Couchbase.Filters.Action { public class NullCouchbaseActionFilterExecuter : ICouchbaseActionFilterExecuter { diff --git a/src/Stove.Couchbase/Couchbase/ISessionProvider.cs b/src/Stove.Couchbase/Couchbase/ISessionProvider.cs index 095caab..2207b80 100644 --- a/src/Stove.Couchbase/Couchbase/ISessionProvider.cs +++ b/src/Stove.Couchbase/Couchbase/ISessionProvider.cs @@ -1,6 +1,6 @@ using Couchbase.Linq; -namespace Stove.Couchbase.Couchbase +namespace Stove.Couchbase { public interface ISessionProvider { diff --git a/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs index 34fbfed..9681717 100644 --- a/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs +++ b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs @@ -3,12 +3,12 @@ using Couchbase; using Couchbase.Linq; -using Stove.Couchbase.Couchbase.Filters.Action; +using Stove.Couchbase.Filters.Action; using Stove.Domain.Entities; using Stove.Domain.Repositories; using Stove.Extensions; -namespace Stove.Couchbase.Couchbase.Repositories +namespace Stove.Couchbase.Repositories { public class CouchbaseRepositoryBase : StoveRepositoryBase where TEntity : class, IEntity { diff --git a/src/Stove.Couchbase/Couchbase/StoveCouchbaseBootstrapper.cs b/src/Stove.Couchbase/Couchbase/StoveCouchbaseBootstrapper.cs index a949e97..7ca3520 100644 --- a/src/Stove.Couchbase/Couchbase/StoveCouchbaseBootstrapper.cs +++ b/src/Stove.Couchbase/Couchbase/StoveCouchbaseBootstrapper.cs @@ -1,9 +1,9 @@ using Couchbase.Core; using Stove.Bootstrapping; -using Stove.Couchbase.Couchbase.Configuration; +using Stove.Couchbase.Configuration; -namespace Stove.Couchbase.Couchbase +namespace Stove.Couchbase { public class StoveCouchbaseBootstrapper : StoveBootstrapper { diff --git a/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs b/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs index c236a7e..03ca677 100644 --- a/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs +++ b/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs @@ -5,12 +5,12 @@ using Couchbase; using Couchbase.Core; -using Stove.Couchbase.Couchbase.Configuration; -using Stove.Couchbase.Couchbase.Repositories; +using Stove.Couchbase.Configuration; +using Stove.Couchbase.Repositories; using Stove.Domain.Repositories; using Stove.Reflection.Extensions; -namespace Stove.Couchbase.Couchbase +namespace Stove.Couchbase { public static class StoveCouchbaseRegistrationExtensions { diff --git a/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs b/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs index cbbd731..dc616f9 100644 --- a/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs +++ b/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs @@ -8,7 +8,7 @@ using Stove.Domain.Uow; -namespace Stove.Couchbase.Couchbase.Uow +namespace Stove.Couchbase.Uow { public class CouchbaseUnitOfWork : UnitOfWorkBase, ITransientDependency { diff --git a/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkExtensions.cs b/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkExtensions.cs index 5b0e589..397727d 100644 --- a/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkExtensions.cs +++ b/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkExtensions.cs @@ -4,7 +4,7 @@ using Stove.Domain.Uow; -namespace Stove.Couchbase.Couchbase.Uow +namespace Stove.Couchbase.Uow { public static class UnitOfWorkExtensions { diff --git a/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkSessionProvider.cs b/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkSessionProvider.cs index a0885b1..b2f8676 100644 --- a/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkSessionProvider.cs +++ b/src/Stove.Couchbase/Couchbase/Uow/UnitOfWorkSessionProvider.cs @@ -4,7 +4,7 @@ using Stove.Domain.Uow; -namespace Stove.Couchbase.Couchbase.Uow +namespace Stove.Couchbase.Uow { public class UnitOfWorkSessionProvider : ISessionProvider, ITransientDependency { diff --git a/src/Stove.Couchbase/Stove.Couchbase.csproj b/src/Stove.Couchbase/Stove.Couchbase.csproj index 10b2d7c..d25bee4 100644 --- a/src/Stove.Couchbase/Stove.Couchbase.csproj +++ b/src/Stove.Couchbase/Stove.Couchbase.csproj @@ -1,9 +1,27 @@ + + netstandard2.0 + Stove + Stove.Couchbase + Stove.Couchbase + .net;framework;boilerplate;NoSQL;Couchbase + Couchbase integration for Stove. + + bin\Release\netstandard2.0\Stove.Couchbase.xml + + + + + lib/netstandard2.0/ + true + + + diff --git a/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs b/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs index 5090ebf..6094f66 100644 --- a/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs +++ b/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs @@ -4,7 +4,6 @@ using Couchbase.Configuration.Client; -using Stove.Couchbase.Couchbase; using Stove.TestBase; namespace Stove.Couchbase.Tests diff --git a/test/Stove.Couchbase.Tests/CouchbaseTestBootstrapper.cs b/test/Stove.Couchbase.Tests/CouchbaseTestBootstrapper.cs index 4797e95..5b52d49 100644 --- a/test/Stove.Couchbase.Tests/CouchbaseTestBootstrapper.cs +++ b/test/Stove.Couchbase.Tests/CouchbaseTestBootstrapper.cs @@ -1,5 +1,4 @@ using Stove.Bootstrapping; -using Stove.Couchbase.Couchbase; namespace Stove.Couchbase.Tests { From 0c1480bef6d585a254e5bc0965a6b9aee016bb69 Mon Sep 17 00:00:00 2001 From: osoykan Date: Sun, 15 Oct 2017 22:45:47 +0300 Subject: [PATCH 08/11] refactor for entitydeletion filter --- .../Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs index 9681717..01cfe5f 100644 --- a/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs +++ b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs @@ -69,8 +69,6 @@ public override void Delete(TEntity entity) if (entity is ISoftDelete) { - entity.As().IsDeleted = true; - Session.Bucket.Upsert(new Document { Content = entity, From d9fc81555b4593029975873ffab2a57da1ebec99 Mon Sep 17 00:00:00 2001 From: Oguzhan Soykan Date: Mon, 16 Oct 2017 14:35:21 +0300 Subject: [PATCH 09/11] Entity events added --- .../CouchbaseRepositoryBaseOfTEntity.cs | 33 ++++++++++++++----- .../StoveCouchbaseRegistrationExtensions.cs | 7 +++- .../Couchbase/Uow/CouchbaseUnitOfWork.cs | 2 +- .../CouchbaseTestBase.cs | 2 +- .../General_Repository_Tests.cs | 32 ++++++++++++++++++ 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs index 01cfe5f..da8b752 100644 --- a/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs +++ b/src/Stove.Couchbase/Couchbase/Repositories/CouchbaseRepositoryBaseOfTEntity.cs @@ -6,25 +6,28 @@ using Stove.Couchbase.Filters.Action; using Stove.Domain.Entities; using Stove.Domain.Repositories; -using Stove.Extensions; +using Stove.Events.Bus.Entities; namespace Stove.Couchbase.Repositories { public class CouchbaseRepositoryBase : StoveRepositoryBase where TEntity : class, IEntity { - private readonly ICouchbaseActionFilterExecuter _actionFilterExecuter; private readonly ISessionProvider _sessionProvider; public CouchbaseRepositoryBase( - ISessionProvider sessionProvider, - ICouchbaseActionFilterExecuter actionFilterExecuter) + ISessionProvider sessionProvider) { _sessionProvider = sessionProvider; - _actionFilterExecuter = actionFilterExecuter; GuidGenerator = SequentialGuidGenerator.Instance; + EntityChangeEventHelper = NullEntityChangeEventHelper.Instance; + ActionFilterExecuter = NullCouchbaseActionFilterExecuter.Instance; } + public ICouchbaseActionFilterExecuter ActionFilterExecuter { get; set; } + + public IEntityChangeEventHelper EntityChangeEventHelper { get; set; } + public IGuidGenerator GuidGenerator { get; set; } public IBucketContext Session => _sessionProvider.Session; @@ -37,7 +40,10 @@ public override IQueryable GetAll() public override TEntity Insert(TEntity entity) { entity.Id = GuidGenerator.Create().ToString("N"); - _actionFilterExecuter.ExecuteCreationAuditFilter(entity); + ActionFilterExecuter.ExecuteCreationAuditFilter(entity); + + EntityChangeEventHelper.TriggerEntityCreatingEvent(entity); + IDocumentResult result = Session.Bucket.Insert(new Document { Content = entity, @@ -46,12 +52,17 @@ public override TEntity Insert(TEntity entity) result.EnsureSuccess(); + EntityChangeEventHelper.TriggerEntityCreatedEventOnUowCompleted(entity); + return result.Content; } public override TEntity Update(TEntity entity) { - _actionFilterExecuter.ExecuteModificationAuditFilter(entity); + ActionFilterExecuter.ExecuteModificationAuditFilter(entity); + + EntityChangeEventHelper.TriggerEntityUpdatingEvent(entity); + IDocumentResult result = Session.Bucket.Upsert(new Document { Content = entity, @@ -60,12 +71,16 @@ public override TEntity Update(TEntity entity) result.EnsureSuccess(); + EntityChangeEventHelper.TriggerEntityUpdatedEventOnUowCompleted(entity); + return result.Content; } public override void Delete(TEntity entity) { - _actionFilterExecuter.ExecuteDeletionAuditFilter(entity); + ActionFilterExecuter.ExecuteDeletionAuditFilter(entity); + + EntityChangeEventHelper.TriggerEntityDeletingEvent(entity); if (entity is ISoftDelete) { @@ -83,6 +98,8 @@ public override void Delete(TEntity entity) Id = $"{typeof(TEntity).Name}:{entity.Id}" }).EnsureSuccess(); } + + EntityChangeEventHelper.TriggerEntityDeletedEventOnUowCompleted(entity); } public override void Delete(string id) diff --git a/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs b/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs index 03ca677..b1bab45 100644 --- a/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs +++ b/src/Stove.Couchbase/Couchbase/StoveCouchbaseRegistrationExtensions.cs @@ -1,5 +1,6 @@ using System; +using Autofac; using Autofac.Extras.IocManager; using Couchbase; @@ -19,11 +20,15 @@ public static IIocBuilder UseStoveCouchbase(this IIocBuilder builder, Func { - r.RegisterGeneric(typeof(IRepository<,>), typeof(CouchbaseRepositoryBase<>)); + r.UseBuilder(cb => + { + cb.RegisterGeneric(typeof(CouchbaseRepositoryBase<>)).As(typeof(IRepository<,>)).PropertiesAutowired(); + }); r.Register(ctx => configurer); r.Register(ctx => { var cfg = ctx.Resolver.Resolve(); + return new Cluster(cfg.ClientConfiguration); }, Lifetime.Singleton); diff --git a/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs b/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs index dc616f9..1559624 100644 --- a/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs +++ b/src/Stove.Couchbase/Couchbase/Uow/CouchbaseUnitOfWork.cs @@ -47,8 +47,8 @@ public override void SaveChanges() protected override void CompleteUow() { - SaveChanges(); Session.EndChangeTracking(); + SaveChanges(); } protected override Task CompleteUowAsync(CancellationToken cancellationToken = default(CancellationToken)) diff --git a/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs b/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs index 6094f66..ab5faa4 100644 --- a/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs +++ b/test/Stove.Couchbase.Tests/CouchbaseTestBase.cs @@ -15,6 +15,7 @@ protected CouchbaseTestBase() : base(true) Building(builder => { builder + .UseStoveEventBus() .UseStoveCouchbase(configuration => { ClientConfiguration cfg = configuration.ClientConfiguration; @@ -27,7 +28,6 @@ protected CouchbaseTestBase() : base(true) { BucketName = "default", UseSsl = false, - Password = "", DefaultOperationLifespan = 2000, PoolConfiguration = new PoolConfiguration { diff --git a/test/Stove.Couchbase.Tests/General_Repository_Tests.cs b/test/Stove.Couchbase.Tests/General_Repository_Tests.cs index e18d9c6..7ddbb3d 100644 --- a/test/Stove.Couchbase.Tests/General_Repository_Tests.cs +++ b/test/Stove.Couchbase.Tests/General_Repository_Tests.cs @@ -5,6 +5,8 @@ using Stove.Couchbase.Tests.Entities; using Stove.Domain.Repositories; using Stove.Domain.Uow; +using Stove.Events.Bus; +using Stove.Events.Bus.Entities; using Xunit; @@ -19,16 +21,45 @@ public General_Repository_Tests() [Fact] public void Insert_should_work() + { + + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + string productName = Guid.NewGuid().ToString("N"); + var product = new Product(productName); + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Product inserted = The>().Insert(product); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + Product item = The>().FirstOrDefault(x => x.Id == inserted.Id); + item.ShouldNotBeNull(); + } + + [Fact] + public void Insert_and_event_fire_should_work() { //----------------------------------------------------------------------------------------------------------- // Arrange //----------------------------------------------------------------------------------------------------------- string productName = Guid.NewGuid().ToString("N"); var product = new Product(productName); + var eventInvocationCount = 0; //----------------------------------------------------------------------------------------------------------- // Act //----------------------------------------------------------------------------------------------------------- + + The().Register>(data => + { + eventInvocationCount++; + }); + Product inserted = The>().Insert(product); //----------------------------------------------------------------------------------------------------------- @@ -36,6 +67,7 @@ public void Insert_should_work() //----------------------------------------------------------------------------------------------------------- Product item = The>().FirstOrDefault(x => x.Id == inserted.Id); item.ShouldNotBeNull(); + eventInvocationCount.ShouldBe(1); } [Fact] From 1b7ca4b92054da3dd4ed257197c86852c6b7c38a Mon Sep 17 00:00:00 2001 From: osoykan Date: Mon, 16 Oct 2017 22:11:43 +0300 Subject: [PATCH 10/11] Packages updated --- common.props | 4 ++-- src/Stove.Mapster/Stove.Mapster.csproj | 2 +- src/Stove.RabbitMQ/Stove.RabbitMQ.csproj | 6 +++--- src/Stove/Stove.csproj | 4 ++-- test/Stove.Demo.WebApi.Core/Stove.Demo.WebApi.Core.csproj | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/common.props b/common.props index ba7d2bb..99c43dc 100644 --- a/common.props +++ b/common.props @@ -1,6 +1,6 @@ - 2.0.7 + 2.0.8 $(NoWarn);CS1591 https://raw.githubusercontent.com/osoykan/Stove/master/stove.png https://github.com/osoykan/Stove @@ -22,6 +22,6 @@ - + diff --git a/src/Stove.Mapster/Stove.Mapster.csproj b/src/Stove.Mapster/Stove.Mapster.csproj index 224f30e..329f846 100644 --- a/src/Stove.Mapster/Stove.Mapster.csproj +++ b/src/Stove.Mapster/Stove.Mapster.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/Stove.RabbitMQ/Stove.RabbitMQ.csproj b/src/Stove.RabbitMQ/Stove.RabbitMQ.csproj index 8729d89..083b707 100644 --- a/src/Stove.RabbitMQ/Stove.RabbitMQ.csproj +++ b/src/Stove.RabbitMQ/Stove.RabbitMQ.csproj @@ -27,9 +27,9 @@ - - - + + + diff --git a/src/Stove/Stove.csproj b/src/Stove/Stove.csproj index 9f1067f..9d4f17e 100644 --- a/src/Stove/Stove.csproj +++ b/src/Stove/Stove.csproj @@ -22,8 +22,8 @@ - - + + diff --git a/test/Stove.Demo.WebApi.Core/Stove.Demo.WebApi.Core.csproj b/test/Stove.Demo.WebApi.Core/Stove.Demo.WebApi.Core.csproj index 5bae8a0..b4fa492 100644 --- a/test/Stove.Demo.WebApi.Core/Stove.Demo.WebApi.Core.csproj +++ b/test/Stove.Demo.WebApi.Core/Stove.Demo.WebApi.Core.csproj @@ -10,9 +10,9 @@ - + - + From efff0dffa4e94bbda04bb498dae258bdfc3d13b9 Mon Sep 17 00:00:00 2001 From: osoykan Date: Mon, 16 Oct 2017 22:23:53 +0300 Subject: [PATCH 11/11] rabbitmq refactors --- .../StoveRabbitMQRegistrationExtensions.cs | 109 +++++++++++------- 1 file changed, 65 insertions(+), 44 deletions(-) diff --git a/src/Stove.RabbitMQ/RabbitMQ/StoveRabbitMQRegistrationExtensions.cs b/src/Stove.RabbitMQ/RabbitMQ/StoveRabbitMQRegistrationExtensions.cs index 4b31974..0cd2a9a 100644 --- a/src/Stove.RabbitMQ/RabbitMQ/StoveRabbitMQRegistrationExtensions.cs +++ b/src/Stove.RabbitMQ/RabbitMQ/StoveRabbitMQRegistrationExtensions.cs @@ -18,7 +18,7 @@ namespace Stove.RabbitMQ public static class StoveRabbitMQRegistrationExtensions { /// - /// Uses the stove rabbit mq. + /// Uses the Stove RabbitMq integration with producer and consumer.Loads consumers from Ioc. /// /// The builder. /// The rabbit mq configurer. @@ -28,7 +28,7 @@ public static class StoveRabbitMQRegistrationExtensions public static IIocBuilder UseStoveRabbitMQ( [NotNull] this IIocBuilder builder, [NotNull] Func rabbitMQConfigurer, - Action busConfigurer = null) + Action busConfigurer) { Check.NotNull(rabbitMQConfigurer, nameof(rabbitMQConfigurer)); @@ -49,26 +49,7 @@ public static IIocBuilder UseStoveRabbitMQ( IBusControl busControl = Bus.Factory.CreateUsingRabbitMq(cfg => { - IRabbitMqHost host = cfg.Host(new Uri(configuration.HostAddress), h => - { - h.Username(configuration.Username); - h.Password(configuration.Password); - }); - - if (configuration.UseRetryMechanism) - { - cfg.UseRetry(rtryConf => { rtryConf.Immediate(configuration.MaxRetryCount); }); - } - - if (configuration.PrefetchCount.HasValue) - { - cfg.PrefetchCount = (ushort)configuration.PrefetchCount; - } - - if (configuration.ConcurrencyLimit.HasValue) - { - cfg.UseConcurrencyLimit(configuration.ConcurrencyLimit.Value); - } + IRabbitMqHost host = EnsureStoveDefaults(cfg, configuration); cfg.ReceiveEndpoint(host, configuration.QueueName, ec => { ec.LoadFrom(ctx); }); @@ -85,12 +66,54 @@ public static IIocBuilder UseStoveRabbitMQ( } /// - /// Uses the stove rabbit mq. Consumer loading doesn't come from IoC, you should register explicitly with + /// Uses the Stove RabbitMQ integration as just publisher. + /// + /// The builder. + /// The rabbit mq configurer. + /// + [NotNull] + public static IIocBuilder UseStoveRabbitMQ( + [NotNull] this IIocBuilder builder, + [NotNull] Func rabbitMQConfigurer) + { + Check.NotNull(rabbitMQConfigurer, nameof(rabbitMQConfigurer)); + + builder + .RegisterServices(r => + { + r.RegisterAssemblyByConvention(typeof(StoveRabbitMQRegistrationExtensions).GetAssembly()); + r.Register(Lifetime.Singleton); + r.Register(); + r.Register(ctx => rabbitMQConfigurer); + }); + + builder.RegisterServices(r => r.UseBuilder(cb => + { + cb.Register(ctx => + { + var configuration = ctx.Resolve(); + + IBusControl busControl = Bus.Factory.CreateUsingRabbitMq(cfg => + { + EnsureStoveDefaults(cfg, configuration); + }); + + return busControl; + }).SingleInstance() + .As() + .As(); + })); + + return builder; + } + + /// + /// Uses the Stove RabbitMQ integration. Consumer loadings doesn't come from IoC, you should register explicitly with + /// /// /// The builder. /// The rabbit mq configurer. /// - /// /// [NotNull] public static IIocBuilder UseStoveRabbitMQ( @@ -119,26 +142,7 @@ Action consum IBusControl busControl = Bus.Factory.CreateUsingRabbitMq(cfg => { - IRabbitMqHost host = cfg.Host(new Uri(configuration.HostAddress), h => - { - h.Username(configuration.Username); - h.Password(configuration.Password); - }); - - if (configuration.UseRetryMechanism) - { - cfg.UseRetry(rtryConf => { rtryConf.Immediate(configuration.MaxRetryCount); }); - } - - if (configuration.PrefetchCount.HasValue) - { - cfg.PrefetchCount = (ushort)configuration.PrefetchCount; - } - - if (configuration.ConcurrencyLimit.HasValue) - { - cfg.UseConcurrencyLimit(configuration.ConcurrencyLimit.Value); - } + IRabbitMqHost host = EnsureStoveDefaults(cfg, configuration); consumerConfigurer(host, cfg, ctx); }); @@ -151,5 +155,22 @@ Action consum return builder; } + + private static IRabbitMqHost EnsureStoveDefaults(IRabbitMqBusFactoryConfigurator cfg, IStoveRabbitMQConfiguration configuration) + { + IRabbitMqHost host = cfg.Host(new Uri(configuration.HostAddress), h => + { + h.Username(configuration.Username); + h.Password(configuration.Password); + }); + + if (configuration.UseRetryMechanism) cfg.UseRetry(rtryConf => { rtryConf.Immediate(configuration.MaxRetryCount); }); + + if (configuration.PrefetchCount.HasValue) cfg.PrefetchCount = (ushort)configuration.PrefetchCount; + + if (configuration.ConcurrencyLimit.HasValue) cfg.UseConcurrencyLimit(configuration.ConcurrencyLimit.Value); + + return host; + } } }