Multiple DataAnnotation ForeignKeys cycles or multiple cascade paths Exception - c#

I'm trying to understand and trust in EF.
I have a database with tables and I'm trying to replicate most of the structure with EF Code-First technology as an exercise to practice their basics.
All my classes have 'Hb' as a prefix in their names.
I want to write the object references on other classes without the 'Hb' on the property name.
I'm follow instructions of this site
The first implementation cause exceptions like:
Introducing FOREIGN KEY constraint
'FK_Common.HbZipcode_Common.HbCountry_CountryId' on table 'HbZipcode'
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.
[Required, ForeignKey("Country")]
public int CountryId { get; set; }
public virtual HbCountry Country { get; set; }
[Required, ForeignKey("State")]
public int StateId { get; set; }
public virtual HbState State { get; set; }
[Required, ForeignKey("City")]
public int CityId { get; set; }
public virtual HbCity City { get; set; }
// This Foreignkey never throw exception
[ForeignKey("Neighborhood")]
public int? NeighborhoodId { get; set; }
public virtual HbNeighborhood Neighborhood { get; set; }
I'm doing something wrong and I cannot see.
If anyone could help me it would be nice.
Edit:
After applying the solution passed by the plushpuffin, everything went as expected.
Here's the code:
var modelConfig = dbModelBuilder.Entity<HbZipcode>();
modelConfig
.HasRequired(zc => zc.Country)
.WithMany(c => c.Zipcodes)
.HasForeignKey(zc => zc.CountryId)
.WillCascadeOnDelete(false);
modelConfig
.HasRequired(zc => zc.State)
.WithMany(s => s.Zipcodes)
.HasForeignKey(zc => zc.StateId)
.WillCascadeOnDelete(false);
modelConfig
.HasOptional(zc => zc.Neighborhood)
.WithMany(n => n.Zipcodes)
.HasForeignKey(zc => zc.NeighborhoodId)
.WillCascadeOnDelete(false);
Delete cascade on HbZipcode now occurs only when HbCity is deleted

What's happening here is that HbZipCode has foreign keys to multiple tables, and you can't create 2+ foreign key constraint with ON CASCADE DELETE that have multiple ways of deleting the same rows from the same table.
It's likely that your entity classes are set up such that when HbZipCode is deleted, it cascades the delete to HbNeighborhood, then HbCity, then HbState, and then HbCountry. If you added another foreign key to HbZipCode with ON CASCADE DELETE pointing to HbCity, deleting an HbZipCode record would result in a direct cascade delete to HbNeighborhood and a direct cascade delete to HbCity, but the HbNeighborhood being deleted would also cause a cascade delete to HbCity.
What you need to do is resolve the multiple cascade delete paths to HbCity and the other entity types by turning off CASCADE DELETE for most of them.
See this MSDN page on fluent configuration.
It's likely that you want something like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<HbZipCode>()
.HasRequired(t => t.HbCity)
.WithMany(t => t.HbZipCodes)
.HasForeignKey(t => t.CityId)
.WillCascadeOnDelete(false);
}

Related

Delete cascade a tree in EF core (SQL Server)

I have the following code:
public class A
{
public Guid Id { get; set; }
public List<A> AList { get; set; }
}
Configured with fluent Api:
modelBuilder
.Entity<A>()
.HasMany(x => x.AList)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
Creating a Migration works fine but when doing "Update-Database"
I get the following error:
"Introducing FOREIGN KEY constraint 'FK_A_A_AId' on table 'A' 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."
How do I delete cascade a tree in EF Core then?
You can’t “automatically” cascade delete; you must recursively delete child records in client code or create an INSTEAD OF DELETE trigger in the DB with recursive CTE to query for and delete all child records
SO answer for recursively removing children in client code:
Implementing Cascade Delete in a self referencing table in EF Core 2
SO answer for trigger:
On delete cascade for self-referencing table
Alternatively, implement the trigger logic as a stored procedure and configure EFCore to use that sproc for deletes (not built in functionality like EF6 for MapToStoredProcedures so some effort is required:
EF Core - What is MapToStoredProcedures replacement in EF Core 3.1 or 5
With the way the current model is, the same instance of A can appear multiple times in the hierarchy. This would prevent even a database engine from doing a cascade delete.
An alternative would be (depending on your requirement) to add on a "parent" foreign key property to your entity, like the following:
public class A
{
public Guid Id { get; set; }
public Guid? ParentId { get; set; }
public A Parent { get; set; }
public List<A> Children { get; set; }
}
And in your model builder...
modelBuilder.Entity<A>
.HasKey(x => x.Id);
modelBuilder.Entity<A>
.HasMany(x => x.Children)
.WithOne(x => x.Parent)
.HasForeignKey(x => x.ParentId)
.OnDelete(DeleteBehavior.Cascade);
If you did need the same exact instance of a node in the hierarchy appearing in multiple places in the hierarchy, then some additional modeling would be needed to make it work.

Entity Framework throwing conflict with reference constraint while deleting

I have the following entity declared
public class TransactionEvent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public virtual List<TransactionSignInError> SignInErrors { get; set; }
}
And the context
public class TransactionAuditsDbContext : DbContext
{
public virtual DbSet<TransactionEvent> TransactionEvents { get; set; }
}
Now when I try to delete a transaction event, I want the relevant SignInError rows to be deleted as well. I realize I can do this by using cascade on delete if I had set that up in the context, too late for that now.
How can I delete successfully a transaction? I'm getting this error.
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TransactionSignInErrors_dbo.TransactionEvents_TransactionEvent_Id". The conflict occurred in database "db", table "dbo.TransactionSignInErrors", column 'TransactionEvent_Id'
I have tried clearing the SignInErrors list before deleting, that did get rid of the above error but left NULLs in the TransactionSignInErrors table.
What you want, is "Cascade on Delete": if a TransactionEvent is deleted, then you also want that all its TransactionSignInErrors are deleted.
This works on a one-to-many relation, this does not work on a many-to-many-relation.
If you have a one-to-many relation between TransactionEvents and TransactionSignInErrors, and you followed the entity framework conventions, you will have classes like
public class TransactionEvent
{
public Guid Id { get; set; }
...
// Every TransactionEvent has zero or more TransactionSignInErrors (one-to-many)
public virtual ICollection<TransactionSignInError> SignInErrors { get; set; }
}
public class TransactionSignInError
{
public Guid Id { get; set; }
...
// Every TransactionSignInError belongs to exactly oneTransactionEvent, using foreign key
public Guid TransactionEventId {get; set;}
public virtual TransactionEvent TransactionEvent { get; set; }
}
public class TransactionAuditsDbContext : DbContext
{
public DbSet<TransactionEvent> TransactionEvents { get; set; }
public DbSet<TransactionSignInError> TransactionSignInErrors {get; set;}
}
This is all that entity framework needs to know to detect the tables, the columns in the tables and the one-to-many relation between these two tables.
In entity framework the non virtual properties represent the columns in the table, the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
The foreign key TransactionEventId is a real column, hence it is non-virtual. TransactionEvent is not a real column, it only refers to the relation, hence it is declared virtual.
If you stick to the conventions, there is no need for attributes, nor fluent API. Only if you want non-default identifiers for tables, columns, column types or non-default behaviour for table relations, you might need attributes or fluent API.
Default behaviour is cascade on delete: if you delete a TransactionEvent, all its TransactioinSigninErrors are also deleted.
I'm not sure whether your problems arise because you have a GUID as primary key, instead of an int. If you want, you can inform entity framework about your one-to-many relation and cascade on delete in OnModelCreating:
protected override void OnModelCreating (DbModelBuilder modelBuilder)
{
// Every TransactionEvent has zero or more TransactionSignInErrors
// Every TransactionSignInError belongs to exactly one TransactionEvent
// using foreign key TransactionEventId.
// Also: cascade on delete:
modelBuilder.Entity<TransactionEvent>()
.HasMany(transactionEvent => transactionEvent.TransactionSignInErrors)
.WithRequired(transactionSignInError => transactionSignInError.TransactionEvent)
.HasForeignKey(transactionSignInError => transactionSignInError.TransactionEventId)
.WillCascadeOnDelete();
So three major changes to your code:
The DbSets in the DbContext are non-virtual
Added the table TransactionSignInErrors to your DbContext
If that is not enough for CascadeOnDelete (check this first!) add fluent API.
Small change: Use ICollection instead of IList.
Rationale: if you fetch a TransactionEvent with its TransactionSignInErrors, does TransactionEvent.SignInErrors[4] have a defined meaning? Wouldn't it be better if people have no access to methods that they don't know what they really mean?
If you want to use a cascade delete you have to include the children:
var removingRow=_context.Set<TransactionEvent>()
.Include(x=> x.SignInErrors )
.Where(x => x.Id ==id)
.FirstOrDefault();
if(removingRow != null)
{
_context.Remove(removingRow);
_context.SaveChanges();
}
Your post has the tag of entity-framework. I'm not sure how things work with Entity Framework 6 or previous versions, but with Entity Framework Core you can solve your issue like -
var tEvent = dbCtx.TransactionEvents
.Include(p=> p.SignInErrors)
.FirstOrDefault(p => p.Id == id);
foreach (var error in eventx.SignInErrors)
{
dbCtx.SignInErrors.Remove(error);
}
dbCtx.TransactionEvents.Remove(tEvent);
dbCtx.SaveChanges();

Composite Key EF Core getting error when using Fluent Api

So I have the following class in Entity Framework Core. I am trying to do a code first migration and can't for the life of me figure out how to make the fluent API for this work.
public class Participants
{
public Activity Activity { get; set; } //Class with Id and Name of Activity
public ApplicationUser Participant { get; set; }
[Key]
[Column(Order = 1)]
public int ActivityId { get; set; }
[Key]
[Column(Order = 2)]
public string ParticipantId { get; set; }
}
In EF6 I was able to do this in OnModelCreating to get it to work fine.
modelBuilder.Entity<Attendance>()
.HasRequired(a => a.Activity)
.WithMany()
.WillCascadeOnDelete(false);
But in EF Core I get
" Entity type 'Participants' has composite primary key defined with data annotations. To set composite primary key, use fluent API."
I have tried using
modelBuilder.Entity<Participants>().HasKey(p => new {p.Activity, p.Participant});
But, that just leads to
Introducing FOREIGN KEY constraint 'FK_Participants_AspNetUsers_ParticipantId' on table 'Participants' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
If there is a better way to do the whole thing I'm open to suggestions. If you have pluralsight subscription, I'm basically trying to get "Become a Full Stack Developer" by Mosh Hamedani to work in EF core. The example is in "13-full-stack-fundamentals" folder.
UPDATE: Also tried
modelBuilder.Entity<Participants>()
.HasOne(p => p.Activity)
.WithMany()
.OnDelete(DeleteBehavior.Cascade);
Still got
"Entity type 'Participants' has composite primary key defined with data annotations. To set composite primary key, use fluent API."
UPDATE 2: After trying Roy's suggestion this is what I'm getting
Introducing FOREIGN KEY constraint 'FK_Participants_AspNetUsers_ParticipantId' on table 'Participants' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
UPDATE 3: In the Migration
I removed one of the OneDelete: ReferntialAction.Cascade and it worked. I removed the one off of FK_Participants_AspNetUsers_ParticipantId.
I also changed to this in my OnModelCreating
modelBuilder.Entity<Participants>()
.HasKey(p => new { p.ActivityId, p.ParticipantId });
base.OnModelCreating(modelBuilder);
//Added this ( Not sure if it's needed if anyone knows let me know)
modelBuilder.Entity<Participants>()
.HasOne(p => p.Activity)
.WithMany()
.OnDelete(DeleteBehavior.Cascade);
What you are trying to do is create a relationship between Activity and Participant which is a little different in EFCore.
To do it, you would need to reference the ForeignKey Properties instead of NavigationProperties in the modelbuilder as follows:
modelBuilder.Entity<Participants>()
.HasKey(p => new { p.ActivityId , p.ParticipantId });

Entity Framework Core cascade delete one to many relationship

public class Station : IEntitie
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual ICollection<RegulatorySchedule> RegulatoryScheduleDispatchStations { get; set; }
public virtual ICollection<RegulatorySchedule> RegulatoryScheduleDestinationStations { get; set; }
}
public class RegulatorySchedule : IEntitie
{
[Key]
public int Id { get; set; }
public virtual Station DispatchStation { get; set; }
public virtual Station DestinationStation { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<RegulatorySchedule>()
.HasOne(s => s.DestinationStation)
.WithMany(s => s.RegulatoryScheduleDestinationStations)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
modelBuilder.Entity<RegulatorySchedule>()
.HasOne(s => s.DispatchStation)
.WithMany(s => s.RegulatoryScheduleDispatchStations)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
}
The database is created during migration only when I clearly expose the behavior when deleting Restrict
OnDelete (Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict).
Otherwise, it throws an exception:
"Introducing FOREIGN KEY constraint
'FK_RegulatorySchedules_Stations_DispatchStationId' on table
'RegulatorySchedules' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other
FOREIGN KEY constraints."
I need the removal of the Station Stations of the table and the table-related properties RegulatorySchedules DispatchStation and DestinationStation exposed to NULL.
But Restrict option there is an exception when you delete a SetNull I can not put.
Tell me how to be?
Described "problem" is not related to Entity Framework - this is restriction of MS SQL Server itself. Table with several FKs may have only one of them with cascade delete.
So, as soon as you need both FKs to have cascade - you should implement such "cleanup" in your code. Set one (or both) FKs to DeleteBehavior.Restrict, and in your controller/service prior to removing Station manually find and delete all related RegulatorySchedule
Dmitry's answer worked perfectly. For any future traveler a working sample of a mapping table down below.
The code is located in the OnModelCreating(ModelBuilder modelBuilder) method in your
DbContext class:
modelBuilder.Entity<AB>()
.HasKey(e => new { e.AId, e.BId});
modelBuilder.Entity<AB>()
.HasOne(e => e.A)
.WithMany(e => e.ABs)
.HasForeignKey(e => e.AId)
.OnDelete(DeleteBehavior.Cascade); // <= This entity has cascading behaviour on deletion
modelBuilder.Entity<AB>()
.HasOne(e => e.B)
.WithMany(e => e.ABs)
.HasForeignKey(e => e.BId)
.OnDelete(DeleteBehavior.Restrict); // <= This entity has restricted behaviour on deletion

Entity Framework - Model Self-Referencing Dependencies Not Cascading Delete

I have a Group model/table:
namespace Project
{
#region Usings
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
#endregion
[Table("Groups")]
public class Group
{
public Group()
{
this.Pk = Guid.NewGuid();
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Key]
[Required]
public Guid Pk
{
get;
set;
}
public virtual ICollection<Group> Parents
{
get;
set;
}
public virtual ICollection<Group> Children
{
get;
set;
}
public virtual ICollection<Document> Documents
{
get;
set;
}
...
}
A group can have many groups as either a child or parent, as well as many documents.
I have the relationships wired up in Fluent API as so:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Group>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.Map(e =>
{
e.MapLeftKey("ParentPk");
e.MapRightKey("ChildPk");
e.ToTable("GroupMappings");
});
modelBuilder.Entity<Group>()
.HasMany(e => e.Documents)
.WithMany(e => e.Groups)
.Map(e =>
{
e.MapLeftKey("DocumentPk");
e.MapRightKey("GroupPk");
e.ToTable("DocumentMappings");
});
}
When I generate the SQL for these models, the relationship between Groups and Documents (DocumentMappings) has ON DELETE CASCASDE:
ALTER TABLE [dbo].[DocumentMappings] ADD CONSTRAINT [FK_dbo.DocumentMappings_dbo.Documents_GroupPk] FOREIGN KEY ([GroupPk]) REFERENCES [dbo].[Documents] ([Pk]) ON DELETE CASCADE
But the GroupMappings does not:
ALTER TABLE [dbo].[GroupMappings] ADD CONSTRAINT [FK_dbo.GroupMappings_dbo.Groups_ParentPk] FOREIGN KEY ([ParentPk]) REFERENCES [dbo].[Groups] ([Pk])
ALTER TABLE [dbo].[GroupMappings] ADD CONSTRAINT [FK_dbo.GroupMappings_dbo.Groups_ChildPk] FOREIGN KEY ([ChildPk]) REFERENCES [dbo].[Groups] ([Pk])
Visual representation of the tables:
What could be wrong with my mappings for Group (Parent/Children) that is causing ON DELETE CASCADE to not be "enabled"? Is it related to it being a reference on the same type?
Disclaimer: I have seen a few posts saying this can not be done because you "can't use CASCADE DELETE on self referencing table in SQL SERVER." (Entity Framework 6 Code-First cascade delete on self referencing entity). I am using a mapping table in this case, so this should not be the situation.
Is it related to it being a reference on the same type?
Yes it is. The GroupMappings table has two foreign keys. If you try to set them bot to cascading delete you will get this exception:
Introducing FOREIGN KEY constraint 'contraintName' on table 'GroupMappings' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
With two foreign keys, if you delete a Group there would multiple cascade paths to deleting GroupMappings. Sql Server has this restriction that it doesn't allow this, even when the paths end up at the same table.
EF has knowledge of this restriction and doesn't want to choose for you which FK it equips with cascaded delete. It can't do both.

Categories

Resources