efcore выдает ошибку при чтении с базы данных The entity type 'Phone' requires a primary key to be defined. на поле ValueObject
в приложении есть 2 контекста(на чтение и на запись)
public class AppReadDbContext : DbContext
{
private readonly IConfiguration _configuration;
public AppReadDbContext(IConfiguration configuration)
{
_configuration = configuration;
}
public DbSet<ArticleDto> Articles => Set<ArticleDto>();
public DbSet<AppUserDto> Users => Set<AppUserDto>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(UserEntityConfiguration).Assembly,
type => type.FullName.Contains("EntityConfigurations.Read"));
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(_configuration.GetConnectionString("DatabaseAccess"));
optionsBuilder.UseSnakeCaseNamingConvention();
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
}
public class AppWriteDbContext : DbContext
{
private readonly IConfiguration _configuration;
public AppWriteDbContext(IConfiguration configuration)
{
_configuration = configuration;
}
public DbSet<Article> Articles => Set<Article>();
public DbSet<AppUser> Users => Set<AppUser>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ArticleEntityConfiguration).Assembly,
type => type.FullName.Contains("EntityConfigurations.Write")); ;
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(_configuration.GetConnectionString("DatabaseAccess"));
optionsBuilder.UseSnakeCaseNamingConvention();
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
}
}
так же есть две доменные модели(на чтение и на запись)
public class AppUser
{
private static readonly DateTime minDate = new DateTime(1920, 1, 1);
private static readonly DateTime maxDate = DateTime.UtcNow;
private AppUser() { }
private AppUser(
string userName,
string passwordHash,
string email,
DateTimeOffset registrationDate)
{
UserName = userName;
PasswordHash = passwordHash;
Email = email;
RegistrationDate = registrationDate;
}
public Guid Id { get; private set; }
[Required]
public string UserName { get; private set; }
[Required]
public string PasswordHash { get; private set; }
public string Email { get; private set; }
public Phone Phone { get; private set; }
public string FirstName { get; private set; } = string.Empty;
public string LastName { get; private set; } = string.Empty;
public string SecondName { get; private set; } = string.Empty;
public DateTimeOffset BirthDate { get; private set; } = DateTimeOffset.MinValue;
public DateTimeOffset RegistrationDate { get; private set; }
private IReadOnlyList<Article> _articles = [];
public IReadOnlyList<Article> Articles => _articles;
private IReadOnlyList<Comment> _comments = [];
public IReadOnlyList<Comment> Comments => _comments;
public static Result<AppUser, Error> Create(
string userName,
string passwordHash,
string emailInput)
{
emailInput = emailInput.Trim().ToLower();
var emailResult = EmailObject.Create(emailInput);
if (emailResult.IsFailure) return emailResult.Error;
userName = userName.Trim().ToLower();
if (string.IsNullOrEmpty(userName))
userName = emailResult.Value.Email;
passwordHash = passwordHash.Trim();
if (string.IsNullOrEmpty(passwordHash))
return Errors.General.InValid(passwordHash);
return new AppUser(
userName,
passwordHash,
emailResult.Value.Email,
DateTimeOffset.UtcNow);
}
}
public class AppUserDto
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string SecondName { get; set; }
public DateTimeOffset BirthDate { get; set; }
public DateTimeOffset RegistrationDate { get; set; }
public ICollection<ArticleDto> Articles { get; set; }
public ICollection<CommentDto> Comments { get; set; }
}
в доменной модели есть value object Phone
public class Phone
{
private const string regexValidationPhone = @"^((8 |\+7)[\- ]?)?(\(?\d{3}\)?[\- ]?)?[\d\- ]{7,10}$";
private Phone() { }
private Phone(string phoneNumber)
{
PhoneNumber = phoneNumber;
}
public Guid Id { get;}
public string PhoneNumber { get;} = string.Empty;
public static Result<Phone, Error> Create(string input)
{
if(string.IsNullOrEmpty(input.Trim()))
return Errors.General.InValid(input);
if(!Regex.IsMatch(input, regexValidationPhone))
return Errors.General.InValid(input);
return new Phone(input);
}
}
определены конфигурации для обоих моделей
public class AppUserEntityConfiguration : IEntityTypeConfiguration<AppUser>
{
public void Configure(EntityTypeBuilder<AppUser> builder)
{
builder.ToTable("users");
builder.HasKey(u => u.Id);
builder.HasIndex(u => u.UserName)
.IsUnique();
builder.HasIndex(u => u.Email)
.IsUnique();
builder.Property(u => u.Id)
.HasColumnName("id");
builder.Property(u => u.UserName)
.IsRequired()
.HasColumnName("user_name");
builder.Property(u => u.PasswordHash)
.IsRequired()
.HasColumnName("user_password_hash");
builder.Property(p => p.Email)
.HasColumnName("email")
.IsRequired();
builder.Property(u => u.RegistrationDate)
.IsRequired()
.HasColumnName("user_registration_date;");
builder.Property(u => u.FirstName)
.HasDefaultValue("")
.IsRequired()
.HasColumnName("user_first_name");
builder.Property(u => u.SecondName)
.HasDefaultValue("")
.IsRequired()
.HasColumnName("user_second_name;");
builder.Property(u => u.LastName)
.HasDefaultValue("")
.IsRequired()
.HasColumnName("user_last_name;");
builder.Property(u => u.BirthDate)
.IsRequired()
.HasColumnName("user_birth_date;")
.HasDefaultValue(DateTimeOffset.MinValue);
builder.OwnsOne(u => u.Phone, p =>
p.Property(p => p.PhoneNumber)
.HasDefaultValue("+79998887766")
.HasColumnName("phone_number"));
builder.HasMany(u => u.Articles)
.WithOne(a => a.Author)
.OnDelete(DeleteBehavior.NoAction);
builder.HasMany(u => u.Comments)
.WithOne(c => c.Author)
.OnDelete(DeleteBehavior.NoAction);
}
}
public class UserEntityConfiguration : IEntityTypeConfiguration<AppUserDto>
{
public void Configure(EntityTypeBuilder<AppUserDto> builder)
{
builder.ToTable("users");
builder.HasKey(u => u.Id);
builder.HasIndex(u => u.UserName)
.IsUnique();
builder.HasIndex(u => u.Email)
.IsUnique();
builder.Property(u => u.Id)
.HasColumnName("id");
builder.Property(u => u.UserName)
.HasColumnName("user_name");
builder.Property(p => p.Email)
.HasColumnName("email");
builder.Property(u => u.RegistrationDate)
.HasColumnName("user_registration_date;");
builder.Property(u => u.FirstName)
.HasColumnName("user_first_name");
builder.Property(u => u.SecondName)
.HasColumnName("user_second_name;");
builder.Property(u => u.LastName)
.HasColumnName("user_last_name;");
builder.Property(u => u.BirthDate)
.HasColumnName("user_birth_date;");
builder.Property(u => u.Phone)
.HasColumnName("phone_number");
builder.HasMany(u => u.Articles)
.WithOne(a => a.Author);
builder.HasMany(u => u.Comments)
.WithOne(c => c.Author);
}
}
вроде должно все работать. на запись так и есть все добавляется в базу без проблем.
а вот на чтение когда пытаюсь получить AppUserDto
EF Core выдает ошибку:InvalidOperationException The entity type 'Phone' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types
причем если меняю на стринг в AppUser
поле Phone
все работает хорошо.