I have this mapping:
modelBuilder.Entity<Reservation>()
.HasMany(e => e.Chairs)
.WithMany(e => e.Reservations)
.Map(m => m.ToTable("ReservationsChairs").MapLeftKey("Reservation_ID").MapRightKey("Chair_ID"));
So, the problem is when I try to create a reservation.
I have this Reservation Object:
public class Reservation
{
public Reservation()
{
Chairs = new HashSet<Chair>();
}
public int ID { get; set; }
public bool IsPaid { get; set; }
public virtual Planning Planning { get; set; }
public virtual ICollection<Chair> Chairs { get; set; }
}
I attach the chairs (Wich is already created), but, Entity does not use the Many-to-Many table, to bind the Reservation with the Chairs. It actually begins to create the same Chairs in the Chairs table...
Why does it do this, and what can I do to make it use the Many-To-Many?
My Chair Object has this:
public virtual ICollection<Reservation> Reservations { get; set; }
Above node will be "Null" when I create the Reservation, with the attached Chairs. Is that wrong?
Related
I have 3 entities: Person, User, and Location.
A Person can have multiple Locations
A User can have multiple Locations
My entities are set up as such:
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual IList<Location>? Locations { get; set; }
}
public class PersonEntityTypeConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder
.HasMany(b => b.Locations)
.WithOne(b => b.Person)
.HasForeignKey(b => b.PersonId)
.IsRequired(false);
}
}
public class User
{
public Guid Id { get; set; }
public Guid? Username { get; set; }
public virtual IList<Location>? Locations { get; set; }
}
public class UserEntityTypeConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder
.HasMany(b => b.Locations)
.WithOne(b => b.User)
.HasForeignKey(b => b.UserId)
.IsRequired(false);
}
}
public class Location : UdbObject
{
public Guid Id { get; set; }
public string Description { get; set; }
[ForeignKey(nameof(Person))]
public Guid? PersonId { get; set; }
public virtual Person? Person { get; set; }
[ForeignKey(nameof(User))]
public Guid? UserId { get; set; }
public virtual User? User { get; set; }
}
Problem: I tried to insert a User into my SQL Server DB. This user has one Location object within its IList<Location>? Locations collection. I am getting the following error: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Locations_Persons_PersonId".
Here is where it is going wrong:
Since Person.Id is a Guid? object, it automatically gets set to the equivalent of Guid.Empty before it is submitted to the DB. This causes the FK conflict, since the DB sees that there is no Person object in the DB with an Id set to the equivalent of Guid.Empty.
What I've tried: I saw that in previous version of EF Core, there is a .WithOptional() method that can be used in the Fluent API, but unfortunately this method is not recognized in EF Core 7. I tried to use the .IsRequired(false) method in the API, and it probably works from the DB standpoint, but my problem is that the GUID-based Id property is being set to Guid.Empty on the server before being passed to the DB, so .IsRequired(false) doesn't have the opportunity to do its job.
Am I missing something? Is there some other way to configure this?
Solution: I had a PersonDto that had a property public Guid Id { get; set; } instead of Guid? and it was being mapped back to the Person object with Guid.Empty loaded in it. Duh.
Just make them M2M relationships and the foreign keys will all be in bridge tables. eg
public class Location : UdbObject
{
public Guid Id { get; set; }
public string Description { get; set; }
public virtual ICollection<Person> Persons { get; } = new HashSet<Person>();
public virtual ICollection<User> Users { get; } = new HashSet<User>();
}
I am designing a database i have three models(user, movie, review).The idea is each user can submit a movie each user can leave a review for a movie.The user model has ICollection and ICollecyion, the movie model has foreign key UserId and ICollection, and the review model has foreign keys for UserId and MovieId. When i try update-database i get this error:
Introducing FOREIGN KEY constraint 'FK_Review_User_UserId' on table 'Review' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Here are my models :
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public int FavouriteGenre { get; set; }
public ICollection<Movie> SubmittedMovies { get; set; }
public ICollection<Review> SubmittedReviews { get; set; }
}
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string PosterUrl { get; set; }
public int Year { get; set; }
public int Genre { get; set; }
public DateTime Posted { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public ICollection<Review> Reviews { get; set; }
}
public class Review
{
public int Id { get; set; }
public int Rating { get; set; }
public string UserReview { get; set; }
public int MovieId { get; set; }
public Movie Movie { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public DateTime Posted { get; set; }
}
And my on creating method in the dbContext :
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Review>()
.HasOne(r => r.User)
.WithMany(u => u.SubmittedReviews)
.HasForeignKey(r => r.UserId);
builder.Entity<Review>()
.HasOne(r => r.Movie)
.WithMany(m => m.Reviews)
.HasForeignKey(r => r.MovieId);
}
I have tried this :
builder.Entity<Review>()
.HasOne(r => r.User)
.WithMany(u => u.SubmittedReviews)
.HasForeignKey(r => r.UserId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<Review>()
.HasOne(r => r.Movie)
.WithMany(m => m.Reviews)
.HasForeignKey(r => r.MovieId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
I have also tried making the foreign keys in the Review model nullable(i have tried separately and both at the same time) with DeleteBehaviour.Restrict in the context , but nothing seems to work , when i deleted a movie it worked well , but when i tried to delete a user i got this error in SSMS :
The DELETE statement conflicted with the REFERENCE constraint "FK_Review_User_UserId". The conflict occurred in database "MovieDb", table "dbo.Review", column 'UserId'.
I think i understand the problem but i cant find a solution.
From this article
In SQL Server, a table cannot appear more than one time in a list of all the cascading referential actions that are started by either a DELETE or an UPDATE statement. For example, the tree of cascading referential actions must only have one path to a particular table on the cascading referential actions tree
Your schema contains a circular reference because a user can have a review on their own movie.
Here's what's (potentially) happening when you try to delete a user:
Reviews related to that user are cascade-deleted
Movies related to that user are cascade-deleted
Reviews related to that movie are cascade-deleted
The last two points there are what is causing the issue. Since both User and Movie have (I'm assuming) required relationships, you can have a scenario where the DB tries to delete the same record twice because it has a foreign key reference to a User and that same review is on a movie that the User created.
I would make the UserId field nullable/optional on the Reviews table.
Based on your description, I'm not sure if you set the property to be nullable as well, but your code should look like this:
public class Review
{
// Other fields hidden
public int? UserId { get; set; }
// ...
}
In your FluentAPI model builder definitions, you can set Movie Reviews to cascade, and SubmittedReviews on the users table to restrict on delete. You can also remove the .IsRequired(false) line from the model builder definition for Movie Reviews
Personally, this is why I just use "Soft Delete" columns (like DeletedBy, DeletedOn) in models with lots of dependent relationships. It's a bit more involved in your API code, as you have to manually go in and delete/mark
as deleted the related records, but it prevents headaches like this.
i have looked at a ton of previous posts on how to have multiple foreign keys from the same table but I cant seem to get it working, even though my code seems identical to these posts. As far as I can tell, the below should work.
My User model
public string Name{get;set;}
public ICollection<UserOwnership> Slaves { get; set; } //Users this user owns
public ICollection<UserOwnership> Masters { get; set; } //Users that own this user
My user ownership model
public class UserOwnership
{
public int Id { get; set; }
public int MasterUserId { get; set; }
public int SlaveUserId { get; set; }
public virtual User Master { get; set; }
public virtual User Slave { get; set; }
}
UserOwnership mapping
public class UserOwnershipMapping : IEntityTypeConfiguration<UserOwnership>
{
public void Configure(EntityTypeBuilder<UserOwnership> builder)
{
builder.ToTable("UserOwnership");
builder.HasKey(e => new { e.Id });
builder.HasOne(e => e.Master).WithMany(e => e.Slaves).HasForeignKey(e => e.MasterUserId).OnDelete(DeleteBehavior.Restrict); // one master many slaves
builder.HasOne(e => e.Slave).WithMany(e => e.Masters).HasForeignKey(e => e.SlaveUserId).OnDelete(DeleteBehavior.Restrict); // one slave many masters
}
}
my error:
Unable to determine the relationship represented by navigation property 'User.Slaves' of type 'ICollection<UserOwnership>'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I have two entities in my MVC application and I populated the database with Entity Framework 6 Code First approach. There are two city id in the Student entity; one of them for BirthCity, the other for WorkingCity. When I define the foreign keys as above an extra column is created named City_ID in the Student table after migration. Id there a mistake or how to define these FKs? Thanks in advance.
Student:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
public virtual City LivingCity { get; set; }
}
City:
public class City
{
public int ID { get; set; }
public string CityName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
To achieve what you want you need to provide some aditional configuration.Code First convention can identify bidirectional relationships, but not when there are
multiple bidirectional relationships between two entities.You can add configuration (using Data Annotations or the Fluent API) to present this
information to the model builder. With Data Annotations, you’ll use an annotation
called InverseProperty. With the Fluent API, you’ll use a combination of the Has/With methods to specify the correct ends of these relationships.
Using Data Annotations could be like this:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
[InverseProperty("Students")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
public virtual City LivingCity { get; set; }
}
This way you specifying explicitly that you want to relate the BirthCity navigation property with Students navigation property in the other end of the relationship.
Using Fluent Api could be like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasRequired(m => m.BirthCity)
.WithMany(m => m.Students).HasForeignKey(m=>m.BirthCityId);
modelBuilder.Entity<Student>().HasRequired(m => m.LivingCity)
.WithMany().HasForeignKey(m=>m.LivingCityId);
}
With this last solution you don't need to use any attibute.
Now, the suggestion of #ChristPratt in have a collection of Student in your City class for each relationship is really useful. If you do that, then the configurations using Data Annotations could be this way:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
[InverseProperty("BirthCityStudents")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
[InverseProperty("LivingCityStudents")]
public virtual City LivingCity { get; set; }
}
Or using Fluent Api following the same idea:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasRequired(m => m.BirthCity)
.WithMany(m => m.BirthCityStudents).HasForeignKey(m=>m.BirthCityId);
modelBuilder.Entity<Student>().HasRequired(m => m.LivingCity)
.WithMany(m => m.LivingCityStudents).HasForeignKey(m=>m.LivingCityId);
}
Sheesh. It's been a long day. There's actually a very big, glaring problem with your code, actually, that I completely missed when I commented.
The problem is that you're using a single collection of students on City. What's actually happening here is that EF can't decide which foreign key it should actually map that collection to, so it creates another foreign key specifically to track that relationship. Then, in effect you have no navigation properties for the collections of students derived from BirthCity and LivingCity.
For this, you have to drop down to fluent configuration, as there's no way to configure this properly using just data annotations. You'll also need an additional collection of students so you can track both relationships:
public class City
{
...
public virtual ICollection<Student> BirthCityStudents { get; set; }
public virtual ICollection<Student> LivingCityStudents { get; set; }
}
Then, for Student:
public class Student
{
...
public class StudentMapping : EntityTypeConfiguration<Student>
{
public StudentMapping()
{
HasRequired(m => m.BirthCity).WithMany(m => m.BirthCityStudents);
HasRequired(m => m.LivingCity).WithMany(m => m.LivingCityStudents);
}
}
}
And finally in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new Student.StudentMapping());
}
Courses have many prerequisites, and simultaneously a particular course can be a prerequisite for many courses. I've tried to establish the many-to-many relationship (in OnModelBCreating) using EF code-first with the following:
modelBuilder.Entity<Course>()
.HasMany(e => e.Prerequisites)
.WithMany(e => e.Postrequisites)
.Map(m => m.ToTable("CourseRequisiteMappings")
.MapLeftKey("CourseId").MapRightKey("CourseId")); // EDIT: THIS LINE IS THE PROBLEM. SEE MARKED ANSWER AND MY COMMENT ON IT.
Also, here is the Course class:
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public string InstitutionCode { get; set; }
public string Description { get; set; }
public bool IsElective { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Module> Modules { get; set; }
public virtual ICollection<Course> Prerequisites { get; set; }
public virtual ICollection<Course> Postrequisites { get; set; }
}
When I implemented this and went to update the database, it gave me the following errors:
CourseId: Name: Each property name in a type must be unique. Property
name 'CourseId' is already defined.
ModuleId: Name: Each property name in a type must be unique. Property
name 'ModuleId' is already defined.
CourseCourse: EntityType: EntitySet 'CourseCourse' is based on type
'CourseCourse' that has no keys defined.
ModuleModule: EntityType: EntitySet 'ModuleModule' is based on type
'ModuleModule' that has no keys defined.
I could not find an example of doing this which leads me to believe one of the following three are true:
There's a different way of accomplishing this that I don't see
I'm on the right track but overlooking something due to my lack of knowledge with EF
I'm the first one to try and EF doesn't support this (very unlikely)
First, does anyone know how I can set up this relationship, i.e., what do these errors mean (responding to #2)? For bonus points, is there another way of doing this that might be better or worse (kinda #1)? Thanks in advance.
Your mapping is nearly correct. But you have to understand that under the hood the Entity Framework wil create a so callled junction table that stores the many to many relationship.
This junction table wil just have two fields, containing the foreign keys which together make up the primary key. Obviously these foreign keys cannot have the same name.EF is smart enough to all figure it out by itself and no maping is necessary. Below a working example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
namespace ManyToManyUnderTheHoodSpike
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<CourseContext>());
using (CourseContext context=new CourseContext())
{
context.Courses.Add(new Course("Top of the bill")
{
PrerequisiteCourses = new List<Course>()
{
new Course("My two cents"),
new Course("Counting to two")
}
});
context.SaveChanges();
}
}
}
public class CourseContext : DbContext
{
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
public class Course
{
public Course() { }
public Course(string name)
{
Name = name;
}
public string Name {get;set;}
public int CourseId{get;set;}
public ICollection<Course> PrerequisiteCourses{get;set;}
public ICollection<Course> FollowUpCourses{get;set;}
}
}
If you run this code you get a database with two tables: Courses and CourseCourses with as the only fields Course_Id and Course_Id1.
But that is not very readable, so let's make the mapping to make it more readable:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Course>().HasMany(course => course.PrerequisiteCourses)
.WithMany(course => course.FollowUpCourses)
.Map(data => data.ToTable("Prerequisites")
.MapLeftKey("FollowUpId")
.MapRightKey("PrerequisiteId"));
}
Presto!
I would model like this. I know you wanted only 1 table. But Ef will create the many to many table if you dont. Not sure what you didnt get right without testing. So anyway, here is another option.
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public string InstitutionCode { get; set; }
public string Description { get; set; }
public bool IsElective { get; set; }
//nav elements
public virtual ICollection<Instructor> Instructors { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Module> Modules { get; set; }
public virtual ICollection<PreReqCourse> Prerequisites { get; set; }
// You can Find follow on courses, by accessing PreReqCourse table, but if you felt this navigation offered enough value, create a post req table too. Using same approach.
// public virtual ICollection<Course> Postrequisites { get; set; }
}
public class PreReqCourse
{
public virtual int Id {get; set;}
public virtual int CourseId { get; set; }
public virtual Course PreReqForCourse { get; set; } //Nav prop
}
modelBuilder.Entity<Course>()
.HasMany(e => e.Prerequisites)
.WithMany();
// Leave WithMany empty. You can define in PreReqCourse Table model, you dont need to model from both directions.
modelBuilder.Entity<PreReqCourse>()
.HasRequired(e => e.PreReqForCourse)
.HasForeignKey(f => f.CourseId)
.WithMany(p=>p.PreRequisites);