From 147b04a85612acf46ea757dbed32e2202fd9ffad Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Fri, 7 Apr 2023 19:16:16 +0800 Subject: [PATCH] fix: fix [issues-542](https://github.com/masastack/MASA.Framework/issues/542) --- .../DefaultMasaDbContext.cs | 32 ++++++++----- .../Extensions/ServiceCollectionExtensions.cs | 16 ++++--- .../CustomDbContext.cs | 3 ++ .../MasaDbContextTest.cs | 47 +++++++++++++++++++ 4 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/DefaultMasaDbContext.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/DefaultMasaDbContext.cs index cc2d8dae7..3b9c51abd 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/DefaultMasaDbContext.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/DefaultMasaDbContext.cs @@ -71,14 +71,20 @@ internal void TryInitializeMasaDbContextOptions(MasaDbContextOptions? options) try { - base.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + _ = base.ChangeTracker; } catch (InvalidOperationException ex) { - var logger = Options!.ServiceProvider?.GetService(Options.ContextType) as ILogger; - logger?.LogDebug("Error generating data context", ex); - throw new InvalidOperationException( - "No database provider has been configured for this DbContext. A provider can be configured by overriding the 'MasaDbContext.OnConfiguring' method or by using 'AddMasaDbContext' on the application service provider. If 'AddMasaDbContext' is used, then also ensure that your DbContext type accepts a 'MasaDbContextOptions' object in its constructor and passes it to the base constructor for DbContext."); + ILogger? logger = null; + if (options != null) + { + var loggerType = typeof(ILogger<>).MakeGenericType(options.ContextType); + logger = options.ServiceProvider?.GetService(loggerType) as ILogger; + } + + logger ??= MasaApp.GetService>(); + logger?.LogDebug(ex, "Error generating data context"); + throw new InvalidOperationException("No database provider has been configured for this DbContext. A provider can be configured by overriding the 'MasaDbContext.OnConfiguring' method or by using 'AddMasaDbContext' on the application service provider. If 'AddMasaDbContext' is used, then also ensure that your DbContext type accepts a 'MasaDbContextOptions' object in its constructor and passes it to the base constructor for DbContext."); } } @@ -158,8 +164,8 @@ protected virtual void ConfigureGlobalFilters(ModelBuilder modelBuilder if (typeof(IMultiEnvironment).IsAssignableFrom(typeof(TEntity)) && EnvironmentContext != null) { Expression> envFilter = entity => !IsEnvironmentFilterEnabled || - EF.Property(entity, nameof(IMultiEnvironment.Environment)) - .Equals(EnvironmentContext != null ? EnvironmentContext.CurrentEnvironment : default); + EF.Property(entity, nameof(IMultiEnvironment.Environment)) + .Equals(EnvironmentContext != null ? EnvironmentContext.CurrentEnvironment : default); expression = envFilter.And(expression != null, expression); } @@ -176,11 +182,12 @@ protected virtual void ConfigureGlobalFilters(ModelBuilder modelBuilder { string defaultTenantId = Guid.Empty.ToString(); Expression> tenantFilter = entity => !IsTenantFilterEnabled || - (EF.Property(entity, nameof(IMultiTenant.TenantId)).ToString()) - .Equals(TenantContext.CurrentTenant != null ? TenantContext.CurrentTenant.Id : defaultTenantId); + (EF.Property(entity, nameof(IMultiTenant.TenantId)).ToString()) + .Equals(TenantContext.CurrentTenant != null ? TenantContext.CurrentTenant.Id : defaultTenantId); expression = tenantFilter.And(expression != null, expression); } + return expression; } @@ -294,7 +301,7 @@ public class DefaultMasaDbContext : DefaultMasaDbContext { protected override bool IsTenantFilterEnabled => DataFilter?.IsEnabled>() ?? false; - protected DefaultMasaDbContext() + protected DefaultMasaDbContext() : base() { } @@ -310,11 +317,12 @@ public DefaultMasaDbContext(MasaDbContextOptions options) : base(options) { string defaultTenantId = default(TMultiTenantId)?.ToString() ?? string.Empty; Expression> tenantFilter = entity => !IsTenantFilterEnabled || - (EF.Property(entity, nameof(IMultiTenant.TenantId)).ToString() ?? string.Empty) - .Equals(TenantContext.CurrentTenant != null ? TenantContext.CurrentTenant.Id : defaultTenantId); + (EF.Property(entity, nameof(IMultiTenant.TenantId)).ToString() ?? string.Empty) + .Equals(TenantContext.CurrentTenant != null ? TenantContext.CurrentTenant.Id : defaultTenantId); expression = tenantFilter.And(expression != null, expression); } + return expression; } } diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Extensions/ServiceCollectionExtensions.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Extensions/ServiceCollectionExtensions.cs index 753c248c1..a4f06024c 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Extensions/ServiceCollectionExtensions.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Extensions/ServiceCollectionExtensions.cs @@ -43,9 +43,10 @@ private static IServiceCollection AddCoreServices( { parameters.Add(serviceProvider.GetService(parameter.ParameterType)); } - var dbContext = parameters.Count > 0 ? - Activator.CreateInstance(typeof(TDbContextImplementation), parameters.ToArray()) as DefaultMasaDbContext : - Activator.CreateInstance(typeof(TDbContextImplementation)) as DefaultMasaDbContext; + + var dbContext = parameters.Count > 0 + ? Activator.CreateInstance(typeof(TDbContextImplementation), parameters.ToArray()) as DefaultMasaDbContext + : Activator.CreateInstance(typeof(TDbContextImplementation)) as DefaultMasaDbContext; MasaArgumentException.ThrowIfNull(dbContext); dbContext.TryInitializeMasaDbContextOptions(serviceProvider.GetService>()); @@ -57,8 +58,12 @@ private static IServiceCollection AddCoreServices( optionsBuilder?.Invoke(masaBuilder); return services.AddCoreServices((serviceProvider, efDbContextOptionsBuilder) => { - efDbContextOptionsBuilder.DbContextOptionsBuilder.UseApplicationServiceProvider(serviceProvider); - masaBuilder.Builder?.Invoke(serviceProvider, efDbContextOptionsBuilder.DbContextOptionsBuilder); + if (masaBuilder.Builder != null) + { + efDbContextOptionsBuilder.DbContextOptionsBuilder.UseApplicationServiceProvider(serviceProvider); + efDbContextOptionsBuilder.DbContextOptionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + masaBuilder.Builder.Invoke(serviceProvider, efDbContextOptionsBuilder.DbContextOptionsBuilder); + } }, masaBuilder.EnableSoftDelete, optionsLifetime); } @@ -180,7 +185,6 @@ private static IServiceCollection TryAddConfigure( #pragma warning disable S2094 private sealed class MasaDbContextProvider { - } #pragma warning restore S2094 #pragma warning restore S2326 diff --git a/src/Contrib/Data/Orm/EFCore/Scenes/Masa.Contrib.Data.EFCore.Tests.Scenes.Isolation/CustomDbContext.cs b/src/Contrib/Data/Orm/EFCore/Scenes/Masa.Contrib.Data.EFCore.Tests.Scenes.Isolation/CustomDbContext.cs index c81d4d718..2a9ac5179 100644 --- a/src/Contrib/Data/Orm/EFCore/Scenes/Masa.Contrib.Data.EFCore.Tests.Scenes.Isolation/CustomDbContext.cs +++ b/src/Contrib/Data/Orm/EFCore/Scenes/Masa.Contrib.Data.EFCore.Tests.Scenes.Isolation/CustomDbContext.cs @@ -18,6 +18,7 @@ public class CustomDbContext2 : MasaDbContext public CustomDbContext2(MasaDbContextOptions options) : base(options) { + base.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTrackingWithIdentityResolution; } } @@ -29,6 +30,8 @@ protected override void OnConfiguring(MasaDbContextOptionsBuilder optionsBuilder { var connectionString = "data source=customDbContext3"; optionsBuilder.UseSqlite(connectionString); + + optionsBuilder.DbContextOptionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTrackingWithIdentityResolution); } } diff --git a/src/Contrib/Data/Orm/EFCore/Scenes/Masa.Contrib.Data.EFCore.Tests.Scenes.Isolation/MasaDbContextTest.cs b/src/Contrib/Data/Orm/EFCore/Scenes/Masa.Contrib.Data.EFCore.Tests.Scenes.Isolation/MasaDbContextTest.cs index 882a165f2..5fc2c0f5b 100644 --- a/src/Contrib/Data/Orm/EFCore/Scenes/Masa.Contrib.Data.EFCore.Tests.Scenes.Isolation/MasaDbContextTest.cs +++ b/src/Contrib/Data/Orm/EFCore/Scenes/Masa.Contrib.Data.EFCore.Tests.Scenes.Isolation/MasaDbContextTest.cs @@ -1,6 +1,8 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +using Microsoft.Extensions.Logging; + namespace Masa.Contrib.Data.EFCore.Tests.Scenes.Isolation; [TestClass] @@ -95,6 +97,50 @@ public async Task TestTenantIdByAddOrderAsync() Assert.AreEqual(2, orderAll.Count); } + [TestMethod] + public void TestQueryTrackingBehaviorByDefault() + { + var services = new ServiceCollection(); + services.AddMasaDbContext(dbContext => + { + dbContext.UseInMemoryDatabase(Guid.NewGuid().ToString()); + dbContext.UseFilter(); + }); + var rootServiceProvider = services.BuildServiceProvider(); + + var dbContext = rootServiceProvider.GetRequiredService(); + Assert.AreEqual(QueryTrackingBehavior.NoTracking, dbContext.ChangeTracker.QueryTrackingBehavior); + } + + [TestMethod] + public void TestCustomQueryTrackingBehaviorByConstructor() + { + var services = new ServiceCollection(); + services.AddMasaDbContext(dbContext => + { + dbContext.UseInMemoryDatabase(Guid.NewGuid().ToString()); + dbContext.UseFilter(); + }); + var rootServiceProvider = services.BuildServiceProvider(); + + var dbContext = rootServiceProvider.GetRequiredService(); + Assert.AreEqual(QueryTrackingBehavior.NoTrackingWithIdentityResolution, dbContext.ChangeTracker.QueryTrackingBehavior); + } + + [TestMethod] + public void TestCustomQueryTrackingBehaviorByOnConfiguring() + { + var services = new ServiceCollection(); + services.AddMasaDbContext(dbContext => + { + dbContext.UseFilter(); + }); + var rootServiceProvider = services.BuildServiceProvider(); + + var dbContext = rootServiceProvider.GetRequiredService(); + Assert.AreEqual(QueryTrackingBehavior.NoTrackingWithIdentityResolution, dbContext.ChangeTracker.QueryTrackingBehavior); + } + [TestMethod] public async Task TestTenantIdByAddOrderAndNoConstructorAsync() { @@ -135,6 +181,7 @@ public async Task TestTenantIdByAddOrderAndNoConstructorAsync() [TestMethod] public void TestAddMasaDbContextWhenNotUseDatabase() { + _services.AddLogging(log => log.AddConsole()); _services.AddMasaDbContext(builder => builder.UseFilter()); var rootServiceProvider = _services.BuildServiceProvider();