From 8ae0e794067bdfdc5b61b545c0b597895b547571 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 18 May 2022 19:17:09 +0800 Subject: [PATCH 1/3] fix(Data.Contracts.EF): Fix the problem that soft delete fails when using IsolationDbContext --- .../Internal/ServiceCollectionExtensions.cs | 22 +++++++++++++++++++ .../MasaDbContextOptionsBuilderExtensions.cs | 4 +++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/Data/Masa.Contrib.Data.Contracts.EF/Internal/ServiceCollectionExtensions.cs diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/Internal/ServiceCollectionExtensions.cs b/src/Data/Masa.Contrib.Data.Contracts.EF/Internal/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..18e1d7e0a --- /dev/null +++ b/src/Data/Masa.Contrib.Data.Contracts.EF/Internal/ServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Data.Contracts.EF.Internal; + +internal static class ServiceCollectionExtensions +{ + public static IServiceCollection TryAddEnumerable( + this IServiceCollection services, + Type serviceType, + Type implementationType, + ServiceDescriptor serviceDescriptor) + { + if (services.Any(s => s.ServiceType == serviceType && s.ImplementationType == implementationType)) + { + return services; + } + + services.Add(serviceDescriptor); + return services; + } +} diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/MasaDbContextOptionsBuilderExtensions.cs b/src/Data/Masa.Contrib.Data.Contracts.EF/MasaDbContextOptionsBuilderExtensions.cs index 997e47aa7..b189bbe59 100644 --- a/src/Data/Masa.Contrib.Data.Contracts.EF/MasaDbContextOptionsBuilderExtensions.cs +++ b/src/Data/Masa.Contrib.Data.Contracts.EF/MasaDbContextOptionsBuilderExtensions.cs @@ -49,7 +49,9 @@ private static void UseSoftDelete(this MasaDbContextOptionsBuilder masaDbContext var constructorInfo = softDeleteSaveChangesFilterType.GetConstructors().FirstOrDefault()!; var invokeDelegate = InstanceBuilder.CreateInstanceDelegate(constructorInfo); - masaDbContextOptionsBuilder.Services.TryAdd( + masaDbContextOptionsBuilder.Services.TryAddEnumerable( + typeof(ISaveChangesFilter), + softDeleteSaveChangesFilterType, new ServiceDescriptor(typeof(ISaveChangesFilter), serviceProvider => { From b7a2cf8936c09f5dabff971743c487048ccc1289 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 19 May 2022 09:22:31 +0800 Subject: [PATCH 2/3] test(EntityFramework): Add SoftDelete Integration Test --- .../CustomDbContext.cs | 16 ++++ ...Masa.Contrib.Isolation.UoW.EF.Tests.csproj | 1 + .../TestIsolation.cs | 95 ++++++++++++++++--- .../_Imports.cs | 3 + 4 files changed, 100 insertions(+), 15 deletions(-) diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs index 3c70f7f92..c0e8e0624 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs @@ -9,6 +9,8 @@ public CustomDbContext(MasaDbContextOptions options) : base(options) { } public DbSet User { get; set; } + public DbSet Tag { get; set; } + protected override void OnModelCreatingExecuting(ModelBuilder builder) { builder.Entity(ConfigureUserEntry); @@ -40,3 +42,17 @@ public User() this.Id = Guid.NewGuid(); } } + +public class Tag : ISoftDelete +{ + public Guid Id { get; private set; } + + public string Name { get; set; } = default!; + + public bool IsDeleted { get; protected set; } + + public Tag() + { + this.Id = Guid.NewGuid(); + } +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj b/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj index 863a918a9..a5bd40440 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj @@ -24,6 +24,7 @@ + diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs index e4c8cd870..f640be9cf 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs @@ -1,8 +1,6 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -using Masa.BuildingBlocks.Data; - namespace Masa.Contrib.Isolation.UoW.EF.Tests; [TestClass] @@ -36,7 +34,8 @@ public void TestUseIsolationUoW2() eventBuilder.Setup(builder => builder.Services).Returns(_services).Verifiable(); Assert.ThrowsException(() => { - eventBuilder.Object.UseIsolationUoW(null!, dbOptionBuilder => dbOptionBuilder.UseTestSqlite(_connectionString)); + eventBuilder.Object.UseIsolationUoW(null!, + dbOptionBuilder => dbOptionBuilder.UseTestSqlite(_connectionString)); }); } @@ -69,7 +68,8 @@ public void TestUseIsolationUoWByUseEnvironment() { Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiEnvironment(), dbOptionBuilder => dbOptionBuilder.UseTestSqlite(_connectionString)); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiEnvironment(), + dbOptionBuilder => dbOptionBuilder.UseTestSqlite(_connectionString)); var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); @@ -81,7 +81,9 @@ public void TestUseIsolationUoWByUseMultiEnvironment() { Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiEnvironment().UseMultiEnvironment(), dbOptionBuilder => dbOptionBuilder.UseTestSqlite(_connectionString)); + dispatcherOption.Object.UseIsolationUoW( + isolationBuilder => isolationBuilder.UseMultiEnvironment().UseMultiEnvironment(), + dbOptionBuilder => dbOptionBuilder.UseTestSqlite(_connectionString)); var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); Assert.IsTrue(serviceProvider.GetServices().Count() == 1); @@ -93,7 +95,8 @@ public void TestUseIsolationUoWByUseTenant() { Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant(), dbOptionBuilder => dbOptionBuilder.UseTestSqlite(_connectionString)); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant(), + dbOptionBuilder => dbOptionBuilder.UseTestSqlite(_connectionString)); var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); @@ -105,7 +108,8 @@ public void TestUseIsolationUoWByUseMultiTenant() { Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant().UseMultiTenant(), dbOptionBuilder => dbOptionBuilder.UseTestSqlite(_connectionString)); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant().UseMultiTenant(), + dbOptionBuilder => dbOptionBuilder.UseTestSqlite(_connectionString)); var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); Assert.IsTrue(serviceProvider.GetServices().Count() == 1); @@ -122,7 +126,8 @@ public void TestUseIsolation() _services.AddSingleton(configurationRoot); Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant().UseMultiEnvironment(), dbOptionBuilder => dbOptionBuilder.UseSqlite()); + dispatcherOption.Object.UseIsolationUoW( + isolationBuilder => isolationBuilder.UseMultiTenant().UseMultiEnvironment(), dbOptionBuilder => dbOptionBuilder.UseSqlite()); var serviceProvider = _services.BuildServiceProvider(); var customDbContext = serviceProvider.GetRequiredService(); var unitOfWorkAccessor = serviceProvider.GetRequiredService(); @@ -164,7 +169,8 @@ public void TestUseIsolation() unifOfWorkNew3.ServiceProvider.GetRequiredService().SetTenant(new Tenant("00000000-0000-0000-0000-000000000002")); unifOfWorkNew3.ServiceProvider.GetRequiredService().SetEnvironment("development"); var dbContext3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test2" && unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test2"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test2" && + unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test2"); var unifOfWorkNew4 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); @@ -200,7 +206,8 @@ public void TestUseMultiEnvironment() }); Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiEnvironment(), dbOptionBuilder => dbOptionBuilder.UseSqlite()); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiEnvironment(), + dbOptionBuilder => dbOptionBuilder.UseSqlite()); var serviceProvider = _services.BuildServiceProvider(); var customDbContext = serviceProvider.GetRequiredService(); var unitOfWorkAccessor = serviceProvider.GetRequiredService(); @@ -214,19 +221,22 @@ public void TestUseMultiEnvironment() var unitOfWorkAccessorNew2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); unifOfWorkNew2.ServiceProvider.GetRequiredService().SetEnvironment("dev"); var dbContext2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "data source=test5" && unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "data source=test5"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "data source=test5" && + unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "data source=test5"); var unifOfWorkNew3 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); unifOfWorkNew3.ServiceProvider.GetRequiredService().SetEnvironment("pro"); var dbContext3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test6" && unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test6"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test6" && + unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test6"); var unifOfWorkNew4 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); unifOfWorkNew4.ServiceProvider.GetRequiredService().SetEnvironment("staging"); var dbContext4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "data source=test4" && unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "data source=test4"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "data source=test4" && + unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "data source=test4"); } [TestMethod] @@ -254,7 +264,8 @@ public void TestUseMultiTenant() }); Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant(), dbOptionBuilder => dbOptionBuilder.UseSqlite()); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant(), + dbOptionBuilder => dbOptionBuilder.UseSqlite()); var serviceProvider = _services.BuildServiceProvider(); var customDbContext = serviceProvider.GetRequiredService(); var unitOfWorkAccessor = serviceProvider.GetRequiredService(); @@ -293,7 +304,8 @@ public void TestUseMultiTenantAndAddMasaConfiguration() builder.AddMasaConfiguration(); Mock dispatcherOption = new(); dispatcherOption.Setup(opt => opt.Services).Returns(builder.Services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiEnvironment(), dbOptionBuilder => dbOptionBuilder.UseSqlite()); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiEnvironment(), + dbOptionBuilder => dbOptionBuilder.UseSqlite()); var serviceProvider = builder.Services.BuildServiceProvider(); var customDbContext = serviceProvider.GetRequiredService(); var unitOfWorkAccessor = serviceProvider.GetRequiredService(); @@ -302,5 +314,58 @@ public void TestUseMultiTenantAndAddMasaConfiguration() Assert.IsTrue(currentDbContextOptions.ConnectionString == "data source=test1"); } + [TestMethod] + public async Task TestUseMultiTenantAndUseFilterAsync() + { + var services = new ServiceCollection(); + services.Configure(option => + { + option.ConnectionStrings = new ConnectionStrings() + { + DefaultConnection = $"data source=test_{Guid.NewGuid()}" + }; + option.IsolationConnectionStrings = new List + { + new() + { + ConnectionString = $"data source=test_{Guid.NewGuid()}", + TenantId = "1" + } + }; + }); + Mock dispatcherOption = new(); + dispatcherOption.Setup(opt => opt.Services).Returns(services).Verifiable(); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => + isolationBuilder.UseMultiTenant(), + dbOptionBuilder => dbOptionBuilder.UseSqlite().UseFilter()); + + var serviceProvider = services.BuildServiceProvider(); + var customDbContext = serviceProvider.GetRequiredService(); + await customDbContext.Database.EnsureCreatedAsync(); + var tag = new Tag() + { + Name = "Tom" + }; + await customDbContext.Set().AddAsync(tag); + await customDbContext.SaveChangesAsync(); + + Assert.IsTrue(await customDbContext.Set().CountAsync() == 1); + + tag = await customDbContext.Set().FirstOrDefaultAsync(t => t.Id == tag.Id); + Assert.IsNotNull(tag); + + customDbContext.Set().Remove(tag); + await customDbContext.SaveChangesAsync(); + Assert.IsTrue(await customDbContext.Set().CountAsync() == 0); + + var dataFilter = serviceProvider.GetRequiredService(); + using (dataFilter.Disable()) + { + Assert.IsTrue(await customDbContext.Set().CountAsync() == 1); + tag = await customDbContext.Set().FirstOrDefaultAsync(t => t.Id == tag.Id); + Assert.IsNotNull(tag); + } + } + private string GetDataBaseConnectionString(CustomDbContext dbContext) => dbContext.Database.GetConnectionString()!; } diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs index 8f68488b0..c578e8b6d 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs @@ -1,12 +1,15 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +global using Masa.BuildingBlocks.Data; +global using Masa.BuildingBlocks.Data.Contracts.DataFiltering; global using Masa.BuildingBlocks.Data.UoW; global using Masa.BuildingBlocks.Dispatcher.Events; global using Masa.BuildingBlocks.Isolation.Environment; global using Masa.BuildingBlocks.Isolation.MultiTenant; global using Masa.BuildingBlocks.Isolation.Options; global using Masa.Contrib.Configuration; +global using Masa.Contrib.Data.Contracts.EF; global using Masa.Contrib.Data.EntityFrameworkCore; global using Masa.Contrib.Data.EntityFrameworkCore.Sqlite; global using Masa.Contrib.Isolation.MultiEnvironment; From 12489922b17f57144e33d5a8eacac79b40fc8042 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 19 May 2022 09:28:43 +0800 Subject: [PATCH 3/3] chore: Remove invalid judgments --- .../Internal/ServiceCollectionExtensions.cs | 22 ------------------- .../MasaDbContextOptionsBuilderExtensions.cs | 4 +--- .../TestIsolation.cs | 2 +- 3 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 src/Data/Masa.Contrib.Data.Contracts.EF/Internal/ServiceCollectionExtensions.cs diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/Internal/ServiceCollectionExtensions.cs b/src/Data/Masa.Contrib.Data.Contracts.EF/Internal/ServiceCollectionExtensions.cs deleted file mode 100644 index 18e1d7e0a..000000000 --- a/src/Data/Masa.Contrib.Data.Contracts.EF/Internal/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace Masa.Contrib.Data.Contracts.EF.Internal; - -internal static class ServiceCollectionExtensions -{ - public static IServiceCollection TryAddEnumerable( - this IServiceCollection services, - Type serviceType, - Type implementationType, - ServiceDescriptor serviceDescriptor) - { - if (services.Any(s => s.ServiceType == serviceType && s.ImplementationType == implementationType)) - { - return services; - } - - services.Add(serviceDescriptor); - return services; - } -} diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/MasaDbContextOptionsBuilderExtensions.cs b/src/Data/Masa.Contrib.Data.Contracts.EF/MasaDbContextOptionsBuilderExtensions.cs index b189bbe59..de6b72e41 100644 --- a/src/Data/Masa.Contrib.Data.Contracts.EF/MasaDbContextOptionsBuilderExtensions.cs +++ b/src/Data/Masa.Contrib.Data.Contracts.EF/MasaDbContextOptionsBuilderExtensions.cs @@ -49,9 +49,7 @@ private static void UseSoftDelete(this MasaDbContextOptionsBuilder masaDbContext var constructorInfo = softDeleteSaveChangesFilterType.GetConstructors().FirstOrDefault()!; var invokeDelegate = InstanceBuilder.CreateInstanceDelegate(constructorInfo); - masaDbContextOptionsBuilder.Services.TryAddEnumerable( - typeof(ISaveChangesFilter), - softDeleteSaveChangesFilterType, + masaDbContextOptionsBuilder.Services.Add( new ServiceDescriptor(typeof(ISaveChangesFilter), serviceProvider => { diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs index f640be9cf..16e21e12a 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs @@ -337,7 +337,7 @@ public async Task TestUseMultiTenantAndUseFilterAsync() dispatcherOption.Setup(opt => opt.Services).Returns(services).Verifiable(); dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant(), - dbOptionBuilder => dbOptionBuilder.UseSqlite().UseFilter()); + dbOptionBuilder => dbOptionBuilder.UseSqlite().UseFilter().UseFilter()); var serviceProvider = services.BuildServiceProvider(); var customDbContext = serviceProvider.GetRequiredService();