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.
Related
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();
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 });
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);
}
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.
I have two classes:
public class Cluster
{
public int Id { get; set; }
public virtual ICollection<Blob> Blobs { get; set; }
}
public class Blob
{
public int Id { get; set; }
public virtual ICollection<Cluster> Clusters { get; set; }
}
public ClusterConfiguration ()
{
this.HasKey(p => p.Id)
.HasRequired(p => p.Frame)
.WithMany(p => p.Clusters)
.HasForeignKey(p => p.FrameId)
.WillCascadeOnDelete(true)
;
this.HasMany(p => p.Blobs)
.WithMany(p => p.Clusters)
;
}
public BlobConfiguration ()
{
this.HasKey(p => p.Id)
.HasRequired(p => p.Frame)
.WithMany(p => p.Blobs)
.HasForeignKey(p => p.FrameId)
.WillCascadeOnDelete(true)
;
this.HasMany(p => p.Clusters)
.WithMany(p => p.Blobs)
;
}
There are references to other tables in these classes but I do not think that is the problem. The error is:
[{"Introducing FOREIGN KEY constraint 'FK_dbo.ClusterBlobs_dbo.Blob_Blob_Id' on table 'ClusterBlobs' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.\r\nCould not create constraint. See previous errors."}].
I'm not quite sure how to tell EF to cascade delete Blobs if Clusters are deleted but not to delete Clusters if Blobs are deleted. Please advise.
UPDATE: Using EF5 by the way.
The multiple cascading delete path is actually in effect if you delete a Frame, not a Cluster or Blob:
Frame is deleted -> Cascades to Clusters -> Cascades to link table
Frame is deleted -> Cascades to Blobs -> Cascades to link table
So, these are the two paths from Frame to the link table.
I would suggest to disable cascading delete for one of the two (or both) relationships from Frame to Cluster or Blob. (Use WillCascadeOnDelete(false) there.) Disabling cascading delete for the link table is not possible on an individual relationship basis. The only way is to disable the convention globally:
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
But this will affect all many-to-many relationships in your model.
I'm not quite sure how to tell EF to cascade delete Blobs if Clusters
are deleted but not to delete Clusters if Blobs are deleted.
This is by the way not possible. There is no cascading delete between Cluster and Blob because from database viewpoint the many-to-many relationship is actually modeled with two one-to-many relationships with the link table in between. Cascading delete only acts on the link table which is the dependent in the relationships. Cluster and Blob are both principals.
I believe that turning off ManyToManyCascadeDeleteConvention globally is not a wise option. Instead, it's better to turn it off only for the concerned table.
This can be achieved through editing the generated migration file, for property cascadeDelete. For example:
AddForeignKey("dbo.ClusterBlobs", "Blob_Id", "dbo.Blob", "Blob_Id", cascadeDelete: false);