I have two entities, Movie
and UserRating
. I want to be able to specify table names and column names myself. UserRating
shall have a foreign key to Movie
in the database, but I'd like both entities in my code to have a reference to the other.
When I try to configure the relations using the fluent API in Entity Framework 7 i run into issues where creating a migrations fails due to the property/navigation already existing.
My two entity classes look like this
public class Movie
{
public long Id { get; set; }
public string Title { get; set; }
public UserRating UserRating { get; set; }
}
public class UserRating
{
public long Id { get; set; }
public Movie Movie { get; set; }
public int Rating { get; set; }
}
I have two classes where I configure the mappings:
public class MovieConfig : IEntityTypeConfig<Movie>
{
public void Configure(EntityTypeBuilder<Movie> builder)
{
builder.ToTable("movie");
builder.HasKey(m => m.Id);
builder.Property(m => m.Id).HasColumnName("id").ValueGeneratedOnAdd();
builder.Property(m => m.Title).HasColumnName("title").IsRequired();
}
}
public class UserRatingConfig : IEntityTypeConfig<UserRating>
{
public void Configure(EntityTypeBuilder<UserRating> builder)
{
builder.ToTable("user_rating");
builder.HasKey(ur => ur.Id);
builder.Property(ur => ur.Id).HasColumnName("id").ValueGeneratedOnAdd();
builder.Property(ur => ur.Rating).HasColumnName("rating").IsRequired();
builder.Property(ur => ur.Movie).HasColumnName("movie_id").IsRequired();
builder.HasOne(ur => ur.Movie).WithOne(m => m.UserRating).HasForeignKey("movie_id");
}
}
And lastly the DbContext where the configs are applied:
public class MovieContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseNpgsql("<connection string>");
public DbSet<Movie> Movies { get; set; }
public DbSet<UserRating> UserRatings { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new MovieConfig());
modelBuilder.ApplyConfiguration(new UserRatingConfig());
}
}
With this setup, when I try to run dotnet ef migrations add InitialCreate
I get the following error:
The property or navigation 'Movie' cannot be added to the entity type 'UserRating' because a property or navigation with the same name already exists on entity type 'UserRating'.
The stacktrace originates from the last line in UserRatingConfig
.
So it sounds like the relationship's being configured two times somehow. So I tried to prevent it being configured from Movie
, first by setting [NotMapped]
on the UserRating
property. And then by specifying builder.Ignore(m => m.UserRating);
in MovieConfig
. Both resulted in the same error.
Okay, so what if I try to just skip configuring the relation from UserRating
? I tried to just comment out the last line, where the stacktrace originates:
//builder.HasOne(ur => ur.Movie).WithOne(m => m.UserRating).HasForeignKey("movie_id");
That results in a different error message, that is even more confusing. Now I get the error: The 'Movie' property 'UserRating.Movie' could not be mapped because the database provider does not support this type. Consider converting the property value to a type supported by the database using a value converter. See https://aka.ms/efcore-docs-value-converters for more information. Alternately, exclude the property from the model using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
This makes me even more confused. So somehow I'm trying to configure the relationship two times, and if I remove the only place I know about, then I'm apparently not configuring it at all.
What do I need to change in order for this to work?
You only need to configure the relationship once, and you only need the fluent configuration at all if you don't have a declared foreign key property. eg
modelBuilder.Entity<Movie>()
.HasOne(m => m.UserRating)
.WithOne(r => r.Movie)
.HasForeignKey<UserRating>(r => r.Id);
creates
CREATE TABLE [Movie] (
[Id] bigint NOT NULL IDENTITY,
[Title] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Movie] PRIMARY KEY ([Id])
);
CREATE TABLE [UserRating] (
[Id] bigint NOT NULL,
[Rating] int NOT NULL,
CONSTRAINT [PK_UserRating] PRIMARY KEY ([Id]),
CONSTRAINT [FK_UserRating_Movie_Id] FOREIGN KEY ([Id]) REFERENCES [Movie] ([Id]) ON DELETE CASCADE
);
Also 1-1 relationships have bad code smell. Just add the properties to Movie, or declare UserRating as an Owned Entity Type if you want to dot into the rating properties.