I'm currently using EF Code First 4.3 with migrations enabled, but automatic migrations disabled.
My question is simple, is there a data annotations equivalent of the model configuration .WillCascadeOnDelete(false)
I would like to decorate my class so that the foreign key relationships do NOT trigger a cascading delete.
Code sample:
public class Container
{
public int ContainerID { get; set; }
public string Name { get; set; }
public virtual ICollection<Output> Outputs { get; set; }
}
public class Output
{
public int ContainerID { get; set; }
public virtual Container Container { get; set; }
public int OutputTypeID { get; set; }
public virtual OutputType OutputType { get; set; }
public int Quantity { get; set; }
}
public class OutputType
{
public int OutputTypeID { get; set; }
public string Name { get; set; }
}
I Would like to do something like this:
public class Output
{
[CascadeOnDelete(false)]
public int ContainerID { get; set; }
public virtual Container Container { get; set; }
[CascadeOnDelete(false)]
public int OutputTypeID { get; set; }
public virtual OutputType OutputType { get; set; }
public int Quantity { get; set; }
}
This way i would be able to scaffold the migration correctly. which scaffolds the foreign key relationships to be cascade deleted at the moment.
Any ideas, other than using Model Configuration?
No there is no such equivalent. You must use fluent API to remove cascade delete selectively or you must remove OneToManyCascadeDelete convention to remove it globally.
Create a mapping class (the fluent syntax) and use the code below:
// add relationships "Post" and "User" to a "Comment" entity
this.HasRequired(t => t.Post)
.WithMany(t => t.Comments)
.HasForeignKey(d => d.PostID)
.WillCascadeOnDelete(false); // <---
this.HasOptional(t => t.User)
.WithMany(t => t.Comments)
.HasForeignKey(d => d.UserID)
.WillCascadeOnDelete(false); // <---
Here's a nice post on how to set up fluent mappings if you need more info.
Just make the FK property nullable can prevent cascade delete from happening:
public int? OutputTypeID { get; set; }
Related
I have a similar problem to the one posted here:
Entity Framework Code First - two Foreign Keys from same table, however it's very old and doesn't apply to Core and I can't get the suggestions to work for me.
Basically, I'm trying to create a fixture table which will have two foreign keys to the team table. A fixture is made up of a home team and an away team. Having nullable fields isn't an option.
Consider a fixture, with two teams.
public class Fixture
{
public int Id { get; set; }
public Team HomeTeam { get; set; }
public int HomeTeamId { get; set; }
public Team AwayTeam { get; set; }
public int AwayTeamId { get; set; }
public virtual Team HomeTeam { get; set; }
public virtual Team AwayTeam { get; set; }
}
public class Team
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public String Name { get; set; }
public ICollection<Fixture> HomeFixtures { get; set; } = new List<Fixture>();
public ICollection<Fixture> AwayFixtures { get; set; } = new List<Fixture>();
}
I get the error...
Unable to determine the relationship represented by navigation property 'Fixture.HomeTeam' of type 'Team'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
So I tried to add some OnModelCreating code in the database context:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Fixture>()
.HasOne(m => m.HomeTeam)
.WithMany(t => t.HomeFixtures)
.HasForeignKey(m => m.HomeTeamId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Fixture>()
.HasOne(m => m.AwayTeam)
.WithMany(t => t.AwayFixtures)
.HasForeignKey(m => m.AwayTeamId)
.OnDelete(DeleteBehavior.Restrict);
}
Then I got the error:
Introducing FOREIGN KEY constraint 'FK_Fixtures_Teams_HomeTeamId' on table 'Fixtures' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Can anyone help with getting this setup please?
Thanks.
public class Fixture
{
public int Id { get; set; }
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
[ForeignKey("HomeTeamId")]
public virtual Team HomeTeam { get; set; }
[ForeignKey("AwayTeamId")]
public virtual Team AwayTeam { get; set; }
}
This way navigation will work. Also as suggested by #Ivan remove duplicate getters and setters.
Solution below worked for me for EF Core 3:
Make sure to make foreign keys nullable
Specify default behavior on Delete
public class Match
{
public int? HomeTeamId { get; set; }
public int? GuestTeamId { get; set; }
public float HomePoints { get; set; }
public float GuestPoints { get; set; }
public DateTime Date { get; set; }
public Team HomeTeam { get; set; }
public Team GuestTeam { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Match>()
.HasRequired(m => m.HomeTeam)
.WithMany(t => t.HomeMatches)
.HasForeignKey(m => m.HomeTeamId)
.OnDelete(DeleteBehavior.ClientSetNull);
modelBuilder.Entity<Match>()
.HasRequired(m => m.GuestTeam)
.WithMany(t => t.AwayMatches)
.HasForeignKey(m => m.GuestTeamId)
.OnDelete(DeleteBehavior.ClientSetNull);
}
When I run application I have this error:
PossibleAnswer_Question_Source: : Multiplicity is not valid in Role
'PossibleAnswer_Question_Source' in relationship
'PossibleAnswer_Question'. Because the Dependent Role properties are
not the key properties, the upper bound of the multiplicity of the
Dependent Role must be '*'.
How to resolve it?
Model classes for Question and PossibleAnswer:
public class Question
{
public int ID { get; set; }
public string Text { get; set; }
public bool IsAssociatedWithProfessor { get; set; }
public bool IsAssociatedWithAssistant { get; set; }
public virtual ICollection<PossibleAnswer> PossibleAnswers { get; set; }
}
public class PossibleAnswer
{
public int ID { get; set; }
public string Text { get; set; }
public int QuestionID { get; set; }
[ForeignKey("QuestionID")]
public virtual Question Question { get; set; }
}
And I put this in OnModelCreating(DbModelBuilder modelBuilder):
modelBuilder.Entity<PossibleAnswer>()
.HasRequired(f => f.Question)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
The problem is you are not configuring a one-to-many relationship in the OnModelCreating method (that is a one-to-one configuration). To achieve what you want, you could do this:
modelBuilder.Entity<PossibleAnswer>()
.HasRequired(pa => pa.Question)
.WithMany(q=>q.PossibleAnswers)
.HasForeignKey(pa=>pa.QuestionID)
.WillCascadeOnDelete(false);
This way, you don't need to use the ForeignKey attribute on the Question navigation property. Is a good practice try to not merge Fluent Api with Data Annotations
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());
}
As far as i know, i have two way to implement many-to-many relation in asp.net mvc using code-first.
1- Fluent Api
public class HrPerson
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HrPersonTitle> HrPersonTitle { get; set; }
}
public class HrPersonTitle
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<HrPerson> HrPerson { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<HrPerson>()
.HasMany(s => s.HrPersonTitle)
.WithMany(c => c.HrPerson)
.Map(t =>
{
t.MapLeftKey("HrPersonId")
.MapRightKey("HrPersonTitleId")
.ToTable("HrMapPersonTitle");
});
}
2-Custom Mapping Table
public class HrPerson
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HrMapPersonTitle> HrMapPersonTitle { get; set; }
}
public class HrPersonTitle
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<HrMapPersonTitle> HrMapPersonTitle { get; set; }
}
public class HrMapPersonTitle
{
public int Id { get; set; }
public int HrPersonId { get; set; }
public int HrPersonTitleId { get; set; }
public virtual HrPerson HrPerson { get; set; }
public virtual HrPersonTitle HrPersonTitle { get; set; }
public string Note { get; set; }
public bool Deleted { get; set; }
}
My questions:
If i choose second way, i am not able to reach HrPersonTitle.Name property from HrPerson model in the view. How can i reach the properties ?
If i choose the first way i can reach the HrPersonTitle.Name but i am not able to add more property in the map file ? How can i add more properties?
Regards.
When you create a M2M without a payload (just the foreign key relationships, no extra data), EF collapses the relationship so that you can query directly without having to explicitly go through the join table. However, if you need a payload, then EF can no longer manage the relationship in this way.
So, if you want to get the title, you have to go through HrMapPersonTitle:
#foreach (var title in Model.HrMapPersonTitle)
{
#title.HrPersonTitle.Name
}
Both these methods seem overkill maybe. I don't know your full intentions however I implement this all the time at work and I use the following:
public class HrPerson
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HrPersonTitle> HrPersonTitles { get; set; }
}
public class HrPersonTitle
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<HrPerson> HrPersons { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<HrPerson>()
.HasMany(s => s.HrPersonTitles)
.WithMany(c => c.HrPersons);
}
If you are using code first and you try and access either mapping within the DbContext it should Lazy Load your information and every property should be accessible.
I do have one question though. Are you sure it should be many to many, do they really have multiple titles?
I'm trying to create a list of train journeys (among other things) in MVC, using code first Entity Framework and wondered how I could map foreign keys for the stations. The Journey model/table will have a DepartureStationID and an ArrivalStationID which will be foreign keys linking to one table/model, called Station.
Here is the code for both these models:
public class Station
{
public int StationID { get; set; }
public string StationName { get; set; }
public string StationLocation { get; set; }
}
public class Journey
{
public int JourneyID { get; set; }
public int DepartureID { get; set; }
public int ArrivalID { get; set; }
public int OperatorID { get; set; }
public string JourneyCode { get; set; }
public virtual Operator Operator { get; set; }
public virtual Station DepartureStation { get; set; }
public virtual Station ArrivalStation { get; set; }
}
There is another foreign key value in there, namely Operator and that has mapped successfully, but the departure and arrivals haven't, and return null values in the view: (#Html.DisplayFor(modelItem => item.DepartureStation.StationName).
When I looked in the database, there had been two additional fields created by EF:
DepartureStation_StationID
ArrivalStation_StationID
And the SQL relationship was between the station table and the two fields above, rather than DepartureID and ArrivalID
So, my question is - Do I need to do something different in the model when referencing the same table for two fields? I don't know why those additional fields were added so I presume I've set up the model incorrectly.
Thanks
For completeness, here's the same thing with fluent configuration.
public class MyDb : DbContext
{
public DbSet<Journey> Journeys { get; set; }
public DbSet<Operator> Operators { get; set; }
public DbSet<Station> Stations { get; set; }
protected override void OnModelCreating(DbModelBuilder builder)
{
builder.Entity<Journey>()
.HasRequired(j => j.DepartureStation)
.WithMany()
.HasForeignKey(j => j.DepartureID);
builder.Entity<Journey>()
.HasRequired(j => j.ArrivalStation)
.WithMany()
.HasForeignKey(j => j.ArrivalId);
// ... Same thing for operator ...
base.OnModelCreating(builder);
}
}
Edit: To address your above comment about the cascade delete, you can add .WillCascadeOnDelete(false) after .HasForeignKey() and that might help (although you'll then have to delete Journey records manually)
Add the folowing attributes on your navigation properties :
public class Journey
{
public int JourneyID { get; set; }
public int DepartureID { get; set; }
public int ArrivalID { get; set; }
public int OperatorID { get; set; }
public string JourneyCode { get; set; }
[ForeignKey("OperatorID")]
public virtual Operator Operator { get; set; }
[ForeignKey("DepartureID")]
public virtual Station DepartureStation { get; set; }
[ForeignKey("ArrivalID")]
public virtual Station ArrivalStation { get; set; }
}
And of course you need to regenerate your database in order to apply the new configuration.
Hope this will help.