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 553f90f859..da1a47d779 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -957,8 +957,8 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) { - var softDeleteColumnName = entityTypeBuilder.Metadata.FindProperty(nameof(ISoftDelete.IsDeleted))?.GetColumnName() ?? "IsDeleted"; - expression = e => !IsSoftDeleteFilterEnabled || !EF.Property(e, softDeleteColumnName); + var softDeletePropertyName = entityTypeBuilder.Metadata.FindProperty(nameof(ISoftDelete.IsDeleted))?.Name ?? nameof(ISoftDelete.IsDeleted); + expression = e => !IsSoftDeleteFilterEnabled || !EF.Property(e, softDeletePropertyName); if (UseDbFunction()) { expression = e => AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilter(((ISoftDelete)e).IsDeleted, true); @@ -971,8 +971,8 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity))) { - var multiTenantColumnName = entityTypeBuilder.Metadata.FindProperty(nameof(IMultiTenant.TenantId))?.GetColumnName() ?? "TenantId"; - Expression> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property(e, multiTenantColumnName) == CurrentTenantId; + var multiTenantPropertyName = entityTypeBuilder.Metadata.FindProperty(nameof(IMultiTenant.TenantId))?.Name ?? nameof(IMultiTenant.TenantId); + Expression> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property(e, multiTenantPropertyName) == CurrentTenantId; if (UseDbFunction()) { multiTenantFilter = e => AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilter(((IMultiTenant)e).TenantId, CurrentTenantId, true); diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/AbpEntityFrameworkCoreTestModuleWithoutDbFunction.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/AbpEntityFrameworkCoreTestModuleWithoutDbFunction.cs new file mode 100644 index 0000000000..e4a0d14e28 --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/AbpEntityFrameworkCoreTestModuleWithoutDbFunction.cs @@ -0,0 +1,17 @@ +using Volo.Abp.EntityFrameworkCore.GlobalFilters; +using Volo.Abp.Modularity; + +namespace Volo.Abp.EntityFrameworkCore.DataFiltering; + +// Disables UseDbFunction so the EF.Property soft-delete filter path is exercised. +[DependsOn(typeof(AbpEntityFrameworkCoreTestModule))] +public class AbpEntityFrameworkCoreTestModuleWithoutDbFunction : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.UseDbFunction = false; + }); + } +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/MultiTenant_With_Custom_Column_Name_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/MultiTenant_With_Custom_Column_Name_Tests.cs new file mode 100644 index 0000000000..d5afbfcced --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/MultiTenant_With_Custom_Column_Name_Tests.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; +using Volo.Abp.TestApp.Domain; +using Volo.Abp.TestApp.EntityFrameworkCore; +using Volo.Abp.TestApp.Testing; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore.DataFiltering; + +public class MultiTenant_With_Custom_Column_Name_Tests : TestAppTestBase +{ + private readonly IRepository _repository; + private readonly IDataFilter _multiTenantFilter; + + public MultiTenant_With_Custom_Column_Name_Tests() + { + _repository = GetRequiredService>(); + _multiTenantFilter = GetRequiredService>(); + } + + [Fact] + public async Task MultiTenant_Filter_Should_Work_When_TenantId_Has_Custom_Column_Name() + { + var ctx = ServiceProvider.GetRequiredService(); + var tenantIdProperty = ctx.Model.FindEntityType(typeof(EntityWithCustomTenantIdColumn))! + .FindProperty(nameof(IMultiTenant.TenantId))!; + tenantIdProperty.GetColumnName().ShouldBe("custom_tenant_id_column"); + tenantIdProperty.Name.ShouldBe(nameof(IMultiTenant.TenantId)); + + using (_multiTenantFilter.Disable()) + { + await _repository.InsertAsync(new EntityWithCustomTenantIdColumn { Name = "host", TenantId = null }); + await _repository.InsertAsync(new EntityWithCustomTenantIdColumn { Name = "tenant", TenantId = Guid.NewGuid() }); + + var all = await _repository.GetListAsync(); + all.Count.ShouldBe(2); + } + + var hostScoped = await _repository.GetListAsync(); + hostScoped.Count.ShouldBe(1); + hostScoped[0].Name.ShouldBe("host"); + } +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/SoftDelete_With_Custom_Column_Name_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/SoftDelete_With_Custom_Column_Name_Tests.cs new file mode 100644 index 0000000000..0154eda6f3 --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/SoftDelete_With_Custom_Column_Name_Tests.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Domain; +using Volo.Abp.TestApp.EntityFrameworkCore; +using Volo.Abp.TestApp.Testing; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore.DataFiltering; + +public class SoftDelete_With_Custom_Column_Name_Tests : TestAppTestBase +{ + private readonly IRepository _repository; + private readonly IDataFilter _softDeleteFilter; + + public SoftDelete_With_Custom_Column_Name_Tests() + { + _repository = GetRequiredService>(); + _softDeleteFilter = GetRequiredService>(); + } + + [Fact] + public async Task SoftDelete_Filter_Should_Work_When_IsDeleted_Has_Custom_Column_Name() + { + var ctx = ServiceProvider.GetRequiredService(); + var isDeletedProperty = ctx.Model.FindEntityType(typeof(EntityWithCustomSoftDeleteColumn))! + .FindProperty(nameof(ISoftDelete.IsDeleted))!; + isDeletedProperty.GetColumnName().ShouldBe("custom_is_deleted_column"); + isDeletedProperty.Name.ShouldBe(nameof(ISoftDelete.IsDeleted)); + + await _repository.InsertAsync(new EntityWithCustomSoftDeleteColumn { Name = "kept" }); + await _repository.InsertAsync(new EntityWithCustomSoftDeleteColumn { Name = "removed", IsDeleted = true }); + + var visible = await _repository.GetListAsync(); + visible.Count.ShouldBe(1); + visible[0].Name.ShouldBe("kept"); + + using (_softDeleteFilter.Disable()) + { + var all = await _repository.GetListAsync(); + all.Count.ShouldBe(2); + all.ShouldContain(x => x.Name == "removed" && x.IsDeleted); + } + } +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs index 96f4b1a2e1..3aba18a42f 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs @@ -27,6 +27,10 @@ public class TestMigrationsDbContext : AbpDbContext public DbSet Categories { get; set; } + public DbSet EntityWithCustomSoftDeleteColumns { get; set; } + + public DbSet EntityWithCustomTenantIdColumns { get; set; } + public DbSet AppEntityWithNavigations { get; set; } public DbSet AppEntityWithNavigationChildOneToMany { get; set; } @@ -67,6 +71,17 @@ public class TestMigrationsDbContext : AbpDbContext base.OnModelCreating(modelBuilder); + // Mirror the column renames in TestAppDbContext so the generated SQLite schema matches. + modelBuilder.Entity(b => + { + b.Property(x => x.IsDeleted).HasColumnName("custom_is_deleted_column"); + }); + + modelBuilder.Entity(b => + { + b.Property(x => x.TenantId).HasColumnName("custom_tenant_id_column"); + }); + modelBuilder.Entity(b => { b.HasKey(p => new { p.PersonId, p.Number }); diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs index 2b95877bed..fb52b0f164 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs @@ -1,12 +1,14 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Volo.Abp.DependencyInjection; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.TestApp.FourthContext; using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext; +using Volo.Abp.MultiTenancy; using Volo.Abp.TestApp.Domain; using Volo.Abp.TestApp.Testing; @@ -33,6 +35,10 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, public DbSet Categories { get; set; } + public DbSet EntityWithCustomSoftDeleteColumns { get; set; } + + public DbSet EntityWithCustomTenantIdColumns { get; set; } + public DbSet AppEntityWithNavigations { get; set; } public DbSet AppEntityWithNavigationChildOneToMany { get; set; } @@ -176,4 +182,30 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, modelBuilder.TryConfigureObjectExtensions(); } + + // Renames IsDeleted / TenantId to a custom column and re-registers the global filter so the + // EF.Property path captures the new column name — covered by SoftDelete_With_Custom_Column_Name_Tests + // and MultiTenant_With_Custom_Column_Name_Tests. + protected override void ConfigureBaseProperties(ModelBuilder modelBuilder, IMutableEntityType mutableEntityType) + { + base.ConfigureBaseProperties(modelBuilder, mutableEntityType); + + if (typeof(EntityWithCustomSoftDeleteColumn).IsAssignableFrom(typeof(TEntity))) + { + modelBuilder.Entity() + .Property(nameof(ISoftDelete.IsDeleted)) + .HasColumnName("custom_is_deleted_column"); + + ConfigureGlobalFilters(modelBuilder, mutableEntityType, modelBuilder.Entity()); + } + + if (typeof(EntityWithCustomTenantIdColumn).IsAssignableFrom(typeof(TEntity))) + { + modelBuilder.Entity() + .Property(nameof(IMultiTenant.TenantId)) + .HasColumnName("custom_tenant_id_column"); + + ConfigureGlobalFilters(modelBuilder, mutableEntityType, modelBuilder.Entity()); + } + } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/EntityWithCustomSoftDeleteColumn.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/EntityWithCustomSoftDeleteColumn.cs new file mode 100644 index 0000000000..f352726dfa --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/EntityWithCustomSoftDeleteColumn.cs @@ -0,0 +1,11 @@ +using System; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.TestApp.Domain; + +public class EntityWithCustomSoftDeleteColumn : AggregateRoot, ISoftDelete +{ + public string Name { get; set; } + + public bool IsDeleted { get; set; } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/EntityWithCustomTenantIdColumn.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/EntityWithCustomTenantIdColumn.cs new file mode 100644 index 0000000000..484fd7e192 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/EntityWithCustomTenantIdColumn.cs @@ -0,0 +1,12 @@ +using System; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.TestApp.Domain; + +public class EntityWithCustomTenantIdColumn : AggregateRoot, IMultiTenant +{ + public string Name { get; set; } + + public Guid? TenantId { get; set; } +}