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

HotChocolate GraphQL+FluentValidation:批量创建图书的结果与错误返回

解决HotChocolate批量Mutation部分失败时返回成功结果的问题

问题背景

基于HotChocolate GraphQL框架开发,结合AnyApp.FluentValidation实现输入验证。执行批量创建图书的mutation时,若其中一个请求失败(如目标作者不存在),系统仅返回错误信息,不会返回已成功创建的图书ID,但实际上对应的图书已完成数据库写入。

示例批量请求:

mutation {
  book1:createBook(inputModel: {
    title: "C# is great",
    authorId: "79827c91-5523-48f2-95b2-c8a181e73760"
  })
  
  book2: createBook(inputModel: {
    title: "GraphQL is awesome",
    authorId: "baac5788-0c30-422c-a910-97fb44f2838a"
  })
}

当book2的作者不存在时,当前响应仅返回book2的错误,不包含已成功创建的book1的ID。


解决方案

核心思路是让HotChocolate将业务异常转换为字段级错误,而非终止整个请求执行,从而保留已成功字段的返回结果。

1. 标记业务异常并配置错误过滤器

首先确保自定义业务异常能被HotChocolate识别为字段级错误,而非全局未处理异常:

using HotChocolate;

// 自定义业务异常
public class AuthorDoesNotExistException : Exception
{
    public AuthorDoesNotExistException() : base("作者不存在") { }
}

// 在服务注册时添加错误过滤器
builder.Services.AddGraphQLServer()
    // 其他GraphQL配置(如添加类型、扩展等)
    .AddErrorFilter<CustomErrorFilter>();

// 自定义错误过滤器,将指定异常转换为字段错误
public class CustomErrorFilter : IErrorFilter
{
    public IError OnError(IError error)
    {
        if (error.Exception is AuthorDoesNotExistException ex)
        {
            return error.WithMessage(ex.Message)
                        .WithCode("AUTHOR_NOT_EXIST")
                        .RemoveExtension("stackTrace"); // 可选:移除堆栈信息,简化错误响应
        }
        return error;
    }
}

2. 确保验证逻辑的一致性

CreateBookInputModelValidator已包含作者存在性验证,但服务层仍重复检查。可保留验证器逻辑拦截大部分无效请求,同时通过错误过滤器处理并发场景下(验证后作者被删除)的异常。

3. 验证效果

调整后,当book2的作者不存在时,响应会同时返回成功的book1 ID和book2的错误信息:

{
  "data": {
    "book1": "8611b832-8a0e-4d5c-b956-b395e3f4e5f2",
    "book2": null
  },
  "errors": [
    {
      "message": "作者不存在",
      "locations": [
        {
          "line": 7,
          "column": 3
        }
      ],
      "path": [
        "book2"
      ],
      "extensions": {
        "code": "AUTHOR_NOT_EXIST"
      }
    }
  ]
}

额外优化:使用Union类型明确返回结构(可选)

若需要更清晰的结果结构,可定义Union类型区分成功与失败结果:

// 成功结果模型
public class BookCreationSuccess
{
    public Guid BookId { get; set; }
}

// 错误结果模型
public class BookCreationError
{
    public string Message { get; set; }
}

// 定义Union类型接口
[UnionType("BookCreationOutput")]
public interface IBookCreationOutput { }

// 注册Union类型成员
public class BookCreationSuccessType : ObjectType<BookCreationSuccess>, IBookCreationOutput { }
public class BookCreationErrorType : ObjectType<BookCreationError>, IBookCreationOutput { }

// 修改Mutation方法返回Union类型
public IBookCreationOutput CreateBook(
    [Service] IBookService service,
    [UseFluentValidation, UseValidator<CreateBookInputModelValidator>] CreateBookInputModel inputModel
)
{
    try
    {
        var bookId = service.CreateBook(inputModel);
        return new BookCreationSuccess { BookId = bookId };
    }
    catch (AuthorDoesNotExistException ex)
    {
        return new BookCreationError { Message = ex.Message };
    }
}

此方案下客户端可通过类型判断明确处理每个请求的成功或失败状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 19:57:47