You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Finbuckle.MultiTenant:WebApplicationFactory集成测试中TenantInfo为空引发异常

Finbuckle.MultiTenant集成测试查询触发NullReferenceException排查

问题场景

在使用Finbuckle.MultiTenant多租户特性查询数据表时触发NullReferenceException,异常仅在以下场景出现:

  • 实体配置中调用IsMultiTenant()方法时
  • 使用WebApplicationFactory执行集成测试时

查询代码

public async Task<Unit?> GetByIdAsync(UnitId id, CancellationToken cancellationToken = default)
{
    return await _dbContext.Units
        .FirstOrDefaultAsync(unit => unit.Id == id, cancellationToken);
}

实体配置代码

using Server.Domain.Units;
using Finbuckle.MultiTenant.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Server.Infrastructure.Configurations;

internal class UnitConfiguration : IEntityTypeConfiguration<Unit>
{
    public void Configure(EntityTypeBuilder<Unit> builder)
    {
        builder.ToTable("lookup_units");
        builder.HasKey(u => u.Id);
        builder.Property(u => u.Id)
            .ValueGeneratedNever()
            .HasConversion(
                id => id.Value,
                value => UnitId.Create(value));
        builder.HasIndex(u => u.Name).IsUnique();
        builder.HasQueryFilter(q => !q.IsDeleted);
        builder.IsMultiTenant().AdjustUniqueIndexes();

        builder.Property(u => u.Name).HasMaxLength(64);
        builder.Property(u => u.DefaultContentCount);
        builder.HasData(new List<Unit>()
        {
            Unit.Create(
                UnitId.Create(Guid.Parse("F7AE4D5C-B1B7-47F0-B0A0-D98DAC374FE8")),
                "bag", null, "tenant-1")
        });
    }
}

简化后的多租户配置

services.AddMultiTenant<TenantInfo>()
    .WithStaticStrategy("tenant-1")
    .WithInMemoryStore(config=>
        config.Tenants.Add(new TenantInfo
        {
            Id = "tenant-1",Identifier = "tenant-1", Name = "tenant-1",
        }))
    ;

原因分析

  1. 租户上下文未正确注入:集成测试中WebApplicationFactory初始化流程里,多租户上下文(IMultiTenantContextAccessor<TenantInfo>)可能未在DbContext执行查询前完成初始化,导致IsMultiTenant()添加的租户查询过滤器无法获取有效租户信息,触发空引用。
  2. 唯一索引调整依赖未就绪:调用AdjustUniqueIndexes()时,Finbuckle会尝试将租户键添加到唯一索引中,若租户字段未被正确初始化或上下文缺失,会引发空引用异常。

解决方案

1. 确保测试中租户上下文提前初始化

在WebApplicationFactory的ConfigureWebHost方法中,显式初始化租户上下文,确保DbContext执行查询前已有有效租户信息:

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
    builder.ConfigureServices(services =>
    {
        // 确保静态租户策略正确注册
        var strategyDescriptor = services.SingleOrDefault(
            d => d.ServiceType == typeof(IMultiTenantStrategy<TenantInfo>));
        if (strategyDescriptor != null)
        {
            services.Remove(strategyDescriptor);
            services.AddSingleton<IMultiTenantStrategy<TenantInfo>>(
                new StaticMultiTenantStrategy<TenantInfo>("tenant-1"));
        }
    });

    // 在测试启动时手动设置租户上下文
    var tenantAccessor = Services.GetRequiredService<IMultiTenantContextAccessor<TenantInfo>>();
    tenantAccessor.MultiTenantContext = new MultiTenantContext<TenantInfo>
    {
        TenantInfo = new TenantInfo { Id = "tenant-1", Identifier = "tenant-1", Name = "tenant-1" }
    };
}

2. 验证实体多租户字段配置

确保Unit实体包含Finbuckle默认的TenantId字段,或在IsMultiTenant()中显式指定自定义租户字段:

// 若实体使用自定义租户字段(如TenantCode)
builder.IsMultiTenant(u => u.TenantCode).AdjustUniqueIndexes();

3. 调整种子数据与测试租户一致性

确认HasData中添加的种子数据租户标识(tenant-1)与测试配置的租户ID完全匹配,避免因租户不匹配导致过滤器执行时出现异常。

4. 调试时临时忽略查询过滤器(仅限测试)

若需快速定位问题,可在测试查询中临时禁用租户查询过滤器:

return await _dbContext.Units.IgnoreQueryFilters()
    .FirstOrDefaultAsync(unit => unit.Id == id, cancellationToken);

⚠️ 注意:此方法仅用于调试,生产环境禁止使用。

内容的提问来源于stack exchange,提问作者AhmedSamak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 12:02:10