diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs new file mode 100644 index 00000000000..8c4b636d9e7 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Volo.Abp.EntityFrameworkCore; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class AbpEfCoreDbContextOptionsBuilderExtensions +{ + public static DbContextOptionsBuilder AddAbpDbContextOptionsExtension(this DbContextOptionsBuilder optionsBuilder) + { + ((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(new AbpDbContextOptionsExtension()); + return optionsBuilder; + } + + public static DbContextOptionsBuilder AddAbpDbContextOptionsExtension(this DbContextOptionsBuilder optionsBuilder) + where TContext : DbContext + { + ((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(new AbpDbContextOptionsExtension()); + return optionsBuilder; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs new file mode 100644 index 00000000000..19ad9e02679 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs @@ -0,0 +1,68 @@ +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Volo.Abp; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; +using Volo.Abp.MultiTenancy; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class AbpEfCoreModelBuilderExtensions +{ + public static ModelBuilder ConfigureSoftDeleteDbFunction(this ModelBuilder modelBuilder, MethodInfo methodInfo, AbpEfCoreCurrentDbContext abpEfCoreCurrentDbContext) + { + modelBuilder.HasDbFunction(methodInfo) + .HasTranslation(args => + { + // (bool isDeleted, bool boolParam) + var isDeleted = args[0]; + var boolParam = args[1]; + + if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled() == true) + { + // IsDeleted == false + return new SqlBinaryExpression( + ExpressionType.Equal, + isDeleted, + new SqlConstantExpression(Expression.Constant(false), boolParam.TypeMapping), + boolParam.Type, + boolParam.TypeMapping); + } + + // empty where sql + return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping); + }); + + return modelBuilder; + } + + public static ModelBuilder ConfigureMultiTenantDbFunction(this ModelBuilder modelBuilder, MethodInfo methodInfo, AbpEfCoreCurrentDbContext abpEfCoreCurrentDbContext) + { + modelBuilder.HasDbFunction(methodInfo) + .HasTranslation(args => + { + // (Guid? tenantId, int? currentTenantId) + var tenantId = args[0]; + var currentTenantId = args[1]; + var boolParam = args[2]; + + if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled() == true) + { + // TenantId == CurrentTenantId + return new SqlBinaryExpression( + ExpressionType.Equal, + tenantId, + currentTenantId, + boolParam.Type, + boolParam.TypeMapping); + } + + // empty where sql + return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping); + }); + + return modelBuilder; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 138bf82ec4f..c1c87cdeadc 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -11,11 +11,12 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; using Volo.Abp.Auditing; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; @@ -24,6 +25,7 @@ using Volo.Abp.Domain.Repositories; using Volo.Abp.EntityFrameworkCore.ChangeTrackers; using Volo.Abp.EntityFrameworkCore.EntityHistory; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.ValueConverters; using Volo.Abp.EventBus.Distributed; @@ -37,7 +39,7 @@ namespace Volo.Abp.EntityFrameworkCore; -public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, ITransientDependency +public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, IAbpEfCoreDbFunctionContext, ITransientDependency where TDbContext : DbContext { public IAbpLazyServiceProvider LazyServiceProvider { get; set; } = default!; @@ -78,6 +80,8 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, public IOptions Options => LazyServiceProvider.LazyGetRequiredService>(); + public IOptions GlobalFilterOptions => LazyServiceProvider.LazyGetRequiredService>(); + private static readonly MethodInfo ConfigureBasePropertiesMethodInfo = typeof(AbpDbContext) .GetMethod( @@ -713,7 +717,7 @@ protected virtual void ConfigureGlobalFilters(ModelBuilder modelBuilder { if (mutableEntityType.BaseType == null && ShouldFilterEntity(mutableEntityType)) { - var filterExpression = CreateFilterExpression(); + var filterExpression = CreateFilterExpression(modelBuilder); if (filterExpression != null) { modelBuilder.Entity().HasAbpQueryFilter(filterExpression); @@ -782,22 +786,41 @@ protected virtual bool ShouldFilterEntity(IMutableEntityType entityType return false; } - protected virtual Expression>? CreateFilterExpression() + protected virtual Expression>? CreateFilterExpression(ModelBuilder modelBuilder) where TEntity : class { Expression>? expression = null; + var abpEfCoreCurrentDbContext = this.GetService(); if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) { expression = e => !IsSoftDeleteFilterEnabled || !EF.Property(e, "IsDeleted"); + + if (LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction) + { + expression = e => AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilter(((ISoftDelete)e).IsDeleted, true); + modelBuilder.ConfigureSoftDeleteDbFunction(AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilterMethodInfo, abpEfCoreCurrentDbContext); + } } if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity))) { Expression> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property(e, "TenantId") == CurrentTenantId; + + if (LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction) + { + multiTenantFilter = e => AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilter(((IMultiTenant)e).TenantId, CurrentTenantId, true); + modelBuilder.ConfigureMultiTenantDbFunction(AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilterMethodInfo, abpEfCoreCurrentDbContext); + } + expression = expression == null ? multiTenantFilter : QueryFilterExpressionHelper.CombineExpressions(expression, multiTenantFilter); } return expression; } + + public virtual string GetCompiledQueryCacheKey() + { + return $"{CurrentTenantId?.ToString() ?? "Null"}{IsSoftDeleteFilterEnabled}:{IsMultiTenantFilterEnabled}"; + } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs new file mode 100644 index 00000000000..d9dc4cef9f0 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; + +namespace Volo.Abp.EntityFrameworkCore; + +public class AbpDbContextOptionsExtension : IDbContextOptionsExtension +{ + public void ApplyServices(IServiceCollection services) + { + var serviceDescriptor = services.FirstOrDefault(x => x.ServiceType == typeof(ICompiledQueryCacheKeyGenerator)); + if (serviceDescriptor != null && serviceDescriptor.ImplementationType != null) + { + services.Remove(serviceDescriptor); + services.AddScoped(serviceDescriptor.ImplementationType); + services.Add(ServiceDescriptor.Scoped(provider => + ActivatorUtilities.CreateInstance(provider, + provider.GetRequiredService(serviceDescriptor.ImplementationType) + .As()))); + } + + services.Replace(ServiceDescriptor.Scoped()); + services.AddSingleton(typeof(AbpEfCoreCurrentDbContext)); + } + + public void Validate(IDbContextOptions options) + { + } + + public DbContextOptionsExtensionInfo Info => new AbpOptionsExtensionInfo(this); + + private class AbpOptionsExtensionInfo : DbContextOptionsExtensionInfo + { + public AbpOptionsExtensionInfo(IDbContextOptionsExtension extension) + : base(extension) + { + } + + public override bool IsDatabaseProvider => false; + + public override int GetServiceProviderHashCode() + { + return 0; + } + + public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) + { + return other is AbpOptionsExtensionInfo; + } + + public override void PopulateDebugInfo(IDictionary debugInfo) + { + } + + public override string LogFragment => "AbpOptionsExtension"; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs new file mode 100644 index 00000000000..665757e17bf --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs @@ -0,0 +1,49 @@ +using System.Linq.Expressions; +using System.Threading; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; + +namespace Volo.Abp.EntityFrameworkCore; + +#pragma warning disable EF1001 +public class AbpEntityQueryProvider : EntityQueryProvider +{ + protected AbpEfCoreCurrentDbContext AbpEfCoreCurrentDbContext { get; } + protected ICurrentDbContext CurrentDbContext { get; } + + public AbpEntityQueryProvider( + IQueryCompiler queryCompiler, + AbpEfCoreCurrentDbContext abpEfCoreCurrentDbContext, + ICurrentDbContext currentDbContext) + : base(queryCompiler) + { + AbpEfCoreCurrentDbContext = abpEfCoreCurrentDbContext; + CurrentDbContext = currentDbContext; + } + + public override object Execute(Expression expression) + { + using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext)) + { + return base.Execute(expression); + } + } + + public override TResult Execute(Expression expression) + { + using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext)) + { + return base.Execute(expression); + } + } + + public override TResult ExecuteAsync(Expression expression, CancellationToken cancellationToken = new CancellationToken()) + { + using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext)) + { + return base.ExecuteAsync(expression, cancellationToken); + } + } +} +#pragma warning restore EF1001 diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs index 1b3a6f48b56..113a4aa86c2 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; using Volo.Abp.MultiTenancy; namespace Volo.Abp.EntityFrameworkCore.DependencyInjection; @@ -27,6 +28,11 @@ public static DbContextOptions Create(IServiceProvider s PreConfigure(options, context); Configure(options, context); + if (serviceProvider.GetRequiredService>().Value.UseDbFunction) + { + context.DbContextOptions.AddAbpDbContextOptionsExtension(); + } + return context.DbContextOptions.Options; } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs new file mode 100644 index 00000000000..e43ae1e04de --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query; + +namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; + +public class AbpCompiledQueryCacheKeyGenerator : ICompiledQueryCacheKeyGenerator +{ + protected ICompiledQueryCacheKeyGenerator InnerCompiledQueryCacheKeyGenerator { get; } + protected ICurrentDbContext CurrentContext { get; } + + public AbpCompiledQueryCacheKeyGenerator( + ICompiledQueryCacheKeyGenerator innerCompiledQueryCacheKeyGenerator, + ICurrentDbContext currentContext) + { + InnerCompiledQueryCacheKeyGenerator = innerCompiledQueryCacheKeyGenerator; + CurrentContext = currentContext; + } + + public virtual object GenerateCacheKey(Expression query, bool async) + { + var cacheKey = InnerCompiledQueryCacheKeyGenerator.GenerateCacheKey(query, async); + if (CurrentContext.Context is IAbpEfCoreDbFunctionContext abpEfCoreDbFunctionContext) + { + return new AbpCompiledQueryCacheKey(cacheKey, abpEfCoreDbFunctionContext.GetCompiledQueryCacheKey()); + } + + return cacheKey; + } + + private readonly struct AbpCompiledQueryCacheKey : IEquatable + { + private readonly object _compiledQueryCacheKey; + private readonly string _currentFilterCacheKey; + + public AbpCompiledQueryCacheKey(object compiledQueryCacheKey, string currentFilterCacheKey) + { + _compiledQueryCacheKey = compiledQueryCacheKey; + _currentFilterCacheKey = currentFilterCacheKey; + } + + public override bool Equals(object? obj) + { + return obj is AbpCompiledQueryCacheKey key && Equals(key); + } + + public bool Equals(AbpCompiledQueryCacheKey other) + { + return _compiledQueryCacheKey.Equals(other._compiledQueryCacheKey) && + _currentFilterCacheKey == other._currentFilterCacheKey; + } + + public override int GetHashCode() + { + return HashCode.Combine(_compiledQueryCacheKey, _currentFilterCacheKey); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs new file mode 100644 index 00000000000..c3a29b6d702 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading; + +namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; + +public class AbpEfCoreCurrentDbContext +{ + private readonly AsyncLocal _current = new AsyncLocal(); + + public IAbpEfCoreDbFunctionContext? Context => _current.Value; + + public IDisposable Use(IAbpEfCoreDbFunctionContext? context) + { + var previousValue = Context; + _current.Value = context; + return new DisposeAction(() => + { + _current.Value = previousValue; + }); + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs new file mode 100644 index 00000000000..e7d672e9ba1 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs @@ -0,0 +1,21 @@ +using System; +using System.Reflection; + +namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; + +public static class AbpEfCoreDataFilterDbFunctionMethods +{ + public static bool SoftDeleteFilter(bool isDeleted, bool boolParam) + { + throw new NotSupportedException("This method should be replaced by the database function call."); + } + + public static MethodInfo SoftDeleteFilterMethodInfo => typeof(AbpEfCoreDataFilterDbFunctionMethods).GetMethod(nameof(SoftDeleteFilter))!; + + public static bool MultiTenantFilter(Guid? tenantId, Guid? currentTenantId, bool boolParam) + { + throw new NotSupportedException("This method should be replaced by the database function call."); + } + + public static MethodInfo MultiTenantFilterMethodInfo => typeof(AbpEfCoreDataFilterDbFunctionMethods).GetMethod(nameof(MultiTenantFilter))!; +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs new file mode 100644 index 00000000000..180ae849e7d --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; + +public class AbpEfCoreGlobalFilterOptions +{ + public bool UseDbFunction { get; set; } + + public AbpEfCoreGlobalFilterOptions() + { + UseDbFunction = true; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs new file mode 100644 index 00000000000..85096e9e0a1 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; + +public interface IAbpEfCoreDbFunctionContext +{ + IAbpLazyServiceProvider LazyServiceProvider { get; set; } + + ICurrentTenant CurrentTenant { get; } + + IDataFilter DataFilter { get; } + + string GetCompiledQueryCacheKey(); +} diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs index 4fe3ae9c165..4e14dbc262d 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs @@ -70,7 +70,7 @@ private static SqliteConnection CreateDatabaseAndGetConnection() connection.Open(); using (var context = new AbpAuditingTestDbContext(new DbContextOptionsBuilder() - .UseSqlite(connection).Options)) + .UseSqlite(connection).AddAbpDbContextOptionsExtension().Options)) { context.GetService().CreateTables(); } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index 5e81aa1a7d9..a13aea29452 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -72,7 +72,7 @@ public override void ConfigureServices(ServiceConfigurationContext context) { options.Configure(abpDbContextConfigurationContext => { - abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection); + abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection).AddAbpDbContextOptionsExtension(); }); }); } @@ -101,7 +101,7 @@ private static SqliteConnection CreateDatabaseAndGetConnection() var connection = new AbpUnitTestSqliteConnection("Data Source=:memory:"); connection.Open(); - using (var context = new TestMigrationsDbContext(new DbContextOptionsBuilder().UseSqlite(connection).Options)) + using (var context = new TestMigrationsDbContext(new DbContextOptionsBuilder().UseSqlite(connection).AddAbpDbContextOptionsExtension().Options)) { context.GetService().CreateTables(); context.Database.ExecuteSqlRaw(