How do you make EF not allow deletion of a related entity? eg:
public class Enrollment
{
public int EnrollmentId { get; set; }
public virtual ICollection<Course> courses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
}
When I create a Course A and B, and then I create an Enrollment and add those courses to it, I need it to not allow me to delete Courses A or B. When I run it through my MVC controller this has no problem:
Course course = db.Courses.Find(id);
db.Courses.Remove(course);
db.SaveChanges();
I'm not even sure exactly what to search for. I think it's enforcing or enabling a many to many referential constraint? But I don't seem to find anything. Am I supposed to not expect it to make that constraint automatically? I figured I could always add the following line to the Delete controller:
if(db.Enrollments.Any(e => e.Courses.Any(c => c.CourseId == id)))
{ //error }
Also, trying the following fluentAPI wasn't working (among many variations):
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Enrollment>()
.HasMany<Course>(e => e.Courses)
.WithRequired()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Course>()
.HasMany(e => e.Enrollments);
base.OnModelCreating(modelBuilder);
}
How do you make EF not allow deletion of a related entity?
You want to prevent deleting things that is in used, right? Is course and enrollments a many to many relationship? Many to many will not cascade on delete by default. Try to change to this.
public class Enrollment
{
public int EnrollmentId { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Need this to remove the cascade convention.
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
Related
This question already has an answer here:
ManyToMany Relation in EF Core fluent API
(1 answer)
Closed 1 year ago.
I'm trying to make many to many relationships between two classes with EF Core fluent API. How can I change the foreign key names of the table between that will be created for this relationship?
For example, if we create a many-to-many relationship between the following two classes:
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public string Description { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
// Fluent Api
public class UserMap : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasKey(x => x.Id);
builder.ToTable("Users");
builder.HasMany<Course>(user => user.Courses)
.WithMany(course => course.Users);
}
}
A table is created like this:
What I want is to create the in-between table with the foreign key names I give during code-first.
I know I can write the in-between table as a class and change the names with a one-to-many relationship, but I want to do it with code-first.
Example taken from EF core documentation.
You might want to add a new class for the in-between table:
public class StudentCourse
{
public int FKStudentId { get; set; }
public int FKCourseId { get; set; }
public virtual Student Student { get; set; }
public virtual Course Course { get; set; }
}
and replace the navigation properties:
public virtual ICollection<Course> Courses { get; set; }
with
public virtual ICollection<StudentCourse> Courses { get; set; }
do the same in the Course class.
Then in the fluent Api, you can you now use the new class.
public class StudentConfiguration : IEntityTypeConfiguration<Student>
{
public void Configure(EntityTypeBuilder<Student> entity)
{
entity.HasKey(x => x.StudentId);
entity.HasMany(student => student.Courses)
.WithOne(course => course.Student)
.HasForeignKey(x => x.FKStudentId);
}
}
public class CourseConfiguration : IEntityTypeConfiguration<Course>
{
public void Configure(EntityTypeBuilder<Course> entity)
{
entity.HasKey(x => x.CourseId);
entity.HasMany(course => course.Students)
.WithOne(stud => stud.Course)
.HasForeignKey(x => x.FKCourseId);
}
}
EDIT: If you don't want to add in-between class, you may want to try this:
entity.HasMany(student => student.Courses)
.WithMany(course => course.Students)
.UsingEntity(x => x.Property("StudentsStudentId").HasColumnName("FKStudentId"));
entity.HasMany(course => course.Students)
.WithMany(stud => stud.Courses)
.UsingEntity(x => x.Property("CoursesCourseId").HasColumnName("FKCourseId"));
Note: "StudentsStudentId" and "CoursesCourseId" are generated by naming convention. So you may want to add migration first without the .UsingEntity() and inspect the generated migration.
I am getting the multiple cascade path error.
My models are 0..1 to 0..1
Or at least that is what i am trying to achieve.
My models look like this:
public class House
{
public Guid Id { get; set; }
[ForeignKey("User")]
public User Tenant { get; set; }
public class User
{
public House Home { get; set; }
public Guid Id { get; set; }
I have also this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<House>()
.HasOne(p => p.Tenant)
.WithOne(t => t.Home)
.OnDelete(DeleteBehavior.Restrict);
}
The error is not that i get an error when i delete a object, it simply will not create the DB at all.
I have already been told that this has been solved here:
Entity Framework Core cascade delete one to many relationship
However i am afraid to say that it does not appear to really address my issue, or if it does i do not understand how it solves it.
Any help would be greatly appreciated.
//update
Just to clarify. There are many users/tenants and many houses/homes.
Each of the tenants can have a house but does not always.
Each of the homes can have a tenant but does not always have one.
Try a very simple thing just to be sure when you start getting errors :
public class User{
public Guid Id{get; set;}
public string UserName{ get; set;}
}
public class House{
public Guid Id {get; set;}
public string StreetName {get; set;}
public UserId {get; set;} //this should generate a foreign key to the User table
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<House>().Property(c => c.UserId).IsRequired(false);
}
This should work to create houses that doesn't necessarely requires Users to exist.
So 1 house can have only 1 tenant. And 1 user can stay in only 1 home. Your relationship is 1 to 1. In this case, you can put foreign key in either table, but only 1 table. Then, you don't have issue of cycle cascade.
I suggest to put HouseId in User class, so that you can extend your app to allow 1 house to have many tenants easily later.
Here, one foreign key is declared :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<House>()
.HasOne(p => p.Tenant)
.WithOne(t => t.Home)
.OnDelete(DeleteBehavior.Restrict);
}
House.Tenant is linked to User.Home.
The tenant is the resident. Not what you want.
You need declare two foreign key distinct :
Fluent syntax
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasOne(u => u.Home).WithOne();
modelBuilder.Entity<House>().HasOne(h => h.Tenant).WithOne();
}
Attribute syntax
public class House
{
public Guid Id { get; set; }
[ForeignKey("TenantId")]
public User Tenant { get; set; }
}
public class User
{
[ForeignKey("HomeId")]
public House Home { get; set; }
public Guid Id { get; set; }
}
It turns out that Ivan was correct in one of his comments above. The issue was not actually in the code itself. In fact even simpler code will work in this case.
The error was caused by the migrations. I had to remove all migrations and start from scratch. Clearly some of the older model designs clashed with this new addition in a way that it was simply not possible to move forward. The databases had to be recreated from scratch.
Thank you everyone for your help. Especially Ivan and David.
Here are the simple models that do what i needed to do:
public class Home
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid? TenantId { get; set; }
public Tenant Tenant { get; set; }
}
public class Tenant
{
public Guid Id { get; set; }
public string Name { get; set; }
public Home Home { get; set; }
}
I do not even have to override the OnModelCreating.
Let's say I have an existing entity EmailNotification and another entity User. I want my EmailNotification to contain a list of users that it can be sent to. As I see it achievable from database perspective is that we create an additional table like following:
CREATE TABLE UserGroup (UserGroupID INT NOT NULL, UserID INT NOT NULL)
and add a UserGroupID column into EmailNotification.
However, the problem is that I can't think of a way how I can do this using EntityFramework Code First approach, so that I can have a list of Users inside EmailNotification. I want something like
EmailNotification
{
public virtual IEnumerable<User> Users { get; set; }
}
but I don't see how can I do the mentioned mapping using EntityFramework (preferably set it from DbContext, not FluentAPI).
In nutsell what you need I think is to create many to many relation between EmailNotification and User, if the case is that one user can be included in a lot of notifications and one notification can include a lot of users then you need following construct
public class User
{
public int UserId{ get; set; } /*your properties*/
public virtual ICollection<EmailNotification> Courses { get; set; }
}
public class EmailNotification
{
public int EmailNotificationId{ get; set; } /*your properties*/
public virtual ICollection<User> Courses { get; set; }
}
and to customize many to many table creation you can override OnModelCreating
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany<EmailNotification>(s => s.EmailNotification)
.WithMany(c => c.User)
.Map(cs =>
{
cs.MapLeftKey("UserId");
cs.MapRightKey("EmailNotificationId");
cs.ToTable("UserEmailNotifications");
});
}
At this case you have many-to-many relation:
MODELS:
public class EmailNotification
{
public int ID { get; set; }
//other stuff...
public virtual ICollection<User> Users { get; set; }
}
public class User
{
public int ID { get; set; }
//other stuff...
public virtual ICollection<EmailNotification> EmailNotifications { get; set; }
}
So, EF will implicitley create table: User2EmailNotification with columns: UserID and EmailNotificationID.
P.S. If you, all the same, want to create table UserGroup, it will be
hard (or not comfortable) to access Users from EmailNotification
class, instead you will should to declare UserGroup property inside
this class, so relation between Users and EmailNotifications will be
indirect.
I am developing a sample application where people can place bets on sports events and earn points. It has the following Entity Framework Code-First models:
public class Person
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class Race
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class RaceBet
{
[Key]
public int Id { get; set; }
[Required]
public int RaceId { get; set; }
[Required]
public int PersonId { get; set; }
[Required]
public int CompetitorId { get; set; }
public virtual Race Race { get; set; }
public virtual Person Person { get; set; }
public virtual Person Competitor { get; set; }
}
A Person can place a bet for a Race and he can bet on any other Person (Competitor).
The models will produce the following error:
Introducing FOREIGN KEY constraint 'FK_dbo.RaceBets_dbo.People_PersonId' on table 'RaceBets' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint. See previous errors.
I tried removing the OneToManyCascadeDeleteConvention, adding fluent configurations to prevent cascade delete for RaceBet and all other variations of the api, but everything fails.
modelBuilder
.Entity<RaceBet>()
.HasRequired(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PersonId)
.WillCascadeOnDelete(false);
How can I resolve this? Is the concept behind my models wrong?
Thanks!
Thanks to Oleg for his comment:
I can't to reproduce exception with this code: protected override void
OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Conventions.Add(new
System.Data.Entity.ModelConfiguration.Conventions.OneToManyCascadeDeleteConvention());
modelBuilder .Entity() .HasRequired(x => x.Person)
.WithMany() .HasForeignKey(x => x.PersonId)
.WillCascadeOnDelete(false); }
This fixed the model creation.
i created the below entitie.
[Table("User")]
public class User
{
[...]
[Key]
public String ID { get; set; }
[Column("CreatedBy")]
public virtual User CreatedBy { get; set; }
}
with:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasRequired(a => a.CreatedBy)
.WithRequiredPrincipal();
}
When the tables are created the columname of CreatedBy is "User_ID" although I used the Column attribute. The Columname should be "CreatedBy".
I changed the above code line to
modelBuilder.Entity<User>().HasRequired(a => a.CreatedBy)
.WithRequiredPrincipal().Map(e => e.MapKey("CreatedBy"))
and added in the pakage manager a new migration with add-migration. On Update-Database I get the message: Entities in 'DALDbContext.Users' participate in the 'User_CreatedBy' relationship. 0 related 'User_CreatedBy_Source' were found. 1 'User_CreatedBy_Source' is expected.
My tables are empty. Does someone know a workaround for this?
Not exactly sure what would have lead to your exact issue. However, I would probably just stick to either attribute based configuration or fluent api based configuration. In both cases its best practice to include both the navigation property and foreign key. Moving to this approach is nice and clean and may sort your problem.
Attribute based
public class User
{
[Key]
public string Id { get; set; }
[Column("CreatedBy")]
public int? CreatedByUserId { get; set; }
[ForeignKey("CreatedByUserId")]
public virtual User CreatedBy { get; set; }
}
Fluent api based
public class User
{
public string Id { get; set; }
public int? CreatedByUserId { get; set; }
public virtual User CreatedBy { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// add in configuration for table name, pk, etc
modelBuilder.Entity<User>()
.Property(p => p.CreatedByUserId)
.HasColumnName("CreatedBy");
modelBuilder.Entity<User>()
.HasOptional(x => x.CreatedBy)
.WithMany()
.HasForeignKey(x => x.CreatedByUserId);
}