为什么应该用record来定义DTO(续)
Posted dotNET跨平台
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么应该用record来定义DTO(续)相关的知识,希望对你有一定的参考价值。
前言
上次,我们介绍了因为DTO的“不变性”,应该用record来定义DTO。
今天,我们来说明用record来定义DTO的另一个好处。
问题
首先,我们实现一个Controler,代码如下:
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
private readonly IMediator _mediator;
public UserController(IMediator mediator)
{
this._mediator = mediator;
}
[HttpGet("{id}")]
public async Task<UserDto> GetById(int id)
{
var request = new GetUserByIdQuery { Id = id };
var result = await this._mediator.Send(request);
return result;
}
}
public class UserDto
{
public int Id { get; set; }
public string Name{ get; set; }
}
public class GetUserByIdQuery : IRequest<UserDto>
{
public int Id { get; set; }
}
这里,IRequest<T>
可以认为是DTO。
然后,我们编写测试用例:
[Fact]
public async void Test1()
{
var mediatorMock = new Mock<IMediator>();
var request = new GetUserByIdQuery { Id = 1};
var expectedUser = new UserDto { Id = 1, Name = "My IO" };
mediatorMock.Setup(x => x.Send(request, default(CancellationToken)))
.Returns(Task.FromResult(expectedUser));
var controller = new UserController(mediatorMock.Object);
var result = await controller.GetById(1);
Assert.Equal(expectedUser, result);
}
我们Mock了IMediator
,期望它执行Send
后返回expectedUser。
看起来都没有问题,但是测试执行失败:
调试代码,可以看到传递的参数是正确的,但是返回值是null:
这说明实际没有命中mediatorMock.Setup中的方法。
这是为什么呢?
原因
原因其实是,x.Send(request, default(CancellationToken))
表示必须完全匹配才能返回指定的结果,但是request
和GetById方法中创建的request
其实是2个不同的实例,.NET并不认为它们相等。
虽然可以修改mediatorMock.Setup方法来修复测试。
但对于我来说,属性值完全相同的DTO应该就是相等的,可以让类实现值相等性来解决:
public class GetUserByIdQuery : IRequest<UserDto>
{
public int Id { get; set; }
public override bool Equals(object obj) => this.Equals(obj as GetUserByIdQuery);
public bool Equals(GetUserByIdQuery p)
{
if (p is null)
{
return false;
}
if (Object.ReferenceEquals(this, p))
{
return true;
}
if (this.GetType() != p.GetType())
{
return false;
}
return Id == p.Id;
}
public override int GetHashCode() => Id.GetHashCode();
}
但是,为每个DTO重写Equals
和GetHashCode
也不是个事。
record的相等性
其实,更简单的解决方法是修改定义如下:
public record GetUserByIdQuery : IRequest<UserDto>
{
public int Id { get; set; }
}
你会发现测试通过了。
这是因为,record在设计上就具备创建具有值相等数据类型的能力,编译器会自动生成样板代码:
结论
在本文中,我们介绍了通过使用record类型,可以大大简化定义实现值相等性DTO的代码量。
如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“,记住我!
以上是关于为什么应该用record来定义DTO(续)的主要内容,如果未能解决你的问题,请参考以下文章