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

在Marten中无法成功加载关联实体/文档的问题求助

Marten 7.26.0 加载关联文档时的ArgumentNullException问题解决

问题背景

使用基于PostgreSQL的Marten文档数据库(版本7.26.0),查询MemberKycVerificationStep并加载关联的KycVerificationStep时触发空引用异常,异常栈信息如下:

System.ArgumentNullException: Value cannot be null. (Parameter 'key')
at System.Collections.Generic.Dictionary2.TryInsert(TKey key, TValue value, InsertionBehavior behavior) at System.Collections.Generic.Dictionary2.set_Item(TKey key, TValue value)
at Marten.Linq.Parsing.SelectParser.VisitConstant(ConstantExpression node)
...(省略后续栈信息)

涉及实体代码

KycVerificationStep 实体

public class KycVerificationStep : BaseEntity
{
    public Guid Id { get; set; }
    public string Code { get; private set; }
    public string Name { get; set; }
    public string Description { get; private set; }
    public string Icon { get; private set; }
    public int Order { get; private set; }
    public DateTimeOffset DateCreated { get; private set; }
    public DateTimeOffset? DateUpdated { get; private set; }

    private KycVerificationStep() { }

    private KycVerificationStep(
        string name,
        string description,
        string icon,
        int order,
        IKycVerificationStepDomainService domainService)
    {
        CheckRule(new KycStepMustBeUniqueRule(domainService, name));
        Name = name;
        Description = description;
        Icon = icon;
        Order = order;
        DateCreated = SystemClock.Now;
        Code = $"Step {Order}";

        AddDomainEvent(new KycVerificationStepCreatedDomainEvent(Id));
    }

    public static KycVerificationStep Create(
        string name,
        string description,
        string icon,
        int order,
        IKycVerificationStepDomainService domainService)
    {
        return new KycVerificationStep(name, description, icon, order, domainService);
    }

    // shortened for brevity   
}

MemberKycVerificationStep 实体

public class MemberKycVerificationStep : BaseEntity
{
    public string Id { get; private set; }

    public Guid KycVerificationStepId { get; private set; }

    public KycVerificationStep KycVerificationStep { get; private set; }

    public Guid MemberId { get; private set; }

    public string Status { get; private set; }

    public DateTimeOffset DateUpdated { get; private set; }

    private MemberKycVerificationStep() { }

    private MemberKycVerificationStep(
        Guid kycVerificationStepId,
        Guid memberId)
    {
        Id = $"{memberId}_{kycVerificationStepId}";
        KycVerificationStepId = kycVerificationStepId;
        MemberId = memberId;
        Status = VerificationStatus.NotStarted.ToString();

        AddDomainEvent(new MemberKycVerificationStepCreatedDomainEvent(MemberId, KycVerificationStepId));
    }

    public static MemberKycVerificationStep Create(
        Guid kycVerificationStepId,
        Guid memberId)
    {
        return new MemberKycVerificationStep(kycVerificationStepId, memberId);
    }
}

出错的查询代码

public async Task<Result<IPagedList<MemberKycVerificationStepDto>>> Handle(GetAllVerificationStepsByUserQuery request, CancellationToken cancellationToken)
{
    KycVerificationStep included = null;
    var results = await _querySession
        .Query<MemberKycVerificationStep>()
        .Include<KycVerificationStep>(x => included = x)
            .On(x => x.KycVerificationStepId)
        .Where(x => x.MemberId == request.UserId)
        .Select(x => new MemberKycVerificationStepDto
        {
            KycVerificationStepId = x.KycVerificationStepId,
            Name = included != null ? included.Name : "",
            DateUpdated = x.DateUpdated,
            Status = "x.Status",
            MemberId = x.MemberId
        })
        .ToPagedListAsync(request.PageIndex, request.PageSize, cancellationToken);
    return await Result<IPagedList<MemberKycVerificationStepDto>>.SuccessAsync(results);
}

问题原因

你在查询中使用外部变量included接收Include的关联文档,并在Select投影中直接引用该变量。Marten解析LINQ表达式时,会将included视为初始值为null的常量表达式,内部处理时尝试将null作为键插入字典,触发ArgumentNullException。Marten的Include机制是将关联文档加载到会话缓存中,不能直接在投影表达式中通过外部变量引用。

另外,代码中Status = "x.Status"是笔误,应改为x.Status。

解决方案

提供三种可行的解决方式:

方式一:使用Marten跨文档Join查询

直接通过Join关联两个文档类型,一次性完成查询和投影:

public async Task<Result<IPagedList<MemberKycVerificationStepDto>>> Handle(GetAllVerificationStepsByUserQuery request, CancellationToken cancellationToken)
{
    var results = await _querySession
        .Query<MemberKycVerificationStep>()
        .Join(
            _querySession.Query<KycVerificationStep>(),
            memberStep => memberStep.KycVerificationStepId,
            kycStep => kycStep.Id,
            (memberStep, kycStep) => new MemberKycVerificationStepDto
            {
                KycVerificationStepId = memberStep.KycVerificationStepId,
                Name = kycStep?.Name ?? "",
                DateUpdated = memberStep.DateUpdated,
                Status = memberStep.Status,
                MemberId = memberStep.MemberId
            })
        .Where(dto => dto.MemberId == request.UserId)
        .ToPagedListAsync(request.PageIndex, request.PageSize, cancellationToken);
    
    return await Result<IPagedList<MemberKycVerificationStepDto>>.SuccessAsync(results);
}

方式二:先分页查询,再批量加载关联文档

先获取分页后的MemberKycVerificationStep,再批量加载关联的KycVerificationStep,最后手动映射DTO:

public async Task<Result<IPagedList<MemberKycVerificationStepDto>>> Handle(GetAllVerificationStepsByUserQuery request, CancellationToken cancellationToken)
{
    // 分页查询MemberKycVerificationStep
    var memberSteps = await _querySession
        .Query<MemberKycVerificationStep>()
        .Where(x => x.MemberId == request.UserId)
        .ToPagedListAsync(request.PageIndex, request.PageSize, cancellationToken);
    
    // 提取所有关联ID,批量加载关联文档
    var stepIds = memberSteps.Select(x => x.KycVerificationStepId).ToList();
    var kycSteps = await _querySession.LoadManyAsync<KycVerificationStep>(stepIds, cancellationToken);
    
    // 映射DTO并保留分页信息
    var dtoList = memberSteps.Select(m => new MemberKycVerificationStepDto
    {
        KycVerificationStepId = m.KycVerificationStepId,
        Name = kycSteps.TryGetValue(m.KycVerificationStepId, out var step) ? step.Name : "",
        DateUpdated = m.DateUpdated,
        Status = m.Status,
        MemberId = m.MemberId
    }).ToList();
    
    var pagedDto = new PagedList<MemberKycVerificationStepDto>(
        dtoList, 
        memberSteps.PageIndex, 
        memberSteps.PageSize, 
        memberSteps.TotalCount);
    
    return await Result<IPagedList<MemberKycVerificationStepDto>>.SuccessAsync(pagedDto);
}

方式三:利用Marten会话缓存加载关联文档

先通过Include将关联文档加载到会话缓存,再从缓存中获取关联文档映射DTO:

public async Task<Result<IPagedList<MemberKycVerificationStepDto>>> Handle(GetAllVerificationStepsByUserQuery request, CancellationToken cancellationToken)
{
    // 查询并Include关联文档到会话缓存
    var memberSteps = await _querySession
        .Query<MemberKycVerificationStep>()
        .Include<KycVerificationStep>(x => x.KycVerificationStepId)
        .Where(x => x.MemberId == request.UserId)
        .ToPagedListAsync(request.PageIndex, request.PageSize, cancellationToken);
    
    // 从会话缓存中获取关联文档并映射DTO
    var dtoList = memberSteps.Select(m => 
    {
        var kycStep = _querySession.Load<KycVerificationStep>(m.KycVerificationStepId);
        return new MemberKycVerificationStepDto
        {
            KycVerificationStepId = m.KycVerificationStepId,
            Name = kycStep?.Name ?? "",
            DateUpdated = m.DateUpdated,
            Status = m.Status,
            MemberId = m.MemberId
        };
    }).ToList();
    
    var pagedDto = new PagedList<MemberKycVerificationStepDto>(
        dtoList, 
        memberSteps.PageIndex, 
        memberSteps.PageSize, 
        memberSteps.TotalCount);
    
    return await Result<IPagedList<MemberKycVerificationStepDto>>.SuccessAsync(pagedDto);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 08:50:55