When I am trying to add a migration, I get the following error:
Unable to determine the relationship represented by navigation 'User.Email' of type 'Email'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I don't want to use the [NotMapped] attribute, because I would like to create a column in my Users table. The existing answers I found on the internet were connected to many-many relationship or having collections of entities. I would like to configure the Email as a 'single' owned entity by the user, as far as I know using OwnsOne is the way to configure a value object.
Before the switching the Email to a value object, the add migration worked. Thank you for the help and let me know if you need more information.
The User class:
public class User : AggregateRoot, IAuditableEntity
{
private User(Guid id, Email email, UserName userName) : base(id)
{
Email = email;
UserName = userName;
}
private User()
{
}
public ICollection<RolePermission> RolePermissions { get; set; }
public UserName UserName { get; private set; }
public Email Email { get; private set; }
public DateTime CreatedOnUtc { get; set; }
public DateTime? ModifiedOnUtc { get; set; }
public static User Create(Guid id, Email email, UserName userName)
{
var user = new User(id, email, userName);
return user;
}
public void ChangeName(UserName userName)
{
UserName = userName;
}
}
The Email class (which is a value object):
public sealed class Email : ValueObject
{
public const int MaxLength = 255;
private Email(string value) => Value = value;
private Email()
{
}
public static Result<Email> Create(string email) =>
Result.Create(email)
.Ensure(
e => !string.IsNullOrWhiteSpace(e),
DomainErrors.Email.Empty)
.Ensure(
e => e.Length <= MaxLength,
DomainErrors.Email.TooLong)
.Ensure(
e => e.Split('@').Length == 2,
DomainErrors.Email.InvalidFormat)
.Map(e => new Email(e));
public string Value { get; private set; }
public override IEnumerable<object> GetAtomicValues()
{
yield return Value;
}
}
The DbContext:
public class ApplicationDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Permission> Role { get; set; }
public DbSet<Role> Roles { get; set; }
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(DbContext).Assembly);
}
}
And finally the UserConfiguration:
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable(TableNames.Users);
builder.HasKey(x => x.Id);
builder.OwnsOne(x => x.Email);
builder
.Property(x => x.Email)
.HasConversion(x => x.Value, v => Email.Create(v).Value);
builder
.Property(x => x.UserName)
.HasConversion(x => x.Value, v => UserName.Create(v).Value)
.HasMaxLength(100);
//builder.HasIndex(c => c.Email).IsUnique();
}
}
I tried to use [ComplexType] for the Email entity, it didn't make the migration work.
It turned out that for some reason none of the configurations were applied when I called the
ApplyConfigurationsFromAssemblymethod providing the assembly containing the dbcontext. So the the reason for this is still not clear. After applying the configurations one by one, it fixed the issue: