使用gRPC protobuf-net时能否直接返回null值?
我用gRPC + protobuf-net实现客户端与服务端通信,客户端通过ID获取实体,服务端调用仓库方法,实体不存在时返回null。现在遇到的问题是:实体存在时一切正常,但返回null时会抛出异常,怀疑是protobuf-net无法序列化null值导致的。我考虑过用包含可空目标类型属性的Response类封装结果,这个方案可行,但想尽量避免。想问protobuf-net是否支持传输null值,还是必须用Response类封装?
补充代码
// 用作请求,因为据我所知protobuf-net无法序列化.NET基元类型,需要使用类。 [DataContract] public class LongDTO { public LongDTO() { } public LongDTO(long paramLong) { Long = paramLong; } [DataMember(Order = 1)] public readonly long Long; } [ServiceContract] public interface IMyService { public Task<MyType?> GetEntityAsync(LongDTO paramLongRequest); } [Authorize] public class MyServiceProtobufNet : IMyService { public async Task<MyType?> GetEntityAsync(LongDTO paramLongRequest) { // 若数据库中不存在该实体,仓库会返回null。 return await _repository.GetEntityById(paramLongRequest.Long); } } public class MyCLientProtobufNet { public async Task<MyType?> GetEntityAsync(long paramId) { LongDTO miRequest = new LongDTO(paramId); return await _cliente.GetEntityAsync(miRequest); } }
protobuf-net本身不支持直接将null作为gRPC方法的返回值,根源是gRPC依赖的protobuf协议原生没有"null"的表示逻辑——protobuf序列化的核心是消息对象,返回null相当于没有任何可传输的消息数据,这会直接触发gRPC的传输异常。
如果不想用封装的Response类,有两个替代思路,但局限性都很大:
- 若
MyType是值类型,可使用可空值类型包装,但你的场景里MyType应该是引用类型,这个方案不适用; - 利用protobuf-net的
[DataMember(IsRequired = false)]特性处理消息内部的可空属性,但这只针对消息内部字段,无法解决整个方法返回值为null的问题。
所以最可靠的方案还是用Response类封装结果,比如定义一个包含实体结果和存在标识的响应类:
[DataContract] public class GetEntityResponse { [DataMember(Order = 1)] public MyType? Result { get; set; } [DataMember(Order = 2)] public bool IsFound { get; set; } }
之后修改服务接口、实现以及客户端调用逻辑,通过IsFound判断实体是否存在,再读取对应的Result值。
如果实在不想新增响应类,还有一种不推荐的hack方案:定义一个继承自MyType的"空对象"实例,服务端找不到实体时返回该实例,客户端收到后判断是否为这个空实例。但这种方案可读性差,维护成本高,不适合生产环境。
总结:必须通过封装类处理null结果,这是gRPC和protobuf协议的底层限制,并非protobuf-net的功能缺失。
内容的提问来源于stack exchange,提问作者Álvaro García

