From cd805c95d0d9180b31eabcad2b00428f95c2cd15 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Sun, 19 Jan 2020 21:38:02 -0600 Subject: [PATCH] Merged in the EF Core 3.1 update #1629 --- .../AuditContextFactory.cs | 28 ------ .../{ => AuditStore}/AuditStore_Specs.cs | 66 +++++++------ .../DataAccess/AuditContextFactory.cs | 29 ++++++ .../ContextFactory.cs | 24 ----- .../ContextFactoryWithResilienceStrategy.cs | 25 ----- .../LocalDbConnectionStringProvider.cs | 2 +- ...ntityFrameworkCoreIntegration.Tests.csproj | 3 +- .../Audit/20170710143716_audit_init.cs | 4 + .../20170710150441_Init.Designer.cs | 5 +- .../{ => Saga}/20170710150441_Init.cs | 2 +- .../{ => Saga}/SagaDbContextModelSnapshot.cs | 16 ++-- .../20171224151458_Init.Designer.cs | 1 + .../SagaWithDependency/20171224151458_Init.cs | 18 ++-- .../SagaWithDependencyContextModelSnapshot.cs | 30 +++--- .../20170710150441_Init.Designer.cs | 43 +++++++++ .../SlowConcurrentSaga/20170710150441_Init.cs | 32 +++++++ .../SagaDbContextModelSnapshot.cs | 37 ++++++++ .../SagaInnerDependency.cs | 7 -- .../DataAccess}/SagaDependency.cs | 4 +- .../DataAccess/SagaInnerDependency.cs | 7 ++ .../DataAccess}/SagaWithDependencyContext.cs | 4 +- .../SagaWithDependencyContextFactory.cs | 27 ++++++ .../DataAccess}/SagaWithDependencyMap.cs | 2 +- .../Messages}/UpdateSagaDependency.cs | 5 +- .../SagaWithDependency.cs | 5 +- .../Using_custom_include_in_repository.cs | 51 ++++++---- .../SagaWithDependencyContextFactory.cs | 27 ------ .../Shared/EntityFrameworkTestFixture.cs | 22 +++++ .../Shared/ITestDbParameters.cs | 13 +++ .../Shared/PostgresTestDbParameters.cs | 26 ++++++ .../SqlServerResiliencyTestDbParameters.cs | 32 +++++++ .../Shared/SqlServerTestDbParameters.cs | 31 +++++++ .../DataAccess/SimpleSagaContextFactory.cs | 26 ++++++ .../DataAccess}/SimpleSagaDbContext.cs | 3 +- .../DataAccess}/SimpleSagaMap.cs | 7 +- .../{ => SimpleSaga}/SagaLocator_Specs.cs | 43 +++++---- ...mpleSagaDbContextWithResilienceStrategy.cs | 13 --- .../DataAccess/SlowConcurrentSaga.cs | 17 ++++ .../SlowConcurrentSagaContextFactory.cs | 26 ++++++ .../DataAccess/SlowConcurrentSagaDbContext.cs | 21 +++++ .../DataAccess/SlowConcurrentSagaMap.cs | 17 ++++ .../SlowConcurrentSaga/Events/Begin.cs | 10 ++ .../Events/IncrementCounterSlowly.cs | 10 ++ .../SlowConcurrentSagaStateMachine.cs | 40 ++++++++ .../SlowConcurrentSaga_Specs.cs | 91 ++++++++++++++++++ .../TransactionOutbox_Specs.cs | 17 ++-- .../Using_ef_connection_resiliency.cs | 92 ------------------- .../docker-compose.yml | 17 ++++ ...nsit.EntityFrameworkCoreIntegration.csproj | 12 +-- .../Saga/EntityFrameworkSagaConsumeContext.cs | 3 +- .../Saga/EntityFrameworkSagaRepository.cs | 6 +- ...tyFrameworkSagaRepositoryContextFactory.cs | 28 ++---- .../Saga/PessimisticLoadQueryExecutor.cs | 16 ++-- .../SqlServerLockStatementProvider.cs | 70 +++++++++----- 54 files changed, 808 insertions(+), 405 deletions(-) delete mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditContextFactory.cs rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/{ => AuditStore}/AuditStore_Specs.cs (61%) create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditStore/DataAccess/AuditContextFactory.cs delete mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/ContextFactory.cs delete mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/ContextFactoryWithResilienceStrategy.cs rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/{ => Saga}/20170710150441_Init.Designer.cs (96%) rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/{ => Saga}/20170710150441_Init.cs (99%) rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/{ => Saga}/SagaDbContextModelSnapshot.cs (72%) create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/20170710150441_Init.Designer.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/20170710150441_Init.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/SagaDbContextModelSnapshot.cs delete mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaInnerDependency.cs rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/{ => SagaWithDependency/DataAccess}/SagaDependency.cs (55%) create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaInnerDependency.cs rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/{ => SagaWithDependency/DataAccess}/SagaWithDependencyContext.cs (70%) create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaWithDependencyContextFactory.cs rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/{ => SagaWithDependency/DataAccess}/SagaWithDependencyMap.cs (94%) rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/{ => SagaWithDependency/Messages}/UpdateSagaDependency.cs (82%) rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/{ => SagaWithDependency}/SagaWithDependency.cs (90%) rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/{ => SagaWithDependency}/Using_custom_include_in_repository.cs (60%) delete mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependencyContextFactory.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/EntityFrameworkTestFixture.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/ITestDbParameters.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/PostgresTestDbParameters.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/SqlServerResiliencyTestDbParameters.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/SqlServerTestDbParameters.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/DataAccess/SimpleSagaContextFactory.cs rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/{ => SimpleSaga/DataAccess}/SimpleSagaDbContext.cs (81%) rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/{ => SimpleSaga/DataAccess}/SimpleSagaMap.cs (60%) rename src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/{ => SimpleSaga}/SagaLocator_Specs.cs (71%) delete mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSagaDbContextWithResilienceStrategy.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSaga.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaContextFactory.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaDbContext.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaMap.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/Events/Begin.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/Events/IncrementCounterSlowly.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/SlowConcurrentSagaStateMachine.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/SlowConcurrentSaga_Specs.cs delete mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Using_ef_connection_resiliency.cs create mode 100644 src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/docker-compose.yml diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditContextFactory.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditContextFactory.cs deleted file mode 100644 index 9b174f7f7b..0000000000 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditContextFactory.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests -{ - using System.Reflection; - - using MassTransit.EntityFrameworkCoreIntegration.Audit; - - using Microsoft.EntityFrameworkCore; - using Microsoft.EntityFrameworkCore.Design; - - public class AuditContextFactory : IDesignTimeDbContextFactory - { - public AuditDbContext CreateDbContext(string[] args) - { - var optionsBuilder = new DbContextOptionsBuilder(). - UseSqlServer(LocalDbConnectionStringProvider.GetLocalDbConnectionString(), - m => - { - var executingAssembly = typeof(ContextFactory).GetTypeInfo().Assembly; - - m.MigrationsAssembly(executingAssembly.GetName().Name); - m.MigrationsHistoryTable("__AuditEFMigrationHistoryAudit"); - }); - - return new AuditDbContext(optionsBuilder.Options, "EfCoreAudit"); - - } - } -} \ No newline at end of file diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditStore_Specs.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditStore/AuditStore_Specs.cs similarity index 61% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditStore_Specs.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditStore/AuditStore_Specs.cs index 70de49afcb..e47f79d163 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditStore_Specs.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditStore/AuditStore_Specs.cs @@ -10,26 +10,25 @@ // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -namespace MassTransit.EntityFrameworkCoreIntegration.Tests +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.AuditStore { using System.Linq; - using System.Reflection; using System.Threading.Tasks; - + using Audit; using GreenPipes.Util; - - using MassTransit.EntityFrameworkCoreIntegration.Audit; - using MassTransit.Testing; - using Microsoft.EntityFrameworkCore; - using NUnit.Framework; - + using Shared; using Shouldly; + using Testing; - [TestFixture] - public class Saving_audit_records_to_the_audit_store + [TestFixture(typeof(SqlServerTestDbParameters))] + [TestFixture(typeof(SqlServerResiliencyTestDbParameters))] + [TestFixture(typeof(PostgresTestDbParameters))] + public class Saving_audit_records_to_the_audit_store : + EntityFrameworkTestFixture + where T : ITestDbParameters, new() { [Test] public async Task Should_have_consume_audit_records() @@ -53,30 +52,21 @@ public async Task CleanAudit() } InMemoryTestHarness _harness; + ConsumerTestHarness _consumer; EntityFrameworkAuditStore _store; [OneTimeSetUp] - public async Task Send_message_to_test_consumer() + public async Task SetUp() { - // add migrations by calling - // dotnet ef migrations add --context auditdbcontext --output-dir Migrations\\Audit audit_init - DbContextOptionsBuilder optionsBuilder = new DbContextOptionsBuilder(). - UseSqlServer(LocalDbConnectionStringProvider.GetLocalDbConnectionString(), - m => - { - var executingAssembly = typeof(ContextFactory).GetTypeInfo().Assembly; - - m.MigrationsAssembly(executingAssembly.GetName().Name); - m.MigrationsHistoryTable("__AuditEFMigrationHistoryAudit"); - }); - - _store = new EntityFrameworkAuditStore(optionsBuilder.Options, "EfCoreAudit"); - using (var dbContext = _store.AuditContext) + var contextFactory = new AuditContextFactory(); + + await using (var context = contextFactory.CreateDbContext(DbContextOptionsBuilder)) { - await dbContext.Database.MigrateAsync(); - await dbContext.Database.ExecuteSqlCommandAsync("TRUNCATE TABLE EfCoreAudit"); + await context.Database.MigrateAsync(); } + _store = new EntityFrameworkAuditStore(DbContextOptionsBuilder.Options, "EfCoreAudit"); + _harness = new InMemoryTestHarness(); _harness.OnConnectObservers += bus => { @@ -91,18 +81,24 @@ public async Task Send_message_to_test_consumer() } [OneTimeTearDown] - public Task Teardown() + public async Task Teardown() { - return _harness.Stop(); + await _harness.Stop(); + + var contextFactory = new AuditContextFactory(); + + await using (var context = contextFactory.CreateDbContext(DbContextOptionsBuilder)) + { + context.Database.EnsureDeleted(); + } } async Task GetAuditRecords(string contextType) { - using var dbContext = _store.AuditContext; - - return await dbContext.Set() - .Where(x => x.ContextType == contextType) - .CountAsync(); + using (var dbContext = this._store.AuditContext) + return await dbContext.Set() + .Where(x => x.ContextType == contextType) + .CountAsync(); } diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditStore/DataAccess/AuditContextFactory.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditStore/DataAccess/AuditContextFactory.cs new file mode 100644 index 0000000000..f9b490555c --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/AuditStore/DataAccess/AuditContextFactory.cs @@ -0,0 +1,29 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests +{ + using System.Reflection; + + using MassTransit.EntityFrameworkCoreIntegration.Audit; + using MassTransit.Tests.AutomatonymousIntegration; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Design; + using Shared; + + + public class AuditContextFactory : IDesignTimeDbContextFactory + { + public AuditDbContext CreateDbContext(string[] args) + { + // used only for database update and migrations. Since IDesignTimeDbContextFactory is icky, + // we only support command line tools for SQL Server, so use SQL Server if you need to do + // migrations. + + var optionsBuilder = new SqlServerTestDbParameters().GetDbContextOptions(typeof(AuditDbContext)); + return CreateDbContext(optionsBuilder); + } + + public AuditDbContext CreateDbContext(DbContextOptionsBuilder optionsBuilder) + { + return new AuditDbContext(optionsBuilder.Options, "EfCoreAudit"); + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/ContextFactory.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/ContextFactory.cs deleted file mode 100644 index 7b20490d24..0000000000 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/ContextFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests -{ - using System.Reflection; - using Microsoft.EntityFrameworkCore; - using Microsoft.EntityFrameworkCore.Design; - - - public class ContextFactory : IDesignTimeDbContextFactory - { - public SimpleSagaDbContext CreateDbContext(string[] args) - { - var dbContextOptionsBuilder = new DbContextOptionsBuilder(); - - dbContextOptionsBuilder.UseSqlServer(LocalDbConnectionStringProvider.GetLocalDbConnectionString(), - m => - { - var executingAssembly = typeof(ContextFactory).GetTypeInfo().Assembly; - m.MigrationsAssembly(executingAssembly.GetName().Name); - }); - - return new SimpleSagaDbContext(dbContextOptionsBuilder.Options); - } - } -} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/ContextFactoryWithResilienceStrategy.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/ContextFactoryWithResilienceStrategy.cs deleted file mode 100644 index 1e56eba81f..0000000000 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/ContextFactoryWithResilienceStrategy.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests -{ - using System.Reflection; - using Microsoft.EntityFrameworkCore; - using Microsoft.EntityFrameworkCore.Design; - - - public class ContextFactoryWithResilienceStrategy : IDesignTimeDbContextFactory - { - public SimpleSagaDbContextWithResilienceStrategy CreateDbContext(string[] args) - { - var dbContextOptionsBuilder = new DbContextOptionsBuilder(); - - dbContextOptionsBuilder.UseSqlServer(LocalDbConnectionStringProvider.GetLocalDbConnectionString(), - m => - { - var executingAssembly = typeof(ContextFactory).GetTypeInfo().Assembly; - m.MigrationsAssembly(executingAssembly.GetName().Name); - m.EnableRetryOnFailure(); - }); - - return new SimpleSagaDbContextWithResilienceStrategy(dbContextOptionsBuilder.Options); - } - } -} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/LocalDbConnectionStringProvider.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/LocalDbConnectionStringProvider.cs index e9153924e4..a0c8281e0f 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/LocalDbConnectionStringProvider.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/LocalDbConnectionStringProvider.cs @@ -1,7 +1,7 @@ namespace MassTransit.EntityFrameworkCoreIntegration.Tests { using System; - using System.Data.SqlClient; + using Microsoft.Data.SqlClient; public static class LocalDbConnectionStringProvider diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/MassTransit.EntityFrameworkCoreIntegration.Tests.csproj b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/MassTransit.EntityFrameworkCoreIntegration.Tests.csproj index 631e7c7260..9c577432dd 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/MassTransit.EntityFrameworkCoreIntegration.Tests.csproj +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/MassTransit.EntityFrameworkCoreIntegration.Tests.csproj @@ -13,8 +13,9 @@ - + + diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Audit/20170710143716_audit_init.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Audit/20170710143716_audit_init.cs index 6ae1d93971..ed2d19829d 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Audit/20170710143716_audit_init.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Audit/20170710143716_audit_init.cs @@ -5,6 +5,9 @@ namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations.Audit { + using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + + public partial class audit_init : Migration { protected override void Up(MigrationBuilder migrationBuilder) @@ -14,6 +17,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { AuditRecordId = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), ContextType = table.Column(nullable: true), ConversationId = table.Column(nullable: true), diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/20170710150441_Init.Designer.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Saga/20170710150441_Init.Designer.cs similarity index 96% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/20170710150441_Init.Designer.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Saga/20170710150441_Init.Designer.cs index 168b944c75..32ff066b6e 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/20170710150441_Init.Designer.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Saga/20170710150441_Init.Designer.cs @@ -6,8 +6,11 @@ using MassTransit.Tests.Saga; -namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations.Saga { + using SimpleSaga.DataAccess; + + [DbContext(typeof(SimpleSagaDbContext))] [Migration("20170710150441_Init")] partial class Init diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/20170710150441_Init.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Saga/20170710150441_Init.cs similarity index 99% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/20170710150441_Init.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Saga/20170710150441_Init.cs index 9a81be97d5..a7db7091ba 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/20170710150441_Init.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Saga/20170710150441_Init.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Migrations; -namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations.Saga { public partial class Init : Migration { diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaDbContextModelSnapshot.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Saga/SagaDbContextModelSnapshot.cs similarity index 72% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaDbContextModelSnapshot.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Saga/SagaDbContextModelSnapshot.cs index 7b00296a47..aa81efa0de 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaDbContextModelSnapshot.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/Saga/SagaDbContextModelSnapshot.cs @@ -1,14 +1,12 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using MassTransit.EntityFrameworkCoreIntegration; +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations.Saga +{ + using System; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Infrastructure; + using Microsoft.EntityFrameworkCore.Metadata; + using SimpleSaga.DataAccess; -using MassTransit.Tests.Saga; -namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations -{ [DbContext(typeof(SimpleSagaDbContext))] partial class SagaDbContextModelSnapshot : ModelSnapshot { diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/20171224151458_Init.Designer.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/20171224151458_Init.Designer.cs index 155d0d6271..86b99c1a19 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/20171224151458_Init.Designer.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/20171224151458_Init.Designer.cs @@ -1,5 +1,6 @@ // using MassTransit.EntityFrameworkCoreIntegration.Tests; +using MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/20171224151458_Init.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/20171224151458_Init.cs index e31bc48004..4073e46f4b 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/20171224151458_Init.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/20171224151458_Init.cs @@ -12,8 +12,8 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "SagaInnerDependency", columns: table => new { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: true) + Id = table.Column(nullable: false), + Name = table.Column(nullable: true) }, constraints: table => { @@ -24,8 +24,8 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "SagaDependency", columns: table => new { - Id = table.Column(type: "uniqueidentifier", nullable: false), - SagaInnerDependencyId = table.Column(type: "uniqueidentifier", nullable: false) + Id = table.Column(nullable: false), + SagaInnerDependencyId = table.Column(nullable: false) }, constraints: table => { @@ -42,11 +42,11 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "EfCoreSagasWithDepencies", columns: table => new { - CorrelationId = table.Column(type: "uniqueidentifier", nullable: false), - Completed = table.Column(type: "bit", nullable: false), - DependencyId = table.Column(type: "uniqueidentifier", nullable: false), - Initiated = table.Column(type: "bit", nullable: false), - Name = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + CorrelationId = table.Column(nullable: false), + Completed = table.Column(nullable: false), + DependencyId = table.Column(nullable: false), + Initiated = table.Column(nullable: false), + Name = table.Column(maxLength: 40, nullable: true) }, constraints: table => { diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/SagaWithDependencyContextModelSnapshot.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/SagaWithDependencyContextModelSnapshot.cs index 4877a64429..1a7e305fc9 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/SagaWithDependencyContextModelSnapshot.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SagaWithDependency/SagaWithDependencyContextModelSnapshot.cs @@ -1,12 +1,9 @@ // -using MassTransit.EntityFrameworkCoreIntegration.Tests; +using System; +using MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Storage.Internal; -using System; namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations.SagaWithDependency { @@ -17,10 +14,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") + .HasAnnotation("ProductVersion", "3.1.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaDependency", b => + modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess.SagaDependency", b => { b.Property("Id") .ValueGeneratedOnAdd(); @@ -34,7 +32,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("SagaDependency"); }); - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaInnerDependency", b => + modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess.SagaInnerDependency", b => { b.Property("Id") .ValueGeneratedOnAdd(); @@ -46,7 +44,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("SagaInnerDependency"); }); - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency", b => + modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.SagaWithDependency", b => { b.Property("CorrelationId"); @@ -66,20 +64,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("EfCoreSagasWithDepencies"); }); - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaDependency", b => + modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess.SagaDependency", b => { - b.HasOne("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaInnerDependency", "SagaInnerDependency") + b.HasOne("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess.SagaInnerDependency", "SagaInnerDependency") .WithMany() .HasForeignKey("SagaInnerDependencyId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency", b => + modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.SagaWithDependency", b => { - b.HasOne("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaDependency", "Dependency") + b.HasOne("MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess.SagaDependency", "Dependency") .WithMany() .HasForeignKey("DependencyId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); #pragma warning restore 612, 618 } diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/20170710150441_Init.Designer.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/20170710150441_Init.Designer.cs new file mode 100644 index 0000000000..83b6588157 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/20170710150441_Init.Designer.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +using MassTransit.Tests.Saga; + +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations.SlowConcurrentSaga +{ + using SimpleSaga.DataAccess; + using Tests.SlowConcurrentSaga.DataAccess; + + + [DbContext(typeof(SlowConcurrentSagaDbContext))] + [Migration("20170710150441_Init")] + partial class Init + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SlowConcurrentSaga.DataAccess.SlowConcurrentSaga", b => + { + b.Property("CorrelationId"); + + b.Property("CurrentState") + .HasMaxLength(40); + + b.Property("Name") + .HasMaxLength(40); + + b.Property("Counter"); + + b.HasKey("CorrelationId"); + + b.ToTable("EfCoreSlowConcurrentSagas"); + }); + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/20170710150441_Init.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/20170710150441_Init.cs new file mode 100644 index 0000000000..1eedfa941b --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/20170710150441_Init.cs @@ -0,0 +1,32 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations.SlowConcurrentSaga +{ + public partial class Init : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "EfCoreSlowConcurrentSagas", + columns: table => new + { + CorrelationId = table.Column(nullable: false), + CurrentState = table.Column(maxLength: 40, nullable: false), + Name = table.Column(maxLength: 40, nullable: true), + Counter = table.Column(nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_EfCoreSlowConcurrentSagas", x => x.CorrelationId); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "EfCoreSlowConcurrentSagas"); + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/SagaDbContextModelSnapshot.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/SagaDbContextModelSnapshot.cs new file mode 100644 index 0000000000..5680520df9 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Migrations/SlowConcurrentSaga/SagaDbContextModelSnapshot.cs @@ -0,0 +1,37 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Migrations.SlowConcurrentSaga +{ + using System; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Infrastructure; + using Microsoft.EntityFrameworkCore.Metadata; + using Tests.SlowConcurrentSaga.DataAccess; + + + [DbContext(typeof(SlowConcurrentSagaDbContext))] + partial class SlowConcurrentSagaDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.Tests.SlowConcurrentSaga.SlowConcurrentSaga", b => + { + b.Property("CorrelationId"); + + b.Property("CurrentState") + .HasMaxLength(40); + + b.Property("Name") + .HasMaxLength(40); + + b.Property("Counter"); + + b.HasKey("CorrelationId"); + + b.ToTable("EfCoreSlowConcurrentSagas"); + }); + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaInnerDependency.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaInnerDependency.cs deleted file mode 100644 index 0340f15015..0000000000 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaInnerDependency.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests -{ - public class SagaInnerDependency - { - public string Name { get; set; } - } -} \ No newline at end of file diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaDependency.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaDependency.cs similarity index 55% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaDependency.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaDependency.cs index 3670dc9743..720caeedfd 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaDependency.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaDependency.cs @@ -1,7 +1,7 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess { public class SagaDependency { public SagaInnerDependency SagaInnerDependency { get; set; } } -} \ No newline at end of file +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaInnerDependency.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaInnerDependency.cs new file mode 100644 index 0000000000..530e7ea3b7 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaInnerDependency.cs @@ -0,0 +1,7 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess +{ + public class SagaInnerDependency + { + public string Name { get; set; } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependencyContext.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaWithDependencyContext.cs similarity index 70% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependencyContext.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaWithDependencyContext.cs index 0db1dbfc05..7b37aee2de 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependencyContext.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaWithDependencyContext.cs @@ -1,4 +1,4 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess { using System.Collections.Generic; using Mappings; @@ -7,7 +7,7 @@ namespace MassTransit.EntityFrameworkCoreIntegration.Tests public class SagaWithDependencyContext : SagaDbContext { - public SagaWithDependencyContext(DbContextOptions options) + public SagaWithDependencyContext(DbContextOptions options) : base(options) { } diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaWithDependencyContextFactory.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaWithDependencyContextFactory.cs new file mode 100644 index 0000000000..81e0df8bca --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaWithDependencyContextFactory.cs @@ -0,0 +1,27 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess +{ + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Design; + using Shared; + + + public class SagaWithDependencyContextFactory : + IDesignTimeDbContextFactory + { + public SagaWithDependencyContext CreateDbContext(string[] args) + { + // used only for database update and migrations. Since IDesignTimeDbContextFactory is icky, + // we only support command line tools for SQL Server, so use SQL Server if you need to do + // migrations. + + var optionsBuilder = new SqlServerTestDbParameters().GetDbContextOptions(typeof(SagaWithDependencyContext)); + + return new SagaWithDependencyContext(optionsBuilder.Options); + } + + public SagaWithDependencyContext CreateDbContext(DbContextOptionsBuilder optionsBuilder) + { + return new SagaWithDependencyContext(optionsBuilder.Options); + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependencyMap.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaWithDependencyMap.cs similarity index 94% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependencyMap.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaWithDependencyMap.cs index 301ed6ded1..5596a7ee06 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependencyMap.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/DataAccess/SagaWithDependencyMap.cs @@ -1,4 +1,4 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.DataAccess { using System; using Mappings; diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/UpdateSagaDependency.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/Messages/UpdateSagaDependency.cs similarity index 82% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/UpdateSagaDependency.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/Messages/UpdateSagaDependency.cs index d977717f49..18b436a4b6 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/UpdateSagaDependency.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/Messages/UpdateSagaDependency.cs @@ -1,8 +1,9 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency.Messages { using System; using MassTransit.Tests.Saga.Messages; + [Serializable] public class UpdateSagaDependency : SimpleSagaMessageBase @@ -19,4 +20,4 @@ public UpdateSagaDependency(Guid correlationId, string propertyValue) public string Name { get; set; } } -} \ No newline at end of file +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/SagaWithDependency.cs similarity index 90% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/SagaWithDependency.cs index 4787bd8f5e..be2088334d 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/SagaWithDependency.cs @@ -1,9 +1,12 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency { using System; using System.Threading.Tasks; + using DataAccess; using MassTransit.Saga; using MassTransit.Tests.Saga.Messages; + using Messages; + public class SagaWithDependency : InitiatedBy, diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Using_custom_include_in_repository.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/Using_custom_include_in_repository.cs similarity index 60% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Using_custom_include_in_repository.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/Using_custom_include_in_repository.cs index 9924976571..3159e868c4 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Using_custom_include_in_repository.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependency/Using_custom_include_in_repository.cs @@ -1,20 +1,25 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SagaWithDependency { using System; using System.Threading.Tasks; + using DataAccess; using MassTransit.Saga; using MassTransit.Tests.Saga.Messages; + using Messages; using Microsoft.EntityFrameworkCore; using NUnit.Framework; using Saga; + using Shared; using Shouldly; - using TestFramework; using Testing; - [TestFixture, Category("Integration")] - public class Using_custom_include_in_repository : - InMemoryTestFixture + [TestFixture(typeof(SqlServerTestDbParameters))] + [TestFixture(typeof(SqlServerResiliencyTestDbParameters))] + [TestFixture(typeof(PostgresTestDbParameters))] + public class Using_custom_include_in_repository : + EntityFrameworkTestFixture + where T : ITestDbParameters, new() { [Test] public async Task A_correlated_message_should_update_inner_saga_dependency() @@ -53,27 +58,33 @@ public async Task An_initiating_message_should_start_the_saga() foundId.HasValue.ShouldBe(true); } - readonly Func _sagaDbContextFactory; - readonly Lazy> _sagaRepository; public Using_custom_include_in_repository() { - // add new migration by calling - // dotnet ef migrations add --context "SagaWithDependencyContext" Init -v - var contextFactory = new SagaWithDependencyContextFactory(); + // // add new migration by calling + // // dotnet ef migrations add --context "SagaDbContext``2" Init -v + _sagaRepository = new Lazy>(() => + EntityFrameworkSagaRepository.CreatePessimistic( + () => new SagaWithDependencyContextFactory().CreateDbContext(DbContextOptionsBuilder), + RawSqlLockStatements, + queryable => queryable.Include(it => it.Dependency).ThenInclude(dependency => dependency.SagaInnerDependency))); + } - using (var context = contextFactory.CreateDbContext(Array.Empty())) - { - context.Database.Migrate(); - } + [OneTimeSetUp] + public async Task SetUp() + { + await using var context = new SagaWithDependencyContextFactory().CreateDbContext(DbContextOptionsBuilder); - _sagaDbContextFactory = () => contextFactory.CreateDbContext(Array.Empty()); - _sagaRepository = new Lazy>(() => - EntityFrameworkSagaRepository.CreatePessimistic(_sagaDbContextFactory, - queryCustomization: queryable => - queryable.Include(it => it.Dependency).ThenInclude(dependency => dependency.SagaInnerDependency) - )); + await context.Database.MigrateAsync(); + } + + [OneTimeTearDown] + public async Task TearDown() + { + await using var context = new SagaWithDependencyContextFactory().CreateDbContext(DbContextOptionsBuilder); + + await context.Database.EnsureDeletedAsync(); } protected override void ConfigureInMemoryReceiveEndpoint(IInMemoryReceiveEndpointConfigurator configurator) diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependencyContextFactory.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependencyContextFactory.cs deleted file mode 100644 index bfd3900a06..0000000000 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaWithDependencyContextFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests -{ - using System.Reflection; - using Microsoft.EntityFrameworkCore; - using Microsoft.EntityFrameworkCore.Design; - - - public class SagaWithDependencyContextFactory : - IDesignTimeDbContextFactory - { - public SagaWithDependencyContext CreateDbContext(string[] args) - { - var dbContextOptionsBuilder = - new DbContextOptionsBuilder(); - - dbContextOptionsBuilder.UseSqlServer(LocalDbConnectionStringProvider.GetLocalDbConnectionString(), - m => - { - var executingAssembly = typeof(ContextFactory).GetTypeInfo().Assembly; - m.MigrationsAssembly(executingAssembly.GetName().Name); - m.MigrationsHistoryTable("__SagaWithDependencyMigrations"); - }); - - return new SagaWithDependencyContext(dbContextOptionsBuilder.Options); - } - } -} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/EntityFrameworkTestFixture.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/EntityFrameworkTestFixture.cs new file mode 100644 index 0000000000..b838a9e093 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/EntityFrameworkTestFixture.cs @@ -0,0 +1,22 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Shared +{ + using Microsoft.EntityFrameworkCore; + using NUnit.Framework; + using TestFramework; + + public class EntityFrameworkTestFixture + : InMemoryTestFixture + where TTestDbParameters : ITestDbParameters, new() + where TDbContext : DbContext + { + protected readonly DbContextOptionsBuilder DbContextOptionsBuilder; + protected readonly ILockStatementProvider RawSqlLockStatements; + + public EntityFrameworkTestFixture() + { + var testDbParameters = new TTestDbParameters(); + DbContextOptionsBuilder = testDbParameters.GetDbContextOptions(typeof(TDbContext)); + RawSqlLockStatements = testDbParameters.RawSqlLockStatements; + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/ITestDbParameters.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/ITestDbParameters.cs new file mode 100644 index 0000000000..7b3f509d1e --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/ITestDbParameters.cs @@ -0,0 +1,13 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Shared +{ + using System; + using Microsoft.EntityFrameworkCore; + + + public interface ITestDbParameters + { + DbContextOptionsBuilder GetDbContextOptions(Type dbContextType); + + ILockStatementProvider RawSqlLockStatements { get; } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/PostgresTestDbParameters.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/PostgresTestDbParameters.cs new file mode 100644 index 0000000000..40579ac39a --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/PostgresTestDbParameters.cs @@ -0,0 +1,26 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Shared +{ + using System; + using System.Reflection; + using Microsoft.EntityFrameworkCore; + + + public class PostgresTestDbParameters : ITestDbParameters + { + public DbContextOptionsBuilder GetDbContextOptions(Type dbContextType) + { + var dbContextOptionsBuilder = new DbContextOptionsBuilder(); + + dbContextOptionsBuilder.UseNpgsql("host=localhost;user id=postgres;password=Password12!;database=MassTransitUnitTests;", + m => + { + m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name); + m.MigrationsHistoryTable($"__{dbContextType.Name}"); + }); + + return dbContextOptionsBuilder; + } + + public ILockStatementProvider RawSqlLockStatements => new PostgresLockStatementProvider(false); + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/SqlServerResiliencyTestDbParameters.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/SqlServerResiliencyTestDbParameters.cs new file mode 100644 index 0000000000..2958b83ee2 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/SqlServerResiliencyTestDbParameters.cs @@ -0,0 +1,32 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Shared +{ + using System; + using System.Reflection; + using Microsoft.EntityFrameworkCore; + + + public class SqlServerResiliencyTestDbParameters : + ITestDbParameters + { + /// + /// Get DB context options for SQL Server, with resiliency + /// + /// Type of the DbContext, used for migration conventions + public DbContextOptionsBuilder GetDbContextOptions(Type dbContextType) + { + var dbContextOptionsBuilder = new DbContextOptionsBuilder(); + + dbContextOptionsBuilder.UseSqlServer(LocalDbConnectionStringProvider.GetLocalDbConnectionString(), + m => + { + m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name); + m.MigrationsHistoryTable($"__{dbContextType.Name}"); + m.EnableRetryOnFailure(); + }); + + return dbContextOptionsBuilder; + } + + public ILockStatementProvider RawSqlLockStatements => new SqlServerLockStatementProvider(false); + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/SqlServerTestDbParameters.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/SqlServerTestDbParameters.cs new file mode 100644 index 0000000000..99c5eb2a69 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Shared/SqlServerTestDbParameters.cs @@ -0,0 +1,31 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.Shared +{ + using System; + using System.Reflection; + using Microsoft.EntityFrameworkCore; + using TestFramework.Logging; + + + public class SqlServerTestDbParameters : ITestDbParameters + { + /// + /// Get DB context options for SQL Server. + /// + /// Type of the dbcontext, used for migration conventions + public DbContextOptionsBuilder GetDbContextOptions(Type dbContextType) + { + var dbContextOptionsBuilder = new DbContextOptionsBuilder(); + + dbContextOptionsBuilder.UseSqlServer(LocalDbConnectionStringProvider.GetLocalDbConnectionString(), + m => + { + m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name); + m.MigrationsHistoryTable($"__{dbContextType.Name}"); + }); + + return dbContextOptionsBuilder; + } + + public ILockStatementProvider RawSqlLockStatements => new SqlServerLockStatementProvider(false); + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/DataAccess/SimpleSagaContextFactory.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/DataAccess/SimpleSagaContextFactory.cs new file mode 100644 index 0000000000..dc560b435d --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/DataAccess/SimpleSagaContextFactory.cs @@ -0,0 +1,26 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SimpleSaga.DataAccess +{ + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Design; + using Shared; + + + public class SimpleSagaContextFactory : IDesignTimeDbContextFactory + { + public SimpleSagaDbContext CreateDbContext(string[] args) + { + // used only for database update and migrations. Since IDesignTimeDbContextFactory is icky, + // we only support command line tools for SQL Server, so use SQL Server if you need to do + // migrations. + + var optionsBuilder = new SqlServerTestDbParameters().GetDbContextOptions(typeof(SimpleSagaDbContext)); + + return new SimpleSagaDbContext(optionsBuilder.Options); + } + + public SimpleSagaDbContext CreateDbContext(DbContextOptionsBuilder optionsBuilder) + { + return new SimpleSagaDbContext(optionsBuilder.Options); + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSagaDbContext.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/DataAccess/SimpleSagaDbContext.cs similarity index 81% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSagaDbContext.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/DataAccess/SimpleSagaDbContext.cs index 707a5f005c..800e654004 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSagaDbContext.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/DataAccess/SimpleSagaDbContext.cs @@ -1,7 +1,6 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SimpleSaga.DataAccess { using System.Collections.Generic; - using System.Threading.Tasks; using Mappings; using Microsoft.EntityFrameworkCore; diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSagaMap.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/DataAccess/SimpleSagaMap.cs similarity index 60% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSagaMap.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/DataAccess/SimpleSagaMap.cs index 6d030042e3..ef39452d16 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSagaMap.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/DataAccess/SimpleSagaMap.cs @@ -1,14 +1,13 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SimpleSaga.DataAccess { using Mappings; - using MassTransit.Tests.Saga; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; - class SimpleSagaMap : SagaClassMap + class SimpleSagaMap : SagaClassMap { - protected override void Configure(EntityTypeBuilder entityTypeBuilder, ModelBuilder modelBuilder) + protected override void Configure(EntityTypeBuilder entityTypeBuilder, ModelBuilder modelBuilder) { entityTypeBuilder.Property(x => x.Name).HasMaxLength(40); entityTypeBuilder.Property(x => x.Initiated); diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaLocator_Specs.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/SagaLocator_Specs.cs similarity index 71% rename from src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaLocator_Specs.cs rename to src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/SagaLocator_Specs.cs index 221254bca7..b10514ddbe 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SagaLocator_Specs.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSaga/SagaLocator_Specs.cs @@ -10,25 +10,28 @@ // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -namespace MassTransit.EntityFrameworkCoreIntegration.Tests +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SimpleSaga { using System; using System.Threading.Tasks; + using DataAccess; using MassTransit.Saga; using MassTransit.Tests.Saga; using MassTransit.Tests.Saga.Messages; using Microsoft.EntityFrameworkCore; using NUnit.Framework; using Saga; + using Shared; using Shouldly; - using TestFramework; using Testing; - [TestFixture] - [Category("Integration")] - public class Locating_an_existing_ef_saga : - InMemoryTestFixture + [TestFixture(typeof(SqlServerTestDbParameters))] + [TestFixture(typeof(SqlServerResiliencyTestDbParameters))] + [TestFixture(typeof(PostgresTestDbParameters))] + public class Locating_an_existing_ef_saga : + EntityFrameworkTestFixture + where T : ITestDbParameters, new() { [Test] public async Task A_correlated_message_should_find_the_correct_saga() @@ -64,24 +67,32 @@ public async Task An_initiating_message_should_start_the_saga() foundId.HasValue.ShouldBe(true); } - readonly Func _sagaDbContextFactory; - readonly Lazy> _sagaRepository; public Locating_an_existing_ef_saga() { // add new migration by calling // dotnet ef migrations add --context "SagaDbContext``2" Init -v - var contextFactory = new ContextFactory(); + _sagaRepository = new Lazy>(() => + EntityFrameworkSagaRepository.CreatePessimistic( + () => new SimpleSagaContextFactory().CreateDbContext(DbContextOptionsBuilder), + RawSqlLockStatements)); + } - using (var context = contextFactory.CreateDbContext(Array.Empty())) - { - context.Database.Migrate(); - } + [OneTimeSetUp] + public async Task SetUp() + { + await using var context = new SimpleSagaContextFactory().CreateDbContext(DbContextOptionsBuilder); - _sagaDbContextFactory = () => contextFactory.CreateDbContext(Array.Empty()); - _sagaRepository = new Lazy>(() => - EntityFrameworkSagaRepository.CreatePessimistic(_sagaDbContextFactory)); + await context.Database.MigrateAsync(); + } + + [OneTimeTearDown] + public async Task TearDown() + { + await using var context = new SimpleSagaContextFactory().CreateDbContext(DbContextOptionsBuilder); + + await context.Database.EnsureDeletedAsync(); } protected override void ConfigureInMemoryReceiveEndpoint(IInMemoryReceiveEndpointConfigurator configurator) diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSagaDbContextWithResilienceStrategy.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSagaDbContextWithResilienceStrategy.cs deleted file mode 100644 index 08a774de5a..0000000000 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SimpleSagaDbContextWithResilienceStrategy.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MassTransit.EntityFrameworkCoreIntegration.Tests -{ - using Microsoft.EntityFrameworkCore; - - public class SimpleSagaDbContextWithResilienceStrategy : SimpleSagaDbContext - { - public SimpleSagaDbContextWithResilienceStrategy(DbContextOptions options) - : base(options) - { - - } - } -} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSaga.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSaga.cs new file mode 100644 index 0000000000..e5ee7c83e2 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSaga.cs @@ -0,0 +1,17 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SlowConcurrentSaga.DataAccess +{ + using System; + using Automatonymous; + + + public class SlowConcurrentSaga : SagaStateMachineInstance + { + public Guid CorrelationId { get; set; } + + public string CurrentState { get; set; } + + public string Name { get; set; } + + public int Counter { get; set; } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaContextFactory.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaContextFactory.cs new file mode 100644 index 0000000000..c038c4b354 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaContextFactory.cs @@ -0,0 +1,26 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SlowConcurrentSaga.DataAccess +{ + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Design; + using Shared; + + + public class SlowConcurrentSagaContextFactory : IDesignTimeDbContextFactory + { + public SlowConcurrentSagaDbContext CreateDbContext(string[] args) + { + // used only for database update and migrations. Since IDesignTimeDbContextFactory is icky, + // we only support command line tools for SQL Server, so use SQL Server if you need to do + // migrations. + + var optionsBuilder = new SqlServerTestDbParameters().GetDbContextOptions(typeof(SlowConcurrentSagaDbContext)); + + return new SlowConcurrentSagaDbContext(optionsBuilder.Options); + } + + public SlowConcurrentSagaDbContext CreateDbContext(DbContextOptionsBuilder optionsBuilder) + { + return new SlowConcurrentSagaDbContext(optionsBuilder.Options); + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaDbContext.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaDbContext.cs new file mode 100644 index 0000000000..aefe650cef --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaDbContext.cs @@ -0,0 +1,21 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SlowConcurrentSaga.DataAccess +{ + using System.Collections.Generic; + using Mappings; + using Microsoft.EntityFrameworkCore; + using SimpleSaga.DataAccess; + + + public class SlowConcurrentSagaDbContext : SagaDbContext + { + public SlowConcurrentSagaDbContext(DbContextOptions options) + : base(options) + { + } + + protected override IEnumerable Configurations + { + get { yield return new SlowConcurrentSagaMap(); } + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaMap.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaMap.cs new file mode 100644 index 0000000000..498e24b738 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/DataAccess/SlowConcurrentSagaMap.cs @@ -0,0 +1,17 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SlowConcurrentSaga.DataAccess +{ + using Mappings; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + class SlowConcurrentSagaMap : SagaClassMap + { + protected override void Configure(EntityTypeBuilder entityTypeBuilder, ModelBuilder modelBuilder) + { + entityTypeBuilder.Property(x => x.Name).HasMaxLength(40); + entityTypeBuilder.Property(x => x.CurrentState).HasMaxLength(40); + + entityTypeBuilder.ToTable("EfCoreSlowConcurrentSagas"); + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/Events/Begin.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/Events/Begin.cs new file mode 100644 index 0000000000..584c09abc8 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/Events/Begin.cs @@ -0,0 +1,10 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SlowConcurrentSaga.Events +{ + using System; + + + public class Begin + { + public Guid CorrelationId { get; set; } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/Events/IncrementCounterSlowly.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/Events/IncrementCounterSlowly.cs new file mode 100644 index 0000000000..732b212411 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/Events/IncrementCounterSlowly.cs @@ -0,0 +1,10 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SlowConcurrentSaga.Events +{ + using System; + + + public class IncrementCounterSlowly + { + public Guid CorrelationId { get; set; } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/SlowConcurrentSagaStateMachine.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/SlowConcurrentSagaStateMachine.cs new file mode 100644 index 0000000000..b8b88671b3 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/SlowConcurrentSagaStateMachine.cs @@ -0,0 +1,40 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SlowConcurrentSaga +{ + using System.Threading.Tasks; + using Automatonymous; + using DataAccess; + using Events; + + + public class SlowConcurrentSagaStateMachine : MassTransitStateMachine + { + public SlowConcurrentSagaStateMachine() + { + InstanceState(x => x.CurrentState); + + Event(() => Begin, x => x.CorrelateById(context => context.Message.CorrelationId)); + Event(() => IncrementCounterSlowly, x => x.CorrelateById(context => context.Message.CorrelationId)); + + Initially( + When(Begin) + .Then(context => context.Instance.Counter = 1) + .TransitionTo(Started)); + + During(Started, + When(IncrementCounterSlowly) + .ThenAsync(async context => + { + await Task.Delay(5000); + context.Instance.Counter++; + }) + .TransitionTo(this.DidIncrement)); + } + public Event Begin { get; set; } + + public Event IncrementCounterSlowly { get; set; } + + public State Started { get; set; } + + public State DidIncrement { get; set; } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/SlowConcurrentSaga_Specs.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/SlowConcurrentSaga_Specs.cs new file mode 100644 index 0000000000..da8459a710 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/SlowConcurrentSaga/SlowConcurrentSaga_Specs.cs @@ -0,0 +1,91 @@ +namespace MassTransit.EntityFrameworkCoreIntegration.Tests.SlowConcurrentSaga +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using DataAccess; + using Events; + using MassTransit.Saga; + using Microsoft.EntityFrameworkCore; + using NUnit.Framework; + using Saga; + using Shared; + using Shouldly; + using Testing; + + + [TestFixture(typeof(SqlServerTestDbParameters))] + [TestFixture(typeof(SqlServerResiliencyTestDbParameters))] + [TestFixture(typeof(PostgresTestDbParameters))] + public class SlowConcurrentSaga_Specs : + EntityFrameworkTestFixture + where T : ITestDbParameters, new() + { + [Test] + public async Task Two_Initiating_Messages_Deadlock_Results_In_One_Instance() + { + var sagaId = NewId.NextGuid(); + var message = new Begin {CorrelationId = sagaId}; + + await InputQueueSendEndpoint.Send(message); + + Guid? foundId = await _sagaRepository.Value.ShouldContainSaga(message.CorrelationId, TestTimeout); + + foundId.HasValue.ShouldBe(true); + + var slowMessage = new IncrementCounterSlowly {CorrelationId = sagaId}; + await Task.WhenAll( + Task.Run(() => InputQueueSendEndpoint.Send(slowMessage)), + Task.Run(() => InputQueueSendEndpoint.Send(slowMessage))); + + _sagaTestHarness.Consumed.Select().Take(2).ToList(); + + // I might be getting superstitions but it looks like sometimes the test harness can report consumed before + // the transaction is properly committed. + await Task.Delay(1000); + + await _sagaRepository.Value.ShouldContainSaga( + s => s.CorrelationId == sagaId && s.Counter == 2 && s.CurrentState == "DidIncrement", + this.TestTimeout); + } + + readonly Lazy> _sagaRepository; + readonly SagaTestHarness _sagaTestHarness; + + public SlowConcurrentSaga_Specs() + { + // rowlock statements that don't work to cause a deadlock. + var notWorkingRowLockStatements = new SqlLockStatementProvider("dbo", "SELECT * FROM \"{1}\" WHERE \"CorrelationId\" = @p0"); + + // add new migration by calling + // dotnet ef migrations add --context "SagaDbContext``2" Init -v + _sagaRepository = new Lazy>(() => + EntityFrameworkSagaRepository.CreatePessimistic( + () => new SlowConcurrentSagaContextFactory().CreateDbContext(DbContextOptionsBuilder), + notWorkingRowLockStatements)); + + _sagaTestHarness = BusTestHarness.StateMachineSaga(new SlowConcurrentSagaStateMachine(), _sagaRepository.Value); + } + + [OneTimeSetUp] + public async Task SetUp() + { + await using var context = new SlowConcurrentSagaContextFactory().CreateDbContext(DbContextOptionsBuilder); + + await context.Database.MigrateAsync(); + } + + [OneTimeTearDown] + public async Task TearDown() + { + await using var context = new SlowConcurrentSagaContextFactory().CreateDbContext(DbContextOptionsBuilder); + + await context.Database.EnsureDeletedAsync(); + } + + protected override void ConfigureInMemoryReceiveEndpoint(IInMemoryReceiveEndpointConfigurator configurator) + { + configurator.ConcurrencyLimit = 16; + } + } +} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/TransactionOutbox_Specs.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/TransactionOutbox_Specs.cs index 63bb9cb831..903662269a 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/TransactionOutbox_Specs.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/TransactionOutbox_Specs.cs @@ -29,7 +29,6 @@ namespace MassTransit.EntityFrameworkCoreIntegration.Tests /// so this was the easiest project to add a test spec to which has EF Core already referenced. /// [TestFixture] - [Category("Integration")] public class TransactionOutbox_Specs : InMemoryTestFixture { @@ -37,10 +36,10 @@ public class TransactionOutbox_Specs : public async Task Should_publish_after_db_create() { var message = new InitiateSimpleSaga(); - var product = new Product { Name = "Should_publish_after_db_create" }; + var product = new Product {Name = "Should_publish_after_db_create"}; var transactionOutbox = new TransactionOutbox(Bus, Bus, new NullLoggerFactory()); - using(var dbContext = GetDbContext()) + using (var dbContext = GetDbContext()) using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { dbContext.Products.Add(product); @@ -67,7 +66,7 @@ public async Task Should_publish_after_db_create() public async Task Should_not_publish_properly() { var message = new InitiateSimpleSaga(); - var product = new Product { Name = "Should_not_publish_properly" }; + var product = new Product {Name = "Should_not_publish_properly"}; var transactionOutbox = new TransactionOutbox(Bus, Bus, new NullLoggerFactory()); using (var dbContext = GetDbContext()) @@ -89,9 +88,10 @@ public async Task Should_not_publish_properly() Task> _received; - private TransactionOutboxTestsDbContext GetDbContext() + TransactionOutboxTestsDbContext GetDbContext() { - var dbContext = new TransactionOutboxTestsDbContext(new DbContextOptionsBuilder().UseSqlServer(LocalDbConnectionStringProvider.GetLocalDbConnectionString("MassTransitUnitTests_TransactionOutbox")).Options); + var dbContext = new TransactionOutboxTestsDbContext(new DbContextOptionsBuilder() + .UseSqlServer(LocalDbConnectionStringProvider.GetLocalDbConnectionString("MassTransitUnitTests_TransactionOutbox")).Options); return dbContext; } @@ -111,7 +111,9 @@ public TransactionOutbox_Specs() } } - public class TransactionOutboxTestsDbContext : DbContext + + public class TransactionOutboxTestsDbContext : + DbContext { public TransactionOutboxTestsDbContext(DbContextOptions options) : base(options) @@ -121,6 +123,7 @@ public TransactionOutboxTestsDbContext(DbContextOptions options) public DbSet Products { get; set; } } + public class Product { public Guid Id { get; set; } diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Using_ef_connection_resiliency.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Using_ef_connection_resiliency.cs deleted file mode 100644 index a517605163..0000000000 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/Using_ef_connection_resiliency.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2007-2019 Chris Patterson, Dru Sellers, Travis Smith, et. al. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. -namespace MassTransit.EntityFrameworkCoreIntegration.Tests -{ - using System; - using System.Threading.Tasks; - using MassTransit.Saga; - using MassTransit.Tests.Saga; - using MassTransit.Tests.Saga.Messages; - using Microsoft.EntityFrameworkCore; - using NUnit.Framework; - using Saga; - using Shouldly; - using TestFramework; - using Testing; - - - [TestFixture] - [Category("Integration")] - public class Using_ef_connection_resiliency : - InMemoryTestFixture - { - [Test] - public async Task A_correlated_message_should_find_the_correct_saga() - { - var sagaId = NewId.NextGuid(); - var message = new InitiateSimpleSaga(sagaId); - - await InputQueueSendEndpoint.Send(message); - - Guid? foundId = await _sagaRepository.Value.ShouldContainSaga(message.CorrelationId, TestTimeout); - - foundId.HasValue.ShouldBe(true); - - var nextMessage = new CompleteSimpleSaga {CorrelationId = sagaId}; - - await InputQueueSendEndpoint.Send(nextMessage); - - foundId = await _sagaRepository.Value.ShouldContainSaga(x => x.CorrelationId == sagaId && x.Completed, TestTimeout); - - foundId.HasValue.ShouldBe(true); - } - - [Test] - public async Task An_initiating_message_should_start_the_saga() - { - var sagaId = NewId.NextGuid(); - var message = new InitiateSimpleSaga(sagaId); - - await InputQueueSendEndpoint.Send(message); - - Guid? foundId = await _sagaRepository.Value.ShouldContainSaga(message.CorrelationId, TestTimeout); - - foundId.HasValue.ShouldBe(true); - } - - readonly Func _sagaDbContextFactory; - - readonly Lazy> _sagaRepository; - - public Using_ef_connection_resiliency() - { - // add new migration by calling - // dotnet ef migrations add --context "SagaDbContext``2" Init -v - var contextFactory = new ContextFactoryWithResilienceStrategy(); - - using (var context = contextFactory.CreateDbContext(Array.Empty())) - { - context.Database.Migrate(); - } - - _sagaDbContextFactory = () => contextFactory.CreateDbContext(Array.Empty()); - _sagaRepository = new Lazy>(() => - EntityFrameworkSagaRepository.CreatePessimistic(_sagaDbContextFactory)); - } - - protected override void ConfigureInMemoryReceiveEndpoint(IInMemoryReceiveEndpointConfigurator configurator) - { - configurator.Saga(_sagaRepository.Value); - } - } -} diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/docker-compose.yml b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/docker-compose.yml new file mode 100644 index 0000000000..ca3f9be1e7 --- /dev/null +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration.Tests/docker-compose.yml @@ -0,0 +1,17 @@ +# this compose file will start local services the same as those running on appveyor CI for testing. + +version: '2.3' +services: + mssql: + image: "mcr.microsoft.com/mssql/server:2017-latest" + environment: + - "ACCEPT_EULA=Y" + - "SA_PASSWORD=Password12!" + ports: + - 1433:1433 + postgres: + image: "postgres:9.6" + environment: + - "POSTGRES_PASSWORD=Password12!" + ports: + - 5432:5432 diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/MassTransit.EntityFrameworkCoreIntegration.csproj b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/MassTransit.EntityFrameworkCoreIntegration.csproj index be1762ea0b..03f7ac2a17 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/MassTransit.EntityFrameworkCoreIntegration.csproj +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/MassTransit.EntityFrameworkCoreIntegration.csproj @@ -1,9 +1,9 @@  - - + + - net461;netstandard2.0 + net461;netstandard2.0 @@ -14,8 +14,8 @@ - - - + + + diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaConsumeContext.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaConsumeContext.cs index dbb6a5376b..da4dbd1582 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaConsumeContext.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaConsumeContext.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using Context; - using GreenPipes; using MassTransit.Saga; using Microsoft.EntityFrameworkCore; using Util; @@ -13,7 +12,7 @@ public class EntityFrameworkSagaConsumeContext : ConsumeContextScope, SagaConsumeContext, - IAsyncDisposable + GreenPipes.IAsyncDisposable where TMessage : class where TSaga : class, ISaga { diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaRepository.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaRepository.cs index be6ca3667b..1d557e7bbb 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaRepository.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaRepository.cs @@ -35,11 +35,7 @@ public static class EntityFrameworkSagaRepository { var statementProvider = lockStatementProvider ?? new SqlServerLockStatementProvider(); - ILoadQueryProvider queryProvider = new DefaultSagaLoadQueryProvider(); - if (queryCustomization != null) - queryProvider = new CustomSagaLoadQueryProvider(queryProvider, queryCustomization); - - var queryExecutor = new PessimisticLoadQueryExecutor(queryProvider, statementProvider); + var queryExecutor = new PessimisticLoadQueryExecutor(statementProvider, queryCustomization); var lockStrategy = new PessimisticSagaRepositoryLockStrategy(queryExecutor); return CreateRepository(dbContextFactory, lockStrategy, IsolationLevel.Serializable); diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaRepositoryContextFactory.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaRepositoryContextFactory.cs index 1ed59c1fb3..d1785fc61a 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaRepositoryContextFactory.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/EntityFrameworkSagaRepositoryContextFactory.cs @@ -2,7 +2,6 @@ namespace MassTransit.EntityFrameworkCoreIntegration.Saga { using System; using System.Data; - using System.Data.SqlClient; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -60,7 +59,7 @@ public async Task Send(ConsumeContext context, IPipe Execute(Func, Task> asyn }); var executionStrategy = dbContext.Database.CreateExecutionStrategy(); - if (executionStrategy is SqlServerRetryingExecutionStrategy) + if (executionStrategy is ExecutionStrategy) { return await executionStrategy.ExecuteAsync(Send).ConfigureAwait(false); } @@ -145,7 +144,7 @@ public async Task Execute(Func, Task> asyn async Task WithinTransaction(DbContext context, CancellationToken cancellationToken, Func callback) { - using var transaction = await context.Database.BeginTransactionAsync(_isolationLevel, cancellationToken).ConfigureAwait(false); + await using var transaction = await context.Database.BeginTransactionAsync(_isolationLevel, cancellationToken).ConfigureAwait(false); void Rollback() { @@ -170,11 +169,9 @@ void Rollback() Rollback(); throw; } - catch (DbUpdateException ex) + catch (DbUpdateException) { - if (!IsDeadlockException(ex)) - Rollback(); - + Rollback(); throw; } catch (Exception) @@ -186,7 +183,7 @@ void Rollback() async Task WithinTransaction(DbContext context, CancellationToken cancellationToken, Func> callback) { - using var transaction = await context.Database.BeginTransactionAsync(_isolationLevel, cancellationToken).ConfigureAwait(false); + await using var transaction = await context.Database.BeginTransactionAsync(_isolationLevel, cancellationToken).ConfigureAwait(false); void Rollback() { @@ -213,11 +210,9 @@ void Rollback() Rollback(); throw; } - catch (DbUpdateException ex) + catch (DbUpdateException) { - if (!IsDeadlockException(ex)) - Rollback(); - + Rollback(); throw; } catch (Exception) @@ -226,10 +221,5 @@ void Rollback() throw; } } - - static bool IsDeadlockException(Exception exception) - { - return exception.GetBaseException() is SqlException baseException && baseException.Number == 1205; - } } } diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/PessimisticLoadQueryExecutor.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/PessimisticLoadQueryExecutor.cs index d3f7e60bf1..c76ea3fe1d 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/PessimisticLoadQueryExecutor.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Saga/PessimisticLoadQueryExecutor.cs @@ -1,6 +1,7 @@ namespace MassTransit.EntityFrameworkCoreIntegration.Saga { using System; + using System.Linq; using System.Threading; using System.Threading.Tasks; using MassTransit.Saga; @@ -11,23 +12,26 @@ public class PessimisticLoadQueryExecutor : ILoadQueryExecutor where TSaga : class, ISaga { - readonly ILoadQueryProvider _queryProvider; readonly ILockStatementProvider _lockStatementProvider; + readonly Func, IQueryable> _queryCustomization; string _lockStatement; - public PessimisticLoadQueryExecutor(ILoadQueryProvider queryProvider, ILockStatementProvider lockStatementProvider) + public PessimisticLoadQueryExecutor(ILockStatementProvider lockStatementProvider, Func, IQueryable> queryCustomization) { - _queryProvider = queryProvider; _lockStatementProvider = lockStatementProvider; + _queryCustomization = queryCustomization; } public Task Load(DbContext dbContext, Guid correlationId, CancellationToken cancellationToken) { - var queryable = _queryProvider.GetQueryable(dbContext); - var statement = GetLockStatement(dbContext); - return queryable.FromSql(statement, correlationId).SingleOrDefaultAsync(cancellationToken); + IQueryable queryable = dbContext.Set().FromSqlRaw(statement, correlationId); + + if (_queryCustomization != null) + queryable = _queryCustomization(queryable); + + return queryable.SingleOrDefaultAsync(cancellationToken); } string GetLockStatement(DbContext dbContext) diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/SqlServerLockStatementProvider.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/SqlServerLockStatementProvider.cs index c1fe890473..1c31140b86 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/SqlServerLockStatementProvider.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/SqlServerLockStatementProvider.cs @@ -9,58 +9,84 @@ using Microsoft.EntityFrameworkCore.Metadata; + public class PostgresLockStatementProvider : + SqlLockStatementProvider + { + const string DefaultSchemaName = "public"; + const string DefaultRowLockStatement = "SELECT * FROM \"{0}\".\"{1}\" WHERE \"CorrelationId\" = @p0 FOR UPDATE"; + + public PostgresLockStatementProvider(bool enableSchemaCaching = true) + : base(DefaultSchemaName, DefaultRowLockStatement, enableSchemaCaching) + { + } + } + + public class SqlServerLockStatementProvider : - ILockStatementProvider + SqlLockStatementProvider { - const string DefaultRowLockStatement = "select * from {0}.{1} WITH (UPDLOCK, ROWLOCK) WHERE CorrelationId = @p0"; + const string DefaultRowLockStatement = "SELECT * FROM {0}.{1} WITH (UPDLOCK, ROWLOCK) WHERE CorrelationId = @p0"; const string DefaultSchemaName = "dbo"; + public SqlServerLockStatementProvider(bool enableSchemaCaching = true) + : base(DefaultSchemaName, DefaultRowLockStatement, enableSchemaCaching) + { + } + } + + + public class SqlLockStatementProvider : + ILockStatementProvider + { + readonly bool _enableSchemaCaching; protected static readonly ConcurrentDictionary TableNames = new ConcurrentDictionary(); protected readonly ConcurrentDictionary Statements = new ConcurrentDictionary(); - public SqlServerLockStatementProvider(string defaultSchema = DefaultSchemaName, string rowLockStatement = DefaultRowLockStatement, - Func relationalEntityTypeAnnotations = null) + public SqlLockStatementProvider(string defaultSchema, string rowLockStatement, bool enableSchemaCaching = true) { + _enableSchemaCaching = enableSchemaCaching; DefaultSchema = defaultSchema ?? throw new ArgumentNullException(nameof(defaultSchema)); RowLockStatement = rowLockStatement ?? throw new ArgumentNullException(nameof(rowLockStatement)); - - RelationalEntityTypeAnnotations = relationalEntityTypeAnnotations ?? GetRelationalEntityTypeAnnotations; } protected string DefaultSchema { get; } protected string RowLockStatement { get; } - protected Func RelationalEntityTypeAnnotations { get; } public virtual string GetRowLockStatement(DbContext context) where TSaga : class, ISaga { - return Statements.GetOrAdd(typeof(TSaga), type => - { - var schemaTablePair = GetSchemaAndTableName(context, typeof(TSaga)); + return _enableSchemaCaching + ? Statements.GetOrAdd(typeof(TSaga), type => FormatLockStatement(context)) + : FormatLockStatement(context); + } - return string.Format(RowLockStatement, schemaTablePair.Schema, schemaTablePair.Table); - }); + string FormatLockStatement(IDbContextDependencies context) + where TSaga : class, ISaga + { + var schemaTablePair = GetSchemaAndTableName(context, typeof(TSaga)); + + return string.Format(RowLockStatement, schemaTablePair.Schema, schemaTablePair.Table); } SchemaTablePair GetSchemaAndTableName(IDbContextDependencies dependencies, Type type) { - if (TableNames.TryGetValue(type, out var result)) + if (TableNames.TryGetValue(type, out var result) && _enableSchemaCaching) return result; - var annotations = RelationalEntityTypeAnnotations(dependencies.Model.FindEntityType(type)); + var entityType = dependencies.Model.FindEntityType(type); + + var schema = entityType.GetSchema(); + var tableName = entityType.GetTableName(); - if (string.IsNullOrWhiteSpace(result.Table)) + if (string.IsNullOrWhiteSpace(tableName)) throw new MassTransitException($"Unable to determine saga table name: {TypeMetadataCache.GetShortName(type)} (using model metadata)."); - result = new SchemaTablePair(annotations.Schema ?? DefaultSchema, annotations.TableName); + result = new SchemaTablePair(schema ?? DefaultSchema, tableName); - TableNames.TryAdd(type, result); - return result; - } + if (_enableSchemaCaching) + TableNames.TryAdd(type, result); - protected virtual IRelationalEntityTypeAnnotations GetRelationalEntityTypeAnnotations(IEntityType entityType) - { - return entityType.SqlServer(); + return result; }