Browse Source

Merge remote-tracking branch 'origin/rel-10.5' into liming/fix-soft-delete-filter-property-name

# Conflicts:
#	framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
#	framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
pull/25568/head
maliming 4 months ago
parent
commit
0240f997ee
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 2
      docs/en/suite/customizing-the-generated-code.md
  2. 5
      docs/en/suite/editing-templates.md
  3. 2
      docs/en/suite/generating-crud-page.md
  4. 14
      docs/en/suite/index.md
  5. 4
      framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs
  6. 48
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/SoftDelete_DbFunction_Bool_Literal_Tests.cs
  7. 11
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  8. 11
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  9. 19
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/EntityWithIntSoftDelete.cs

2
docs/en/suite/customizing-the-generated-code.md

@ -84,6 +84,8 @@ As can be seen in the figure above, you can search the related template file, cl
### Customizations on Blazor UI ### Customizations on Blazor UI
> **Blazorise vs MudBlazor templates:** Suite ships two parallel template trees for Blazor — `Frontend.Blazor.*` (Blazorise) and `Frontend.Blazor.MudBlazor.*` (MudBlazor) — and picks the right one based on the [Blazor UI library](index.md#blazor-ui-library) detected in your solution. Customized templates are stored under `.suite/customized-templates/` keyed by the full resource name, so a customization made in a MudBlazor solution is never read by a Blazorise solution (or vice versa). If you migrate an existing solution from Blazorise to MudBlazor, you need to re-apply your customizations to the new resource names.
If you have created your application with Blazor UI, you can see the extended classes for _the code-behind classes of your pages_, under the `Pages` folder: If you have created your application with Blazor UI, you can see the extended classes for _the code-behind classes of your pages_, under the `Pages` folder:
![](../images/custom-code-blazor-folder-structure.png) ![](../images/custom-code-blazor-folder-structure.png)

5
docs/en/suite/editing-templates.md

@ -31,7 +31,10 @@ There's a naming convention for the template files.
* If the template name has `Server` prefix, it's used for backend code like repositories, application services, localizations, controllers, permissions, mappings, unit tests. * If the template name has `Server` prefix, it's used for backend code like repositories, application services, localizations, controllers, permissions, mappings, unit tests.
* If the template name has `Frontend.Angular` prefix, it's used for Angular code generation. The Angular code is being generated via [Angular Schematics](https://angular.dev/tools/cli/schematics). * If the template name has `Frontend.Angular` prefix, it's used for Angular code generation. The Angular code is being generated via [Angular Schematics](https://angular.dev/tools/cli/schematics).
* If the template name has `Frontend.Mvc` prefix, it's used for razor pages, menus, JavaScript, CSS files. * If the template name has `Frontend.Mvc` prefix, it's used for razor pages, menus, JavaScript, CSS files.
* If the template name has `Frontend.Blazor` prefix, it's used for razor components. * If the template name has `Frontend.Blazor` prefix (no `.MudBlazor` segment), it's used for Blazorise-based razor components.
* If the template name has `Frontend.Blazor.MudBlazor` prefix, it's used for [MudBlazor](https://mudblazor.com)-based razor components.
> The template list only shows the variant that matches your solution's [Blazor UI library](index.md#blazor-ui-library) (Blazorise or MudBlazor), so you never see both trees side-by-side and the "outdated" check only flags the active set.
## How do I edit the templates? ## How do I edit the templates?

2
docs/en/suite/generating-crud-page.md

@ -112,6 +112,8 @@ To create a new entity, make sure the *-New entity-* is selected in the **Entity
> **Note:** This option does not generate React UI pages. ABP Suite's UI generation targets Suite-supported UI stacks such as MVC, Blazor and Angular. For React-based solutions, build the UI in the source-owned [React UI](../framework/ui/react/index.md) application. ABP license holders can use [ABP Studio AI Agent](https://abp.io/studio/ai-agent) with predefined AI credits to generate React pages more easily. > **Note:** This option does not generate React UI pages. ABP Suite's UI generation targets Suite-supported UI stacks such as MVC, Blazor and Angular. For React-based solutions, build the UI in the source-owned [React UI](../framework/ui/react/index.md) application. ABP license holders can use [ABP Studio AI Agent](https://abp.io/studio/ai-agent) with predefined AI credits to generate React pages more easily.
> **Blazor UI library:** For Blazor solutions, the generated UI matches the underlying Blazor component library. Solutions that reference `Volo.Abp.MudBlazorUI` produce pages built around `MudDataGrid`, `MudDialog`, `MudForm` and the rest of the [MudBlazor](https://mudblazor.com) controls; Blazorise-based solutions keep emitting the original `DataGrid` / `Modal` / `Validations` markup. ABP Suite picks the variant automatically by scanning the Blazor project `.csproj` and shows the resolved value in the solution info tooltip — no extra switch is required. See the [Blazor UI library](index.md#blazor-ui-library) section for details.
* **Excel export**: Creates a button that exports a list of all the data that were added to the entity to an Excel file. * **Excel export**: Creates a button that exports a list of all the data that were added to the entity to an Excel file.
* **Bulk delete**: Performs bulk deletion of records based on specified criteria. * **Bulk delete**: Performs bulk deletion of records based on specified criteria.

14
docs/en/suite/index.md

@ -26,3 +26,17 @@ ABP Suite is a complementary tool to the ABP Platform. ABP Suite allows you to b
It's a .NET Core Global tool that can be installed from the command line. If you are using [ABP Studio](../studio/index.md), you don't even need to install it because it should already be installed, when you first installed the [ABP Studio](../studio/index.md). It's a .NET Core Global tool that can be installed from the command line. If you are using [ABP Studio](../studio/index.md), you don't even need to install it because it should already be installed, when you first installed the [ABP Studio](../studio/index.md).
By using the ABP Suite, you can generate CRUD pages from the database to the front-end and directly get a kickstart for your application. ABP Suite is actively developed and new features are being added version by version according to the roadmap and your feedback. By using the ABP Suite, you can generate CRUD pages from the database to the front-end and directly get a kickstart for your application. ABP Suite is actively developed and new features are being added version by version according to the roadmap and your feedback.
## Blazor UI library
When the target solution uses any Blazor UI framework (Blazor, BlazorServer, BlazorWebApp or MAUIBlazor), ABP Suite detects the underlying Blazor component library by scanning the Blazor project `.csproj` for known package references:
* A reference to `Volo.Abp.MudBlazorUI`, `Volo.Abp.AspNetCore.Components.*.Theming.MudBlazor` or `*.Blazor.MudBlazor*` selects the **MudBlazor** templates.
* Otherwise ABP Suite falls back to the **Blazorise** templates that have always shipped with Suite.
The detected value is shown in the *solution info* tooltip of the CRUD Page Generator screen and is reused for two things:
1. Template routing — `Frontend.Blazor.*` resource names are resolved to `Frontend.Blazor.MudBlazor.*` for MudBlazor solutions, so the generated `.razor` / `.razor.cs` files use `MudCard`, `MudDataGrid`, `MudDialog`, `MudForm` and the corresponding Mud input controls instead of the Blazorise `Card`, `DataGrid`, `Modal`, `Validations` markup.
2. Template Management UI — the "Manage Templates" screen and the "outdated templates" check only list the variant that matches the current solution, so the Blazorise and MudBlazor template trees never appear side-by-side.
Customized templates are stored under `.suite/customized-templates/` keyed by the full resource name, so MudBlazor customizations (`Frontend.Blazor.MudBlazor.*`) are physically isolated from Blazorise customizations (`Frontend.Blazor.*`) and never clash.

4
framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs

@ -21,11 +21,11 @@ public static class AbpEfCoreModelBuilderExtensions
if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<ISoftDelete>() == true) if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<ISoftDelete>() == true)
{ {
// IsDeleted == false // IsDeleted == false. Use the column's TypeMapping so any ValueConverter is applied to the literal.
return new SqlBinaryExpression( return new SqlBinaryExpression(
ExpressionType.Equal, ExpressionType.Equal,
isDeleted, isDeleted,
new SqlConstantExpression(false, typeof(bool), boolParam.TypeMapping), new SqlConstantExpression(false, typeof(bool), isDeleted.TypeMapping ?? boolParam.TypeMapping),
boolParam.Type, boolParam.Type,
boolParam.TypeMapping); boolParam.TypeMapping);
} }

48
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/SoftDelete_DbFunction_Bool_Literal_Tests.cs

@ -0,0 +1,48 @@
using System;
using System.Linq;
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;
// Regression for the soft-delete DbFunction translator hardcoding a bool false literal
// with the bool TypeMapping instead of routing the literal through the IsDeleted
// property's ValueConverter. EntityWithIntSoftDelete maps false->5, true->9, so the
// buggy SQL "is_deleted = 0" matches no row (returns 0) while the correct SQL
// "is_deleted = 5" matches the not-deleted row (returns 1).
public class SoftDelete_DbFunction_Bool_Literal_Tests : TestAppTestBase<AbpEntityFrameworkCoreTestModule>
{
private readonly IRepository<EntityWithIntSoftDelete, Guid> _repository;
private readonly IDataFilter<ISoftDelete> _softDeleteFilter;
public SoftDelete_DbFunction_Bool_Literal_Tests()
{
_repository = GetRequiredService<IRepository<EntityWithIntSoftDelete, Guid>>();
_softDeleteFilter = GetRequiredService<IDataFilter<ISoftDelete>>();
}
[Fact]
public async Task SoftDelete_Filter_Should_Route_False_Literal_Through_ValueConverter()
{
await _repository.InsertAsync(new EntityWithIntSoftDelete { Name = "kept" });
await _repository.InsertAsync(new EntityWithIntSoftDelete { 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);
}
}
}

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

@ -31,6 +31,8 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
public DbSet<EntityWithCustomTenantIdColumn> EntityWithCustomTenantIdColumns { get; set; } public DbSet<EntityWithCustomTenantIdColumn> EntityWithCustomTenantIdColumns { get; set; }
public DbSet<EntityWithIntSoftDelete> EntityWithIntSoftDeletes { get; set; }
public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; } public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; }
public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; } public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; }
@ -119,6 +121,15 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
b.HasAbpQueryFilter(e => e.Name.StartsWith("abp")); b.HasAbpQueryFilter(e => e.Name.StartsWith("abp"));
}); });
modelBuilder.Entity<EntityWithIntSoftDelete>(b =>
{
b.Property(x => x.IsDeleted)
.HasColumnName(EntityWithIntSoftDelete.IsDeletedColumnName)
.HasConversion(
v => v ? EntityWithIntSoftDelete.DeletedProviderValue : EntityWithIntSoftDelete.NotDeletedProviderValue,
i => i == EntityWithIntSoftDelete.DeletedProviderValue);
});
modelBuilder.Entity<AppEntityWithNavigations>(b => modelBuilder.Entity<AppEntityWithNavigations>(b =>
{ {
b.ConfigureByConvention(); b.ConfigureByConvention();

11
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs

@ -39,6 +39,8 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
public DbSet<EntityWithCustomTenantIdColumn> EntityWithCustomTenantIdColumns { get; set; } public DbSet<EntityWithCustomTenantIdColumn> EntityWithCustomTenantIdColumns { get; set; }
public DbSet<EntityWithIntSoftDelete> EntityWithIntSoftDeletes { get; set; }
public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; } public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; }
public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; } public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; }
@ -135,6 +137,15 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
b.HasAbpQueryFilter(e => e.Name.StartsWith("abp")); b.HasAbpQueryFilter(e => e.Name.StartsWith("abp"));
}); });
modelBuilder.Entity<EntityWithIntSoftDelete>(b =>
{
b.Property(x => x.IsDeleted)
.HasColumnName(EntityWithIntSoftDelete.IsDeletedColumnName)
.HasConversion(
v => v ? EntityWithIntSoftDelete.DeletedProviderValue : EntityWithIntSoftDelete.NotDeletedProviderValue,
i => i == EntityWithIntSoftDelete.DeletedProviderValue);
});
modelBuilder.Entity<AppEntityWithNavigations>(b => modelBuilder.Entity<AppEntityWithNavigations>(b =>
{ {
b.ConfigureByConvention(); b.ConfigureByConvention();

19
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/EntityWithIntSoftDelete.cs

@ -0,0 +1,19 @@
using System;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.TestApp.Domain;
// Uses a custom bool<->int converter where false maps to a non-zero value, so the
// soft-delete DbFunction translator (which hardcodes a bool false literal with the
// bool TypeMapping) is observably wrong on every provider: the generated SQL
// compares against 0/FALSE instead of the converter's actual provider value (5).
public class EntityWithIntSoftDelete : AggregateRoot<Guid>, ISoftDelete
{
public const string IsDeletedColumnName = "is_deleted";
public const int NotDeletedProviderValue = 5;
public const int DeletedProviderValue = 9;
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
Loading…
Cancel
Save