How do I remedy "cycles or multiple cascade paths"? - c#

I have three entities:
ApplicationUser:
public class ApplicationUser : IdentityUser<Guid>
{
// more properties
public List<MeetingNotification> MeetingNotifications { get; set; }
}
MeetingNotifications:
public class MeetingNotification
{
public Guid Id { get; set; }
public Guid MeetingId { get; set; }
public Meeting Meeting { get; set; }
public Guid SummonedUserId { get; set; }
public ApplicationUser SummonedUser { get; set; }
}
Meetings:
public class Meeting
{
public Guid Id { get; set; }
// more properties
public Guid OrganizerId { get; set; }
public ApplicationUser Organizer { get; set; }
public List<MeetingNotification> Notifications { get; set; }
}
The relationships are as such:
One ApplicationUser has many MeetingNotifications
One MeetingNotification has one ApplicationUser and one Meeting
One Meeting has many MeetingNotifications
When I try to update-database, I get the error message
Introducing FOREIGN KEY constraint 'FK_MeetingNotifications_Meetings_MeetingId' on table 'MeetingNotifications' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
This error is my arch nemesis, as I struggle to understand were the error lies. Is it in Meeting or in MeetingNotification. Or somewhere else?
I have tried these three definitions:
modelBuilder.Entity<MeetingNotification>()
.HasOne(m => m.Meeting)
.WithMany(n => n.Notifications)
.HasForeignKey(fk => fk.Id).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MeetingNotification>()
.HasOne(m => m.Meeting)
.WithMany(n => n.Notifications)
.HasForeignKey(fk => fk.MeetingId).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Meeting>()
.HasMany(n => n.Notifications)
.WithOne(m => m.Meeting)
.HasForeignKey(fk => fk.MeetingId).OnDelete(DeleteBehavior.NoAction);
... but they don't seem to do anything. It's just the same error message every time
Update
I finally found a way!
modelBuilder.Entity<MeetingNotification>()
.HasOne(m => m.Meeting)
.WithMany(n => n.Notifications)
.OnDelete(DeleteBehavior.Restrict);
... and the error went away! But I don't fully understand why, so any good explanation would be appreciated!

Related

Cascading Referential Integrity Constraints for many to many relationships in EF Core

I'm using EF Core 3.1.5 with Asp.Net Core 3.1. When I'm trying to Add-Migration I get next error:
Microsoft.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_PhotoDevice_Device_DeviceId' on table 'PhotoDevice' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I have next entities and configuration:
public class User {
[Key]
public long Id { get; set; }
// Fields
public IEnumerable<Device> Devices { get; set; }
public IEnumerable<Photo> Photos { get; set; }
}
public class Photo {
[Key]
public long Id { get; set; }
// Fields
[ForeignKey("User")]
public long UserRef { get; set; }
public User User { get; set; }
public IEnumerable<PhotoDevice> PhotoDevices { get; set; }
}
public class Device {
[Key]
public long Id { get; set; }
// Fields
[ForeignKey("User")]
public long UserRef { get; set; }
public User User { get; set; }
public IEnumerable<PhotoDevice> PhotoDevices { get; set; }
}
public class PhotoDevice {
{
public long? PhotoRef { get; set; }
public Photo Photo { get; set; }
public long? DeviceRef { get; set; }
public Device Device { get; set; }
}
public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Photo> Photos { get; set; }
public DbSet<Device> Devices { get; set; }
public DbSet<PhotoDevice> PhotoDevices { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
Database.Migrate();
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Photo>()
.HasOne(photo => photo.User)
.WithMany(user => user.Photos);
builder.Entity<Device>()
.HasOne(device => device.User)
.WithMany(user => user.Devices);
builder.Entity<PhotoDevice>()
.HasKey(bc => new { bc.PhotoRef, bc.DeviceRef });
builder.Entity<PhotoDevice>()
.HasOne(bc => bc.Photo)
.WithMany(b => b.PhotoDevices)
.HasForeignKey(bc => bc.PhotoRef);
builder.Entity<PhotoDevice>()
.HasOne(bc => bc.Device)
.WithMany(c => c.PhotoDevices)
.HasForeignKey(bc => bc.DeviceRef);
}
}
I found that problem is related to not uniqueness of cascading connections. But now I'm confused. Because I think that this structure is necessary and I don't want to drop any FK. How can I fix the problem If I need next logic:
When User deleted all devices and photos related to the user are also removed
When Device or Photo deleted all PhotoDevice records related to the deleted entity are removed too
What lauxjpn said is correct, but please note that when you add .OnDelete(DeleteBehavior.Restrict);
to your context after that, please remember to delete all your previous migrations, otherwise it will still report an error.
You don't need to drop any of your foreign keys. Just use OnDelete(DeleteBehavior) to explicitly specify what kind of cascading behavior you need.
For example, the following would result in your model being created successfully, but for your real application, you need to decide yourself where and how to break the cascade:
builder.Entity<PhotoDevice>()
.HasOne(bc => bc.Photo)
.WithMany(b => b.PhotoDevices)
.HasForeignKey(bc => bc.PhotoRef)
.OnDelete(DeleteBehavior.Restrict); // <-- define cascading behavior
For further information, see Relationships: Cascade delete and Cascade Delete.
This is not an EF Core, but a SQL Server limitation (therefore the exception is also thrown by SQL Server).
Here are further resources, that deal with this limitation and show how to work around it, by using INSTEAD OF trigger, if breaking the cascade is not an option you can live with:
Solving the SQL Server Multiple Cascade Path Issue with a Trigger
Preferred design to avoid circular/multiple update paths

Many to many relationship issue with column name - EF Core

I have a problem with many to many relationship in EF core. I have the following models:
public class Institution
{
[Key]
public int Id { get; set; }
[JsonIgnore]
public virtual ICollection<InstitutionDepartment> InstitutionDepartments { get; set; }
}
public class InstitutionDepartment
{
[Column("Institution_Id")]
public int InstitutionId { get; set; }
[Column("Department_Id")]
public int DepartmentId { get; set; }
public Institution Institution { get; set; }
public Departments Department { get; set; }
}
public class Departments
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool Published { get; set; }
[JsonIgnore]
public virtual ICollection<InstitutionDepartment> InstitutionDepartments { get; set; }
}
I followed the many tutorials explaining how to correctly map these classes:
modelBuilder.Entity<InstitutionDepartment>()
.HasKey(x => new { x.DepartmentId, x.InstitutionId});
modelBuilder.Entity<InstitutionDepartment>()
.HasOne(pt => pt.Institution)
.WithMany(p => p.InstitutionDepartments)
.HasForeignKey(pt => pt.InstitutionId);
modelBuilder.Entity<InstitutionDepartment>()
.HasOne(pt => pt.Department)
.WithMany(t => t.InstitutionDepartments)
.HasForeignKey(pt => pt.DepartmentId);
I wrote my query:
var institutions = _context.Institutions
.Include(i => i.InstitutionDepartments)
.ThenInclude(id => id.Department);
But no matter what I do, I get the following error:
Invalid column name 'InstitutionId'.
Can anyone tell me what I'm doing wrong here ? D:
Note I don't get the error if I don't write the .ThenInclude(id => id.Department); part.
But that make the data incomplete
The issue came from a line of code that I didn't deem relevant at the time (I'll know better next time)
This was in the Institution model without the [NotMapped] annotation:
[NotMapped]
public IEnumerable<Departments> Departments
=> InstitutionDepartments?.Select(o => o.Department);
This was causing EF to look for a missing One to Many relationship between Institution and Department

Many to one migrations fails on foreign key long

I have 2 models:
public class Text
{
public long Id { get; set; }
public string Text { get; set; }
}
public class User
{
public int Id { get; set; }
public ICollection<Text> Texts { get; set; }
}
My model build on user is that
e.HasMany(o => o.Texts).WithOne().HasForeignKey(d => d.Id).IsRequired();
When I try to run:
dotnet ef migrations add
I get this error:
with foreign key properties {'Id' : long} cannot target the primary
key {'Id' : int} because it is not compatible. Configure a principal
key or a set of compatible foreign key properties for this
relationship.
UPDATE:
It should be able for new models to have a collection of the table Texts like:
public class Customer
{
public int Id { get; set; }
public ICollection<Text> Texts { get; set; }
}
....
e.HasMany(o => o.Texts).WithOne().HasForeignKey(d => d.Id).IsRequired();
Had similar problem using EF Core but didn't want to include the (equivalent in my class) UserId on the dependent entity Text, just to make happy EF. Finally found that you can replace the primary key used in the relationship (UserId)
using HasPrincipalKey()
modelBuilder.Entity<User>()
.HasMany(t => t.Texts)
.WithOne()
.HasPrincipalKey(u => u.Text);
Firstly, change your Model naming please,
public class Text
{
public long Id { get; set; }
public int UserId { get; set; }// add a foreign key that could point to User.Id
public string Body { get; set; }//you cannot have a string property called "Text".
public virtual User Owner { get; set; }
}
public class User
{
public int Id { get; set; }
public virtual ICollection<Text> Texts { get; set; } = new HashSet<Text>();
}
builder.Entity<Text>(table =>
{
table.HasKey(x => x.Id);
table.HasOne(x => x.User)
.WithMany(x => x.Texts)
.HasForeignKey(x => x.UserId)
.HasPrincipalKey(x => x.Id)//<<== here is core code to let foreign key userId point to User.Id.
.OnDelete(DeleteBehavior.Cascade);
});
the reason we have to figure out which key is referred is because of multiple primary keys. I saw it once in MSDN, but cannot find it back.
You can use shadow properties for foreign keys, it looks popular now.
public class Text
{
public long Id { get; set; }
public string Body { get; set; }
public virtual User Owner { get; set; }
}
public class User
{
public int Id { get; set; }
public virtual ICollection<Text> Texts { get; set; } = new HashSet<Text>();
}
builder.Entity<Text>(table =>
{
table.HasKey(x => x.Id);
// Add the shadow property to the model
table.Property<int>("UserId");
table.HasOne(x => x.User)
.WithMany(x => x.Texts)
.HasForeignKey("UserId")//<<== Use shadow property
.HasPrincipalKey(x => x.Id)//<<==point to User.Id.
.OnDelete(DeleteBehavior.Cascade);
});
In the EF context configuration, specifically in the HasForeignKey() you are supposed to specify Which property on the Text model should be the foreign key that points to the User model?
Since User model's primary key is an int, the foreign key pointing from Text to User should naturally also be an int.
I think the mistake you've made is that you are configuring the PK of Textto also be the FK for the relationship Text -> User. Try to change your Text model to :
public class Text
{
public long Id { get; set; }
public string Text{ get; set; }
public int UserId { get; set; }
}
And your configuration to:
e.HasMany(o => o.Texts).WithOne().HasForeignKey(d => d.UserId).IsRequired();
You can simply drop all the migrations or the migration that made that Id, drop the database (if it is small or has no data) and add a clean migration
I was facing the same issue in one-to-one relationship. If you are facing the issue in one-one relationship. Then try this:
public partial class document
{
public document()
{
groups = new group();
}
public int? group_id { get; set; }
public virtual group groups { get; set; }
}
[Table("group")]
public class group
{
[Key]
[Column("group_id")]
public int group_id { get; set; }
[ForeignKey(nameof(group_id))]
public virtual document document { get; set; }
}
Each document has single group. So, we can consider these settings.
modelBuilder.Entity<group>().HasOne(a => a.document)
.WithOne(y => y.groups).HasForeignKey<document>(b => b.group_id);

EF FOREIGN KEY constraint may cause cycles or multiple cascade paths

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.OneToManyCascadeDeleteConventi‌​on());
modelBuilder .Entity() .HasRequired(x => x.Person)
.WithMany() .HasForeignKey(x => x.PersonId)
.WillCascadeOnDelete(false); }
This fixed the model creation.

Code first, multiple foreign keys

First of all let me apologize for the poor title, I'm not quite aware of what to call this topic.
Anyways..
I'm trying to create a BankAccount<-->Transactions relation using code first.
Here are my "entity classes"
public class BankAccount : BaseEntity
{
public string Name { get; set; }
public double Balance { get; set; }
public virtual long UserId { get; set; }
public virtual User User { get; set; }
public virtual ICollection<Transaction> FromTransactions { get; set; }
public virtual ICollection<Transaction> ToTransactions { get; set; }
}
and
public class Transaction : BaseEntity
{
public string Message { get; set; }
public double Amount { get; set; }
public virtual long ToId { get; set; }
public virtual BankAccount To { get; set; }
public virtual long FromId { get; set; }
public virtual BankAccount From { get; set; }
}
As you can see I would like a transaction to be able to have a "From" BankAccount and a "To" BankAccount for making it easy to navigate from a transaction to the belonging BankAccount(s). Also the BankAccount has two collections of transactions, one "From" and one "To", and that's also to make it easy to navigate between the objects.
The thing is that as soon as I run Update-Database it fails with:
Conflicting changes to the role 'BankAccount_ToTransactions_Source' of the relationship 'OpenFridge.Api.Data.BankAccount_ToTransactions' have been detected
I also have added the two following EntityTypeConfigurations:
public class BankAccountEntityTypeConfiguration : BaseEntityTypeConfiguration<BankAccount>
{
public BankAccountEntityTypeConfiguration()
{
ToTable("BankAccounts");
HasRequired(e => e.User)
.WithMany(e => e.BankAccounts);
Property(e => e.Balance)
.IsRequired();
Property(e => e.Name)
.IsRequired();
HasMany(e => e.ToTransactions)
.WithRequired(e => e.To).WillCascadeOnDelete(false);
HasMany(e => e.FromTransactions)
.WithRequired(e => e.From).WillCascadeOnDelete(false);
}
}
public class TransactionEntityTypeConfiguration : BaseEntityTypeConfiguration<Transaction>
{
public TransactionEntityTypeConfiguration()
{
ToTable("Transactions");
Property(e => e.Amount)
.IsRequired();
HasRequired(e => e.From);
HasRequired(e => e.To);
}
}
Any idea of how to create this kind of relation in a proper way? I might simply be missing something in my database-design as well..
Br,
Inx
For what it's worth, a quick fix for update-database that fails (if you're still in the design phase) is to delete the database and run update-database again to build from scratch. Occasionally, errors have to do with sequence of events as you change your model.
Another check you can make is to run update-database -script and run the resultant SQL in the database manually.
The final quick fix I can offer is to delete the offending relationship in the database.
To help you in the future it may help to use data annotations in your model [Required] [Key] [ForeignKey("ForeignKeyId")] can be helpful in specifying how everything connects and reduce code in your configurations to just the relationships.
You may need to specify the names of your foreign keys in the Transaction entity like so:
public class Transaction : BaseEntity
{
public string Message { get; set; }
public double Amount { get; set; }
public virtual long ToId { get; set; }
[ForeignKey("ToId")]
public virtual BankAccount To { get; set; }
public virtual long FromId { get; set; }
[ForeignKey("FromId")]
public virtual BankAccount From { get; set; }
}

Categories

Resources