我正在尝试配置一个具有可为空值对象
User
的聚合根 ChangePasswordCode
。当我在存储库中调用 _dbContext.Users.FirstOrDefaultAsync
时,即使数据存在于数据库中,我也会收到一个带有 null User
的 ChangePasswordCode
对象。
代码如下:
public class User : AggregateRoot<UserId>
{
private readonly List<Ban> _bans = [];
public Name Name { get; private set; }
public Email Email { get; private set; }
public Password Password { get; private set; }
public DepartmentId DepartmentId { get; private set; }
public Roles Roles { get; private set; }
public IReadOnlyList<Ban> Bans => _bans.AsReadOnly();
public ChangePasswordCode? ChangePasswordCode { get; private set; }
#pragma warning disable CS8618
private User() { }
#pragma warning restore CS8618
private User(
UserId id,
Name name,
Email email,
Password password,
DepartmentId departmentId,
Roles roles) : base(id)
{
Name = name;
Email = email;
Password = password;
DepartmentId = departmentId;
Roles = roles;
}
...
}
public class ChangePasswordCode : ValueObject
{
public string Value { get; private set; }
public DateTime ExpiryTime { get; private set; }
public ChangePasswordCode(string value, DateTime expiryTime)
{
Value = value;
ExpiryTime = expiryTime;
}
...
}
internal class UserConfigurations : IEntityTypeConfiguration<User>
{
...
private void ConfigureUsersTable(EntityTypeBuilder<User> builder)
{
...
builder.OwnsOne(u => u.ChangePasswordCode);
...
这是 DbContextSnapshop:
b.OwnsOne("Domain.UserAggregate.ValueObjects.ChangePasswordCode", "ChangePasswordCode", b1 =>
{
b1.Property<Guid>("UserId")
.HasColumnType("uuid");
b1.Property<DateTime>("ExpiryTime")
.HasColumnType("timestamp with time zone");
b1.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b1.HasKey("UserId");
b1.ToTable("Users");
b1.WithOwner()
.HasForeignKey("UserId");
});
b.Navigation("ChangePasswordCode");
这是问题发生的地方:
internal class UserRepository : IUserRepository
{
private readonly IdearDbContext _context;
public UserRepository(IdearDbContext context)
{
_context = context;
}
public async Task<User?> GetByIdAsync(UserId id, CancellationToken cancellationToken = default)
{
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == id, cancellationToken);
return user; // user.ChangePasswordCode is null here
}
我尝试检查更改跟踪器调试视图以查看数据是否实际正确检索,并且调试视图是:
User {Id: Domain.UserAggregate.ValueObjects.UserId} Unchanged
Id: 'Domain.UserAggregate.ValueObjects.UserId' PK
DepartmentId: 'Domain.DepartmentAggregate.ValueObjects.DepartmentId' FK
Email: 'Domain.UserAggregate.ValueObjects.Email'
Name: 'Domain.Shared.ValueObjects.Name'
Password: 'Domain.UserAggregate.ValueObjects.Password'
Roles: 'Admin'
Bans: []
ChangePasswordCode: <null>
ChangePasswordCode {UserId: Domain.UserAggregate.ValueObjects.UserId} Unchanged
UserId: 'Domain.UserAggregate.ValueObjects.UserId' PK FK
ExpiryTime: '11/20/2024 9:31:31 AM'
Value: 'B20BB3'
奇怪的是,ChangePasswordCode 实际上已加载并跟踪,但由于某种原因,它没有附加到
User
条目。
我发现了一个类似的问题here,并尝试了该建议,但它仍然为空。我还尝试切换到不可为空的
ChangePasswordCode
属性,它确实使该字段在数据库中成为必需的,但当映射到 User
对象时它仍然为空。
TL;DR - 我对
GetHashCode
实施了错误的 ValueObject
方法。
创建一个不同的项目来测试
User
和 ChangePasswordCode
后,我发现问题出在 UserId
值对象上,因为当我使用 Guid
作为主键时它工作正常。当时,我怀疑更改跟踪器可能会比较 UserId
和 User
条目中的 2 个 ChangePasswordCode
,但不知何故与 2 不匹配。所以问题可能出在 Equals
和 GetHashCode
方法中我为ValueObject
实施。
通过在调试模式下放置断点,我发现更改跟踪器确实在
GetHashCode
之前先调用 Equals
,并且它为相同的 UserId
返回 2 个不同的哈希代码。我修复了 GetHashCode
方法,现在它可以工作了。