Hi,
Confirmed it's a bug in Volo.Payment.Admin.Web.Menus.PaymentAdminMenuContributor — the .RequirePermissions(...) calls end up on the parent menu instead of the children, so removing one permission hides the whole group. Will be fixed in the next release.
For now, drop this into your Web project:
public class FixedPaymentAdminMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if (context.Menu.Name != StandardMenus.Main) return;
var l = context.GetLocalizer<PaymentResource>();
var paymentMenu = new ApplicationMenuItem(
PaymentAdminMenus.GroupName,
l["Menu:PaymentManagement"],
icon: "fa fa-money-check");
context.Menu.GetAdministration().AddItem(paymentMenu);
paymentMenu.AddItem(new ApplicationMenuItem(
PaymentAdminMenus.Plans.PlansMenu, l["Menu:Plans"].Value, "/Payment/Plans", "fa fa-file-alt")
.RequirePermissions(PaymentAdminPermissions.Plans.Default));
paymentMenu.AddItem(new ApplicationMenuItem(
PaymentAdminMenus.PaymentRequests.PaymentRequestsMenu, l["Menu:PaymentRequests"].Value, "/Payment/Requests", "fa fa-exchange")
.RequirePermissions(PaymentAdminPermissions.PaymentRequests.Default));
await Task.CompletedTask;
}
}
Then in your YourWebModule.ConfigureServices:
Configure<AbpNavigationOptions>(options =>
{
options.MenuContributors.RemoveAll(c => c is Volo.Payment.Admin.Web.Menus.PaymentAdminMenuContributor);
options.MenuContributors.Add(new FixedPaymentAdminMenuContributor());
});
Thanks.
Hi thanhvl1,
To narrow this down, could you check a few things after switching to fti?
Decode the access token. Open DevTools (F12) → Network → pick any authenticated API request after the switch → copy the Authorization: Bearer <token> value → paste it at https://jwt.io. What does the tenant_id (or tenantid) claim show?
Database setup. Are fti / tff configured as shared database or database-per-tenant?
(If per-tenant) Run this on the fti tenant database (not Host):
SELECT Id, Email, TenantId
FROM AbpUsers
WHERE Email = 'thanhvl1@aaa.com';
Once we have these, we can pinpoint where the list is coming from.
Thanks!
Hi,
初始化能跑通就好。新插入数据查不到,我现在信息不够下不了结论,麻烦你把下面几项贴出来一起看:
EF Core SQL 日志。把日志级别开到 Information:
"Logging": {
"LogLevel": {
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
再调一次 swagger 的 Create 接口,把这次调用过程中控制台/日志里输出的所有内容贴出来(重点看:有没有 INSERT INTO ...、INSERT 的表名是带后缀的分表还是无后缀的主表、有没有被吞掉的 exception/warning)。
_spcAutoAnalysesManager.CreateAsync(...) 的实现代码。看 manager 内部到底做了什么、有没有把实体加进仓储。
CreateAsync 应用服务方法的 UoW 配置。是用 ABP 默认的自动 UoW,还是手动加了 [UnitOfWork] / [UnitOfWork(IsDisabled = true)] / IsTransactional = false 之类的属性?同时贴一下这个应用服务类的基类是什么。
你这个项目是不是多租户(启用了 AbpMultiTenancyOptions.IsEnabled = true)。如果是多租户,连接串路由还会和 ShardingCore 的 IVirtualDataSource 再冲突一次,需要单独处理。
完整的 AppModule(或 SpcServiceModule)的 ConfigureServices 代码,特别是 Configure<AbpDbContextOptions> 和 AddShardingDbContext<...>().ReplaceService<IDbContextCreator, ...>()...AddShardingCore() 这两段。确认两处改造都按之前给的样例落地了,没漏掉哪一行。
另外,你第 4 张截图看的是主表 tblSpcAutoAnalyses,里面 CreationTime 最新只到 2026-05-11,应该是分表配置生效前的历史数据,跟这次插入无关。分表生效后新数据应该出现在 tblSpcAutoAnalyses_yyyyMM 那些分表里。
如果方便的话,直接打包一个能复现这个现象的最小测试项目(去掉敏感代码,留下 DbContext / 实体 / 路由 / 应用服务 / Module 配置 + 一个 README 写清复现步骤)发给我们更快,比一条条贴信息高效很多。打包发到 WeTransfer 之类的临时分享平台,把链接贴在这里就行。
把以上信息(或最小复现项目)发上来后再继续诊断。
Thanks
hi
We haven't given up on Blazorise. We're just supporting both Blazorise and MudBlazor in Blazor at the same time. We might support other Blazor libraries in the future.
We'll continue to provide Blazorise licenses for ABP commercial users.
Thanks for your attention.
Hi,
两个问题分别说明下:
AbpAwareDbContextCreator 找不到引用这是需要你自己新建的一个类文件,不是 ShardingCore 自带的,IDE 当然引用不到。把上一条回复里那段 public class AbpAwareDbContextCreator<TShardingDbContext> : IDbContextCreator { ... } 整段贴到你项目里一个新的 .cs 文件(比如 AbpAwareDbContextCreator.cs),namespace 改成你项目自己的,加上下面这些 using 就能编译:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ShardingCore.Core.DbContextCreator;
using ShardingCore.Core.ServiceProviders;
using ShardingCore.Exceptions;
using ShardingCore.Sharding.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
注意类名是 AbpAwareDbContextCreator(中间的 Context 是大写 C,你截图里写成了小写 c),编译器对大小写敏感。
ReplaceService 那行不能去掉泛型参数,必须写成:
.ReplaceService<IDbContextCreator, AbpAwareDbContextCreator<SpcServiceDbContext>>()
AbpAwareDbContextCreator<T> 是开放泛型,必须显式闭合到 SpcServiceDbContext。
Autofac.Core.DependencyResolutionException这是 Autofac 在激活 IShardingRuntimeContext<SpcServiceDbContext> 时内部抛了别的异常被包装出来了,截图里看到的只是外层信息,真正的根因在 InnerException 里。麻烦你按下面任一种方式把内层异常贴出来:
InnerException 的 Message 和 StackTrace 复制出来;另外有个关键点:你截图里 UseShardingQuery((conn, builder) => builder.UseMySql(conn)) 用了小写的 UseMySql —— 那是 Pomelo.EntityFrameworkCore.MySql 的扩展方法。但 ABP 9.3.1 的 Volo.Abp.EntityFrameworkCore.MySQL 模块依赖的是 Oracle 官方 MySql.EntityFrameworkCore(扩展方法是大写 UseMySQL),两个 provider 不能混用。如果你项目里同时引了 Pomelo 和 Oracle 两个 provider,ShardingCore 内部初始化时就会撞你截图里那个 Autofac.Core.DependencyResolutionException。
正确写法应该统一用 ABP 那个 provider:
o.UseShardingQuery((conn, builder) =>
Microsoft.EntityFrameworkCore.MySQLDbContextOptionsExtensions.UseMySQL(builder, conn));
o.UseShardingTransaction((conn, builder) =>
Microsoft.EntityFrameworkCore.MySQLDbContextOptionsExtensions.UseMySQL(builder, conn));
(用全路径是因为 ABP Volo.Abp.EntityFrameworkCore 命名空间里也有同名的 UseMySQL 扩展,避免歧义。)
我这边按你的配置(ABP 9.3.1 + ShardingCore 7.9.2.1 + MySQL 8)搭了个最小可运行项目并完整跑通:启动时自动建出 Orders_202512 ~ Orders_202605 共 6 张月度表,INSERT 3 条不同月份记录会落到正确的物理表,跨月 SELECT 能 union 出 3 行。完整项目 zip 可以从这里下载(WeTransfer 临时链接,注意有有效期):
https://we.tl/t-mJncvNtn6AdkiLWh
解压后 dotnet run 即可对照。zip 里 README.md 有完整说明 + Docker 起 MySQL 命令。如果还有问题再贴 InnerException 详情。
Thanks
Hi thanhvl1,
One more question — the Switch Tenant modal in your screenshots looks like it might not be our default one. Did you customize the modal, or are you using a community / third-party UI?
If yes, could you share the relevant code (or at least which API it calls to load the tenant list)? That'll help us trace where the displayed list comes from.
Thanks!
Hi thanhvl1,
The Switch Tenant modal lists all tenant associations the current user has on the Host side. In Shared User Accounts mode, AbpUsers on the Host database uses three values for TenantId:
Guid.Empty — the user's global root record (filtered out)NULL — Host association (rendered as "Host" in the modal)<tenant guid> — a tenant the user has joinedIf the user has a row with TenantId IS NULL, the modal shows a "Host" entry that lets you switch back to Host from any tenant. The fact that "Host" doesn't appear in your screenshot suggests this user has no Host association — they were only invited into fti / tff.
To help me confirm, could you share a few details:
fti / tff.)SELECT Id, UserName, Email, TenantId
FROM AbpUsers
WHERE Email = '<user email here>';
Most likely outcome — if there's no row with TenantId IS NULL, a Host admin can invite that user to Host through the standard invitation flow (open the invite dialog while the current tenant is Host). After acceptance, the Host association is created and "Host" will appear in the Switch Tenant modal.
Thanks!
Hi,
ShardingCore 是第三方库,和 ABP 的 AbpDbContext 在 DI 注册和 DbContextOptions 构造上有几处时序冲突,我这边帮你定位并跑通了"启动建表 + 分表 INSERT/SELECT"的基本场景,下面给一份方案你可以照着试试。需要提醒的是,第三方库与 ABP 深度集成的场景有可能我们后续也很难持续支持——比如 ABP 多租户连接串切换、事件 Inbox/Outbox、IUnitOfWork 嵌套事务等情况,遇到时可能需要你自己根据 ShardingCore 行为再做调整。
你的报错 not found logic table name 来自 ShardingCore.EFCores.ShardingModelCustomizer.MappingToTable —— 它读 EntityMetadata.LogicTableName 拿到空字符串。再往上追:
LogicTableName 只在 ShardingDbSetInitializer.InitializeSets 内、并且 context.IsShellDbContext() == true 时才会被填充。IsShellDbContext() 判断的是 DbContextOptions 里有没有 ShardingWrapOptionsExtension。UseSharding(...) 加进去,而 services.AddShardingDbContext<T>() 内部用的是 EF Core 的 AddDbContext(TryAdd 注册 DbContextOptions<T>)。services.TryAddTransient(DbContextOptionsFactory.Create<TDbContext>) 注册了 DbContextOptions<TDbContext>,所以 ShardingCore 这边的 TryAdd 被跳过,最终 DbContextOptions<AppDbContext> 是 ABP 工厂产出的,不含 ShardingCore 任何扩展。IsShellDbContext() == false → metadata 永远不被初始化 → LogicTableName 永远是空。所以你日志里 table :_202605 maybe created. 前面空白就是 LogicTableName 没拿到。
DbContextOptions 也加 UseSharding在你的 ABP 模块 ConfigureServices 里:
Configure<AbpDbContextOptions>(opts =>
{
opts.PreConfigure<SpcServiceDbContext>(ctx =>
{
var src = ctx.ServiceProvider
.GetRequiredService<IShardingRuntimeContext<SpcServiceDbContext>>();
ctx.DbContextOptions.UseSharding(src); // 关键:补上 ShardingWrapOptionsExtension、ReplaceService<IDbSetInitializer> 等
});
opts.UseSqlServer();
});
只做这一步,LogicTableName 就能正确填充、启动建表能成功;但你写数据时还会撞下一个错(AbpDbContext.SaveChangesAsync 里 AbpEfCoreNavigationHelper NRE),原因是 ShardingCore 反射 new 内部分表 DbContext 时绕开了 ABP 属性注入。需要改造 2。
IDbContextCreator让分表内部 DbContext 走 ABP DI 构造,并把 LazyServiceProvider 手动补回去:
public class AbpAwareDbContextCreator<TShardingDbContext> : IDbContextCreator
where TShardingDbContext : DbContext, IShardingDbContext
{
public DbContext CreateDbContext(DbContext shellDbContext, ShardingDbContextOptions shardingDbContextOptions)
{
var lazySp = ((TShardingDbContext)shellDbContext)
.As<AbpDbContext<TShardingDbContext>>().LazyServiceProvider;
var sp = lazySp.LazyGetRequiredService<IServiceProvider>();
var dbContext = (TShardingDbContext)ActivatorUtilities.CreateInstance(
sp, typeof(TShardingDbContext), shardingDbContextOptions.DbContextOptions);
// ActivatorUtilities 只做参数注入,不做属性注入;手动补 LazyServiceProvider
dbContext.As<AbpDbContext<TShardingDbContext>>().LazyServiceProvider =
sp.GetRequiredService<IAbpLazyServiceProvider>();
if (dbContext is IShardingTableDbContext s && s.RouteTail == null)
s.RouteTail = shardingDbContextOptions.RouteTail;
_ = dbContext.Model;
return dbContext;
}
public DbContext GetShellDbContext(IShardingProvider shardingProvider)
{
try { return shardingProvider.GetService<TShardingDbContext>(); }
catch (Exception ex)
{
throw new ShardingCoreInvalidOperationException(
$"cant get shell db context, override {nameof(IDbContextCreator)}.{nameof(IDbContextCreator.GetShellDbContext)}", ex);
}
}
}
然后在 services.AddShardingDbContext<...>() 链上加一句 ReplaceService<IDbContextCreator, ...>():
services.AddShardingDbContext<SpcServiceDbContext>()
.ReplaceService<IDbContextCreator, AbpAwareDbContextCreator<SpcServiceDbContext>>()
.UseRouteConfig(o => { o.AddShardingTableRoute<SpcAutoAnalysesMonthRoute>(); })
.UseConfig(o =>
{
o.UseShardingQuery((conn, builder) => builder.UseSqlServer(conn));
o.UseShardingTransaction((conn, builder) => builder.UseSqlServer(conn));
o.AddDefaultDataSource("ds0", connectionString);
})
.AddShardingCore();
ShardingAbpDbContext 基类里两个可以删掉的过时代码ShardingCore 7.x 的 IShardingTableDbContext 接口只剩 RouteTail 一个属性,下面两行运行时已经没人读:
public IShardingRuntimeContext ShardingRuntimeContext { get; set; } // 可删
public IShardingTableDbContext ShardingTableContext { get => this; set { } } // 可删
另外 GetShardingExecutor() 建议参考 ShardingCore 官方 AbstractShardingDbContext 的写法,缓存而不是每次 new:
private IShardingDbContextExecutor _executor;
public IShardingDbContextExecutor GetShardingExecutor()
=> _executor ??= this.CreateShardingDbContextExecutor(); // 仅 Shell DbContext 返回非 null
我在本地搭了个 ABP 9.3.1 + ShardingCore 7.9.2.1 + SQL Server 的最小项目实测,启动时按月自动建出 Orders_202512 ~ Orders_202605,INSERT 三条不同月的记录分别落到对应物理表,跨月 SELECT 能正确 union 出 3 行。完整代码可以参考下面这些文件直接拷贝试:
Order.cs:
using System.ComponentModel.DataAnnotations.Schema;
using Volo.Abp.Domain.Entities;
[Table("Orders")]
public class Order : Entity<Guid>
{
public string Code { get; set; }
public DateTime CreationTime { get; set; }
}
OrderMonthRoute.cs:
using ShardingCore.Core.EntityMetadatas;
using ShardingCore.VirtualRoutes.Months;
public class OrderMonthRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>
{
public override DateTime GetBeginTime() => new DateTime(2025, 12, 1);
public override void Configure(EntityMetadataTableBuilder<Order> builder)
=> builder.ShardingProperty(o => o.CreationTime);
public override bool AutoCreateTableByTime() => true;
}
AppDbContext.cs:
using Microsoft.EntityFrameworkCore;
using ShardingCore.Core.VirtualRoutes.TableRoutes.RouteTails.Abstractions;
using ShardingCore.Extensions;
using ShardingCore.Sharding.Abstractions;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
[ConnectionStringName("Default")]
public class AppDbContext : AbpDbContext<AppDbContext>, IShardingDbContext, IShardingTableDbContext
{
public DbSet<Order> Orders { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public IRouteTail RouteTail { get; set; }
private IShardingDbContextExecutor _executor;
public IShardingDbContextExecutor GetShardingExecutor()
=> _executor ??= this.CreateShardingDbContextExecutor();
}
AppModule.cs:
using Microsoft.Extensions.DependencyInjection;
using ShardingCore;
using ShardingCore.Core.DbContextCreator;
using ShardingCore.Core.RuntimeContexts;
using Volo.Abp;
using Volo.Abp.Autofac;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.SqlServer;
using Volo.Abp.Modularity;
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpEntityFrameworkCoreSqlServerModule)
)]
public class AppModule : AbpModule
{
public const string Cs = "Server=...;Database=...;User Id=...;Password=...;TrustServerCertificate=True;";
public override void ConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
Configure<AbpDbConnectionOptions>(opts => { opts.ConnectionStrings.Default = Cs; });
services.AddAbpDbContext<AppDbContext>(options =>
{
options.AddDefaultRepositories(includeAllEntities: true);
});
// 改造 1
Configure<AbpDbContextOptions>(opts =>
{
opts.PreConfigure<AppDbContext>(ctx =>
{
var src = ctx.ServiceProvider.GetRequiredService<IShardingRuntimeContext<AppDbContext>>();
ctx.DbContextOptions.UseSharding(src);
});
opts.UseSqlServer();
});
// 改造 2
services.AddShardingDbContext<AppDbContext>()
.ReplaceService<IDbContextCreator, AbpAwareDbContextCreator<AppDbContext>>()
.UseRouteConfig(o => { o.AddShardingTableRoute<OrderMonthRoute>(); })
.UseConfig(o =>
{
o.ThrowIfQueryRouteNotMatch = false;
o.UseShardingQuery((conn, builder) => builder.UseSqlServer(conn));
o.UseShardingTransaction((conn, builder) => builder.UseSqlServer(conn));
o.AddDefaultDataSource("ds0", Cs);
})
.AddShardingCore();
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
context.ServiceProvider.UseAutoTryCompensateTable();
}
}
IConnectionStringResolver 按 tenant 动态切换连接串,而 ShardingCore 的 IVirtualDataSource 自己管连接串和数据源切换,两套机制在多租户下会再冲突一次,需要你自己设计 dataSourceName 与 tenantId 的映射,方案上面给的代码不覆盖。7.9.2.1(net8.0,可被 net9 项目引用)或 7.10.2.1(net10.0)。请按你 ABP 9.3.1 实际 TFM 选一个验证。Thanks
Great