I'm using ASP.NET MVC 4 Entity Framework 5 to generate a code first migration for a database structure of the below classes that have relationships between them. However I've encountered a problem where it's causing this error whenever I try to update my database from the migration:
Paste Bin of the migration file can be found here: http://pastebin.com/ngXacrKV
Error returned:
Introducing FOREIGN KEY constraint 'FK_dbo.Bookings_dbo.Rooms_RoomId' on table 'Bookings' 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.
Bunk.cs
public class Bunk
{
[Key]
public int BunkId { get; set; }
public BunkStatus BunkStatus { get; set; }
[ForeignKey("Room")]
public int RoomId { get; set; }
public virtual Room Room { get; set; }
// Added for convenience
public virtual ICollection<Booking> Bookings { get; set; }
}
Room.cs
public class Room
{
[Key]
public int RoomId { get; set; }
public string RoomName { get; set; }
public Gender RoomGender { get; set; }
public RoomStatus RoomStatus { get; set; }
public virtual ICollection<Bunk> Bunks { get; set; }
// Added for convenience
public virtual ICollection<Booking> Bookings { get; set; }
}
Bookings.cs
public class Booking
{
[Key]
public int BookingId { get; set; }
//[ForeignKey("UserProfile")]
//public int UserProfileId { get; set; }
//public UserProfile UserProfile { get; set; }
[ForeignKey("Bunk")]
public int BunkId { get; set; }
public Bunk Bunk { get; set; }
public int Duration { get; set; }
[ForeignKey("Preferred_Room")]
public int RoomId { get; set; }
public Room Preferred_Room { get; set; }
public Decimal Price { get; set; }
public BookingStatus BookingStatus { get; set; }
}
What would the best work around be to remove this issue without causing too much disturbance to the original class structure. I'm not too worried about adding new joining tables as long as I can still access the code in a Lazy loading way in my controllers/view models.
you can try to specify Fluent API to set no cascade delete
public class YOURContext: DbContext{
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
// here is where fluent API goes.
// I suspected the error is EF wanting a NO cascade delete. Hence the suggestion to try
entity<Booking>.HasOptional(t => t.Bunk)
.WithOptionalPrincipal()
.WillCascadeOnDelete(false); // <<<<<< this is the option to try.
// you may also need to try the same with Preferred_Room as well.
If no cascade delete is required then in Database context class we can set cascade delete to false for all the relationships by overriding OnModelCreating method as given below. Default EF makes it cascade delete that is why you are getting exception of cycles or multiple delete path.
public class MyContext: DbContext
{
//db sets defined
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PrimaryKeyTable>().HasMany(x => x.ChildTableCollection).WithRequired(x =>
Key).WillCascadeOnDelete(false);
//In your case
modelBuilder.Entity<Bunk>().HasMany(x => x.Bookings).WithRequired(x =>
x.BunkId).WillCascadeOnDelete(false);
// same for room if required.
}
}
Related
I have two classes that relate to one another (one-to-many) and I thought I had the properties setup correctly, but when I run the Update-Database command for my migration, I get the following error:
Introducing FOREIGN KEY constraint
'FK_dbo.ParentEnrollment_dbo.CellGroup_CellGroupID' on table
'ParentEnrollment' 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 or index. See previous
errors.
My two classes:
[Table("CellGroup")]
public class CellGroup : BaseEntity
{
public Guid AcademicYearID { get; set; }
[ForeignKey("AcademicYearID")]
public virtual AcademicYear AcademicYear { get; set; }
public Guid LeaderID { get; set; }
[ForeignKey("LeaderID")]
public virtual Parent Leader { get; set; }
public Guid PreviousGroupID { get; set; }
[ForeignKey("PreviousGroupID")]
public virtual CellGroup PreviousGroup { get; set; }
public string Name { get; set; }
public int MaximumSize { get; set; }
public virtual ICollection<ParentEnrollment> Parents { get; set; }
}
and
[Table("ParentEnrollment")]
public class ParentEnrollment : BaseEntity
{
public Guid ParentID { get; set; }
[ForeignKey("ParentID")]
public virtual Parent Parent { get; set; }
public Guid AcademicYearID { get; set; }
[ForeignKey("AcademicYearID")]
public virtual AcademicYear AcademicYear { get; set; }
public bool FirstTimeEnrolling { get; set; }
public string HSLDAAccountNumber { get; set; }
public DateTime HSLDARenewalDate { get; set; }
public string CurrentChurch { get; set; }
public string CurrentChurchContact { get; set; }
public string CurrentChurchPhone { get; set; }
public Guid CellGroupID { get; set; }
[Required]
[ForeignKey("CellGroupID")]
public virtual CellGroup CellGroup { get; set; }
public bool VolunteerBuyOut { get; set; }
public Guid VolunteerPositionID { get; set; }
[ForeignKey("VolunteerPositionID")]
public virtual VolunteerPosition VolunteerPosition { get; set; }
public string VolunteerPositionNotes { get; set; }
public virtual ICollection<StudentEnrollment> StudentEnrollments { get; set; }
}
I only have the Parents property on the CellGroup class so I can easily access the list of enrollments in that cell group. I tried to remove the property to see if it cleared up the warning/error, but it did not. Can someone spot where I have gone wrong with my model(s)?
This error says that you cannot introduce a foreign key from table ParentEnrollment to table CellGroup that has cascading delete enabled, because this will create multiple cascade paths, which is not allowed on SQL Server.
According to the code you posted both tables have relations to a table Parent as well as AcademicYear, which are on non nullable FK columns, so EF will enable cascading on delete by default. With another FK from ParentEnrollment to CellGroup there would be multiple cascade paths, e.g. Parent to CellGroup to ParentEnrollment and Parent to ParentEnrollment, and this is causing your error. Removing the Parent property won't solve this because there still is the same cascading path problem starting from table AcademicYear.
So you have to disable cascading delete for your foreign key, which has to be done using Fluent API in your DbContext like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ParentEnrollment>()
.HasRequired(m => m.CellGroup)
.WithMany(m => m.Parents)
.HasForeignKey(m => m.CellGroupID)
.WillCascadeOnDelete(false);
}
I'm trying to create a linked table that will allow me to have a many to many relationship between my product and accessory tables.
My classes are like this:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Accessory> Accessories { get; set; }
}
public class Accessory {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Machine> Machine { get; set; }
}
public class Type {
public int Id { get; set; }
public string Name { get; set; }
}
The same accessory can be on a product more than once if it is a different type, which will be determined in the link table. Something like this:
public class ProductAccessoryLink {
public int productId {get; set;}
public int accessoryId {get; set;}
public int typeId {get; set}
public int sort {get; set;}
public string notes {get; set}
}
Is this the right approach.
EDIT
This is the error I'm getting when I run update-database:
Introducing FOREIGN KEY constraint
'FK_dbo.ProductAccessoryLinks_dbo.Types_TypeId' on table
'ProductAccessoryLinks' 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.
This is the sql causing the error: ALTER TABLE
[dbo].[ProductAccessoryLinks] ADD CONSTRAINT
[FK_dbo.ProductAccessoryLinks_dbo.Types_TypeId] FOREIGN KEY ([TypeId])
REFERENCES [dbo].[Types] ([Id]) ON DELETE CASCADE
In your case you need to map explicitly the junction table. Your model would be like this:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductAccessoryLink> ProductAccessoryLinks { get; set; }
}
public class Accessory
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductAccessoryLink> ProductAccessoryLinks { get; set; }
}
public class Type
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductAccessoryLink
{
public int ProductId { get; set; }
public int AccessoryId { get; set; }
public int TypeId { get; set; }
public int sort { get; set; }
public string notes { get; set; }
public virtual Type Type { get; set; }
public virtual Product Product { get; set; }
public virtual Accessory Accessory { get; set; }
}
And you could configure the relationships overriding the OnModelCreating method on your context this way:
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductAccessoryLink>().HasKey(i => new { i.ProductId, i.AccesoryId, i.TypeId});
modelBuilder.Entity<ProductAccessoryLink>()
.HasRequired(i => i.Product)
.WithMany(k => k.ProductAccessoryLinks)
.HasForeignKey(i=>i.ProductId);
modelBuilder.Entity<ProductAccessoryLink>()
.HasRequired(i => i.Accesory)
.WithMany(k => k.ProductAccessoryLinks)
.HasForeignKey(i=>i.AccesoryId);
modelBuilder.Entity<ProductAccessoryLink>()
.HasRequired(i => i.Type)
.WithMany()
.HasForeignKey(i=>i.TypeId);
}
EF lets you configure directly many-to-many relationships of the way you were attempting to. Thereby EF is responsible for build a join table in the database with the appropriate keys of the tables it’s joining. (The keys are both primary keys of the join table and foreign keys pointing to the joined tables). That lets you to get your data across the join table without you having to be aware of its presence. But when you want to personalize that table (adding, for example, some additional properties), you need to map it explicitly as I show above.
Update
That exception is caused when you have multiple paths of cascade deletes that could end trying to delete the same row in the Types table. To resolve that problem I recommend you check my answer in this post
I got a model where I have Business, Workers and Jobs, also there are WorrkerReports for a business in a specific Job for a Worker.
public class Business
{
public int BusinessId { get; set; }
public List<Job> jobs{ get; set; }
public List<Worker> workers { get; set; }
}
public class Worker
{
public int WorkerId{ get; set; }
public string Name{ get; set; }
public int BusinessId{ get; set; }
}
public class Inspector
{
public int InspectorId { get; set; }
public String Name{ get; set; }
}
public class Job
{
public int JobId { get; set; }
public string NameOfJob{ get; set; }
public int BusinessId { get; set; }
}
public class WorkerReport
{
public int WorkerReportId { get; set; }
public int InspectorId { get; set; }
public int JobId { get; set; }
public int WorkerId { get; set; }
public Job job{ get; set; }
public Worker worker{ get; set; }
public Inspector Inspector { get; set; }
}
But when the model is generating i got this error:
'FK_dbo.WorkerReports_dbo.Workers_WorkerId' on table 'WorkerReports' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I don´t understand why i am getting this error,because i don´t see where the cycle is being produced. Have anyone some idea? Where is the circle being caused?
Thanks a lot, provide help to a dessesperated programmer.
You have two delete cascade path on WorkerReport.
Business -> Worker -> WorkerReport
Business -> Job -> WorkerReport
By default all the relationship are required and the convention enable all the delete cascade from OneToManyCascadeDeleteConvention. The solution is to remove at least one of the cascade manually from Worker or Job.
modelBuilder.Entity<WorkerReport>()
.HasRequired(x => x.job) // HasOptional for nullable FK
.WithMany()
.WillCascadeOnDelete(false);
// modelBuilder.Entity<WorkerReport>()
// .HasRequired(x => x.worker) // HasOptional for nullable FK
// .WithMany()
// .WillCascadeOnDelete(false);
Or just disable all delete cascade by removing the default delete cascade convention.
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Put it inside the the DbContext class.
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// here
}
}
How do I specify ON DELETE NO ACTION Foreign Key Constraint in my model designs?
At present, I have:
public class Status
{
[Required]
public int StatusId { get; set; }
[Required]
[DisplayName("Status")]
public string Name { get; set; }
}
public class Restuarant
{
public int RestaurantId { get; set; }
[Required]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Telephone { get; set; }
[Required]
public int StatusId { get; set; }
public List<Menu> Menus { get; set; }
// NAVIGATION PROPERTIES
public virtual Status Status { get; set; }
}
public class Menu
{
public int MenuId { get; set; }
[Required]
public int RestaurantId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int StatusId { get; set; }
// NAVIGATION PROPERTIES
public virtual Status Status { get; set; }
public virtual Restaurant Restaurant { get; set; }
}
And my DbContext:
public class MenuEntities : DbContext
{
public DbSet<Status> Statuses { get; set; }
public DbSet<Restaurant> Restaurants { get; set; }
public DbSet<Menu> Menus { get; set; }
}
As you can see:
a Restaurant has many menus
a Restaurant has one status
a Menu belongs to 1 restaurant
Both Restaurants and Menus have 1 status. (Live, Invisible, Draft)
Naturally, if a status is deleted, I certainly don't want to cascade as this will muck everything up.
UPDATE:
Mark Oreta mentions using the following in his example below:
modelBuilder.Entity<FirstEntity>()
.HasMany(f => f.SecondEntities)
.WithOptional()
.WillCascadeOnDelete(false);
Where do I put this code? Within my MenuEntities / DbContext class?
Can anybody provide an example of this being used?
UPDATE:
Got this bit working now, however this has created a multiplicity constraint error when trying to seed the DB...
Multiplicity constraint violated. The role 'Menu_Status_Source' of the relationship 'LaCascadaWebApi.Models.Menu_Status' has multiplicity 1 or 0..1.
My Database Initialiser:
http://pastebin.com/T2XWsAqk
You can either disable it for your entire context by removing the cascade delete convention in the OnModelCreating method:
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
or, you can do it per relationship using a fluent mapping (also in the OnModelCreating):
EDIT: you would put it in your menu entities
public class MenuEntities : DbContext
{
public DbSet<Status> Statuses { get; set; }
public DbSet<Restaurant> Restaurants { get; set; }
public DbSet<Menu> Menus { get; set; }
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<Menu>()
.HasRequired( f => f.Status )
.WithRequiredDependent()
.WillCascadeOnDelete( false );
modelBuilder.Entity<Restaurant>()
.HasRequired( f => f.Status )
.WithRequiredDependent()
.WillCascadeOnDelete( false );
}
}
Just make the FK property nullable, then the cascade delete will be gone.
public int? StatusId { get; set; }
After making the changes to the model, make sure you regenerate the migration file by adding the -Force parameter.
Add-Migration MigrationName -Force
add this line to end of the field in the context;
.OnDelete(DeleteBehavior.Restrict);
Put this into your MenuEntities class (class that descend from DbContext):
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
I am a bit confused as to why I am getting this error:
Introducing FOREIGN KEY constraint 'FK_QuestionTerms_Terms_TermId'
on table 'QuestionTerms' 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 have a class Question and a class Term, Questions may have any number of Terms associated with them, and Terms may have any number of Questions associated with them. So I am attempting to create a many to many relationship between the two. First I attempted to use convention, and I am allowing EntityFramework to create the database. This is the Question class
public class Question
{
public Guid Id { get; set; }
public int QuestionNumber { get; set; }
public string StatementHtml { get; set; }
public string AnswerHeaderHtml { get; set; }
public string NotesHtml { get; set; }
public Guid CategoryId { get; set; }
public Guid CourseId { get; set; }
public Guid QuestionTypeId { get; set; }
public Guid? SimulationId { get; set; }
public Guid? SimulationTabId { get; set; }
public ICollection<Term> Terms { get; set; }
public ICollection<ReferenceItem> ReferenceItems { get; set; }
}
And here is the Term Class
public class Term
{
public Guid Id { get; set; }
public string Name { get; set; }
public string StatementHtml { get; set; }
public string Authority { get; set; }
public Guid ProductId { get; set; }
public Product Product { get; set; }
public ICollection<Question> Questions { get; set; }
}
I have also attempted to override OnModelCreating as follows, both process result is the exact same error code.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Question>()
.HasMany(q => q.Terms)
.WithMany(t => t.Questions)
.Map(x =>
{
x.MapLeftKey("QuestionId");
x.MapRightKey("TermId");
x.ToTable("QuestionTerms");
});
}
The problem is that a cacade delete would go back and forth between the tables.
For example first deleting term A which would delete question 1,2 and 3. Question 1 was also used in term B so term B must be deleted .....
It therefore stops you creating such constraints.
There is a good coverage of how to fix it here: Entity Framework 4.1 InverseProperty Attribute and ForeignKey
Edit
This could be a side effect of other problems. You should start with a much simpler model and then gradually build it up.
For example:
Why do you have both ProductId and product
Why CategoryId and not Category
...
Try adding it in your OnModelCreating() method
modelBuilder.Entity<Question>().HasRequired(oo => oo.Term).WithMany(oo => oo.Questions).WillCascadeOnDelete(false);