Browse Source

Use CLR property name in soft-delete and multi-tenant filters

pull/25568/head
maliming 2 months ago
parent
commit
d373e15612
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 8
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  2. 17
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/AbpEntityFrameworkCoreTestModuleWithoutDbFunction.cs
  3. 49
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/MultiTenant_With_Custom_Column_Name_Tests.cs
  4. 49
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/SoftDelete_With_Custom_Column_Name_Tests.cs
  5. 15
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  6. 32
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  7. 11
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/EntityWithCustomSoftDeleteColumn.cs
  8. 12
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/EntityWithCustomTenantIdColumn.cs

8
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -957,8 +957,8 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
var softDeleteColumnName = entityTypeBuilder.Metadata.FindProperty(nameof(ISoftDelete.IsDeleted))?.GetColumnName() ?? "IsDeleted";
expression = e => !IsSoftDeleteFilterEnabled || !EF.Property<bool>(e, softDeleteColumnName);
var softDeletePropertyName = entityTypeBuilder.Metadata.FindProperty(nameof(ISoftDelete.IsDeleted))?.Name ?? nameof(ISoftDelete.IsDeleted);
expression = e => !IsSoftDeleteFilterEnabled || !EF.Property<bool>(e, softDeletePropertyName);
if (UseDbFunction())
{
expression = e => AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilter(((ISoftDelete)e).IsDeleted, true);
@ -971,8 +971,8 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
var multiTenantColumnName = entityTypeBuilder.Metadata.FindProperty(nameof(IMultiTenant.TenantId))?.GetColumnName() ?? "TenantId";
Expression<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property<Guid>(e, multiTenantColumnName) == CurrentTenantId;
var multiTenantPropertyName = entityTypeBuilder.Metadata.FindProperty(nameof(IMultiTenant.TenantId))?.Name ?? nameof(IMultiTenant.TenantId);
Expression<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property<Guid>(e, multiTenantPropertyName) == CurrentTenantId;
if (UseDbFunction())
{
multiTenantFilter = e => AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilter(((IMultiTenant)e).TenantId, CurrentTenantId, true);

17
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<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = false;
});
}
}

49
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<AbpEntityFrameworkCoreTestModuleWithoutDbFunction>
{
private readonly IRepository<EntityWithCustomTenantIdColumn, Guid> _repository;
private readonly IDataFilter<IMultiTenant> _multiTenantFilter;
public MultiTenant_With_Custom_Column_Name_Tests()
{
_repository = GetRequiredService<IRepository<EntityWithCustomTenantIdColumn, Guid>>();
_multiTenantFilter = GetRequiredService<IDataFilter<IMultiTenant>>();
}
[Fact]
public async Task MultiTenant_Filter_Should_Work_When_TenantId_Has_Custom_Column_Name()
{
var ctx = ServiceProvider.GetRequiredService<TestAppDbContext>();
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");
}
}

49
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<AbpEntityFrameworkCoreTestModuleWithoutDbFunction>
{
private readonly IRepository<EntityWithCustomSoftDeleteColumn, Guid> _repository;
private readonly IDataFilter<ISoftDelete> _softDeleteFilter;
public SoftDelete_With_Custom_Column_Name_Tests()
{
_repository = GetRequiredService<IRepository<EntityWithCustomSoftDeleteColumn, Guid>>();
_softDeleteFilter = GetRequiredService<IDataFilter<ISoftDelete>>();
}
[Fact]
public async Task SoftDelete_Filter_Should_Work_When_IsDeleted_Has_Custom_Column_Name()
{
var ctx = ServiceProvider.GetRequiredService<TestAppDbContext>();
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);
}
}
}

15
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs

@ -27,6 +27,10 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
public DbSet<Category> Categories { get; set; }
public DbSet<EntityWithCustomSoftDeleteColumn> EntityWithCustomSoftDeleteColumns { get; set; }
public DbSet<EntityWithCustomTenantIdColumn> EntityWithCustomTenantIdColumns { get; set; }
public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; }
public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; }
@ -67,6 +71,17 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
base.OnModelCreating(modelBuilder);
// Mirror the column renames in TestAppDbContext so the generated SQLite schema matches.
modelBuilder.Entity<EntityWithCustomSoftDeleteColumn>(b =>
{
b.Property(x => x.IsDeleted).HasColumnName("custom_is_deleted_column");
});
modelBuilder.Entity<EntityWithCustomTenantIdColumn>(b =>
{
b.Property(x => x.TenantId).HasColumnName("custom_tenant_id_column");
});
modelBuilder.Entity<Phone>(b =>
{
b.HasKey(p => new { p.PersonId, p.Number });

32
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<TestAppDbContext>, IThirdDbContext,
public DbSet<Category> Categories { get; set; }
public DbSet<EntityWithCustomSoftDeleteColumn> EntityWithCustomSoftDeleteColumns { get; set; }
public DbSet<EntityWithCustomTenantIdColumn> EntityWithCustomTenantIdColumns { get; set; }
public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; }
public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; }
@ -176,4 +182,30 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
modelBuilder.TryConfigureObjectExtensions<TestAppDbContext>();
}
// 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<TEntity>(ModelBuilder modelBuilder, IMutableEntityType mutableEntityType)
{
base.ConfigureBaseProperties<TEntity>(modelBuilder, mutableEntityType);
if (typeof(EntityWithCustomSoftDeleteColumn).IsAssignableFrom(typeof(TEntity)))
{
modelBuilder.Entity<TEntity>()
.Property(nameof(ISoftDelete.IsDeleted))
.HasColumnName("custom_is_deleted_column");
ConfigureGlobalFilters<TEntity>(modelBuilder, mutableEntityType, modelBuilder.Entity<TEntity>());
}
if (typeof(EntityWithCustomTenantIdColumn).IsAssignableFrom(typeof(TEntity)))
{
modelBuilder.Entity<TEntity>()
.Property(nameof(IMultiTenant.TenantId))
.HasColumnName("custom_tenant_id_column");
ConfigureGlobalFilters<TEntity>(modelBuilder, mutableEntityType, modelBuilder.Entity<TEntity>());
}
}
}

11
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<Guid>, ISoftDelete
{
public string Name { get; set; }
public bool IsDeleted { get; set; }
}

12
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<Guid>, IMultiTenant
{
public string Name { get; set; }
public Guid? TenantId { get; set; }
}
Loading…
Cancel
Save