entity framework multiple cascade paths error - c#

What I've done so far to achieve what I want using Entity Framework is something like this:
// User.cs
public class User {
public Guid ID { get; set; } // column: user_id
public virtual ICollection<Event> Events { get; set; }
}
// Event.cs
public class Event {
public Guid ID { get; set; } // column: event_id
public virtual Guid UserID { get; set; } // column: event_userid
public virtual ICollection<User> Guests { get; set; }
}
// MyAppContext.cs
...
protected override void OnModelCreating(DbModelBuilder mb) {
mb.Entity<User>()
.HasKey(u => u.ID)
.HasMany(u => u.Events)
.WithOptional()
.HasForeignKey(e => e.UserID);
mb.Entity<Event>()
.HasKey(e => e.ID)
.HasMany(e => e.Guests)
.WithMany();
}
...
I was expecting the database structure to be as follows:
TABLE: user
user_id uniqueidentifier not null primary key
TABLE: event
event_id uniqueidentifier not null primary key
event_userid uniqueidentifier not null foreign key references user(user_id)
TABLE: event_guests
event_id uniqueidentifier not null
user_id uniqueidentifier not null
I have a feeling that the fluent API I'm using above is not going to give the expected database structure and also, I get the following exception that I've no clue how to fix:
Introducing FOREIGN KEY constraint 'FK_xxx' on table 'event_guests'
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'm new to entity framework, any help would be much appreciated.

Try replacing your configurations with a single many to many configuration.
modelBuilder.Entity<User>()
.HasMany(a => a.Events)
.WithMany(b=> b.Guests)
.Map(x =>
{
x.MapLeftKey("UserId");
x.MapRightKey("EventId");
x.ToTable("EventGuests");
});

Related

How to specify relationship OnDelete behaviour on EF table having Discriminator

How can I map one-to-many (Folder/File)-to-(RoleBindings with Discriminator in one table) using .NET 5 EF and Fluent API?
How can I apply OnDelete() method to have one table RoleBindings where the end result would look like:
Id|Role|Discriminator|FolderId|FileId
and preserve my rule that if I delete RoleBindings nothing happens, but if the Folder/File is deleted, it should delete relations-RoleBindings?
Without Fluent API OnDelete() method the project relationship and behavior is generated badly. Only way I made it work is by defining .OnDelete(DeleteBehavior.Cascade) on Folder/File and .OnDelete(DeleteBehavior.NoAction) on RoleBindings separate tables.
Classes:
public abstract class RoleBindings
{
public Guid Id { get; set; } = Guid.NewGuid();
public Role Role { get; set; }
}
public class FolderRoleBindings : RoleBindings
{
public Guid FolderId { get; set; }
public virtual Folder Folder { get; }
}
public class FileRoleBindings : RoleBindings
{
public Guid FileId { get; set; }
public virtual File File { get; set; }
}
public abstract class File : IFile
{
public Guid Id { get; set; }
public virtual ICollection<FileRoleBindings> Roles { get; set; }
}
public abstract class Folder : IFolder
{
public Guid Id { get; set; }
public virtual ICollection<FolderRoleBindings> Roles { get; set; }
}
Working version when I generate to separate tables:
public class MyDbContext : DbContext
{
public DbSet<Folder> Folders { get; set; }
public DbSet<File> Files { get; set; }
// public DbSet<RoleBindings> RoleBindings { get; set; }
public DbSet<FolderRoleBindings> FolderRoleBindings { get; set; }
public DbSet<FileRoleBindings> FileRoleBindings { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Folder>(e =>
{
e.HasMany(prop => prop.Roles)
.WithOne(ar => ar.Folder)
.HasForeignKey(prop => prop.FolderId)
.OnDelete(DeleteBehavior.NoAction);
});
modelBuilder.Entity<File>(e =>
{
e.HasMany(prop => prop.Roles)
.WithOne(ar => ar.File)
.HasForeignKey(prop => prop.FileId)
.OnDelete(DeleteBehavior.NoAction);
});
modelBuilder.Entity<FileRoleBindings>(e =>
{
e.ToTable(name: "FileRoleBindings", schema: "dbo");
e.HasKey(key => key.Id);
e.HasOne(prop => prop.File)
.WithMany(r => r.Roles)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<FolderRoleBindings>(e =>
{
e.ToTable(name: "FolderRoleBindings", schema: "dbo");
e.HasKey(key => key.Id);
e.HasOne(prop => prop.Folder)
.WithMany(r => r.Roles)
.OnDelete(DeleteBehavior.Cascade);
});
}
}
}
How does one specify to have this kind of relationshiop in one table? I couldn't figure out how to apply OnDelete() on this creation:
modelBuilder.Entity<RoleBindings>(e =>
{
e.ToTable(name: "RoleBindings", schema: "dbo");
e.HasKey(key => key.Id);
e.HasDiscriminator()
.HasValue<FileRoleBindings>("File")
.HasValue<FolderRoleBindings>("Folder");
});
Edit1:
When I'm trying to store everything in one table (previous code modelBuilder.Entity()) I get error
CREATE TABLE [dbo].[RoleBindings] (
[Id] uniqueidentifier NOT NULL,
[Role] int NOT NULL,
[Discriminator] nvarchar(max) NOT NULL,
[FolderId] uniqueidentifier NULL,
[FileId] uniqueidentifier NULL,
CONSTRAINT [PK_RoleBindings] PRIMARY KEY ([Id]),
CONSTRAINT [FK_RoleBindings_Folders_FolderId] FOREIGN KEY ([FolderId]) REFERENCES [hierarchy].[Folders] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_RoleBindings_Files_FileId] FOREIGN KEY ([FileId]) REFERENCES [reports].[Files] ([Id]) ON DELETE CASCADE
);
Microsoft.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_RoleBindings_Files_FileId' on table 'RoleBindings' 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.
When it's table per type I have no issues. Is there anything I can define in modelBuilder.Entity relationship to have this work with table per hierarchy? If I define relationship inside entities File/Folder then the cascade doesn't transfer over and it prevents deletion of File/Folder if RoleBindings exist for the principal entity.

EF6 mapping many-to-many using same column name

In an existing sqlite database I have 3 tables.
First a table with `events`, it uses a composite primary key of `id` and `licence_key`.
The Second table holds `codes`, this too uses a composite key of `session_code` and `licence_key`.
The final table is an associative table from the following sql:
CREATE TABLE `event_code` (
`event_id` INTEGER NOT NULL,
`session_code` TEXT NOT NULL,
`licence_key` TEXT NOT NULL,
CONSTRAINT `event_code$event_id_session_code_licence_key_PK` PRIMARY KEY(`event_id` ASC,`session_code` ASC,`licence_key` ASC),
CONSTRAINT `event_code$event_id_licence_key_FK` FOREIGN KEY(`event_id`, `licence_key`) REFERENCES `event`(`id`, `licence_key`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT `event_code$session_code_licence_key_FK` FOREIGN KEY(`session_code`, `licence_key`) REFERENCES `code`(`session_code`, `licence_key`) ON UPDATE CASCADE ON DELETE CASCADE
);
My program uses entity framework and fluent mapping to load and store objects in these tables.
In EF6 I believe the relevant part of the mapping should look like this:
modelBuilder.Entity<EF6EventInformation>().HasMany(eventInfo => eventInfo.InternalSessionCodes).WithMany().Map(mapping =>
{
mapping.ToTable("event_code");
mapping.MapLeftKey("event_id", "licence_key");
mapping.MapRightKey("session_code", "licence_key");
});
However, this throws an exception:
System.Data.Entity.ModelConfiguration.ModelValidationException: One or more validation errors were detected during model generation:
licence_key: Name: Each property name in a type must be unique. Property name 'licence_key' is already defined.
It seems I can not re-use the same column.
Of course I could change the database design and store the licence_key for both entities in their own separate columns, but because the value for each of those licence_keys would always have to match the other that does not seem particularly useful.
Is there any way to set this mapping up correctly without having to change my database design?
This seems to be a limitation of the EF6 mapping on many-to-many via implicit join table.
It's possible to map the relationship without changing the database structure, but changing the entity model by adding explicit join entity and mapping many-to-many as two many-to-one.
So you'd need an entity like this:
public class EventCode
{
public int event_id { get; set; }
public int session_code { get; set; }
public int license_key { get; set; }
public Event Event { get; set; }
public Code Code { get; set; }
}
then change the existing collection navigation property to something like this:
public ICollection<EventCode> EventCodes { get; set; }
and use fluent configuration like this:
modelBuilder.Entity<EventCode>()
.ToTable("event_code");
modelBuilder.Entity<EventCode>()
.HasKey(e => new { e.event_id, e.session_code, e.license_key });
modelBuilder.Entity<EventCode>()
.HasRequired(e => e.Event)
.WithMany(e => e.EventCodes)
.HasForeignKey(e => new { e.event_id, e.license_key });
modelBuilder.Entity<EventCode>()
.HasRequired(e => e.Code)
.WithMany()
.HasForeignKey(e => new { e.session_code, e.license_key });
Of course you can create better C# conventional property names
public class EventCode
{
public int EventId { get; set; }
public int SessionCode { get; set; }
public int LicenseKey { get; set; }
public Event Event { get; set; }
public Code Code { get; set; }
}
and map them to the existing table column names
but that doesn't change fundamentally the relationship mapping solution.
modelBuilder.Entity<EventCode>()
.ToTable("Event_Code");
modelBuilder.Entity<EventCode>()
.HasKey(e => new { e.EventId, e.SessionCode, e.LicenseKey });
modelBuilder.Entity<EventCode>().Property(e => e.EventId)
.HasColumnName("event_id");
modelBuilder.Entity<EventCode>().Property(e => e.SessionCode)
.HasColumnName("session_code");
modelBuilder.Entity<EventCode>().Property(e => e.LicenseKey)
.HasColumnName("license_key");
modelBuilder.Entity<EventCode>()
.HasRequired(e => e.Event)
.WithMany(e => e.EventCodes)
.HasForeignKey(e => new { e.EventId, e.LicenseKey });
modelBuilder.Entity<EventCode>()
.HasRequired(e => e.Code)
.WithMany()
.HasForeignKey(e => new { e.SessionCode, e.LicenseKey });
Both ways of many-to-many mapping have pros and cons, but here there is simply no choice.

Entity Framework Core - Inserting One-Directional Parent Child Relationship

Since Table Per Type isn't available in Core, I had to do a bit of a workaround to get my entities how I like them. Essentially I have a base class with its properties, and a navigation property to its parent:
public class Provision
{
public Guid ProvisionId { get; set; }
public string ProvisionName { get; set; }
public string ProvisionDescription { get; set; }
public Provision(){}
}
public class CompanyLeaveProvision
{
public Guid ProvisionId { get; set; }
public int CompanyId { get; set; }
public Provision Provision { get; set; }
public CompanyLeaveProvision() { }
}
Configurations:
public void Configure(EntityTypeBuilder<Provision> builder)
{
// Primary Key
builder.HasKey(t => t.ProvisionId);
// Properties
builder.Property(t => t.ProvisionName)
.IsRequired()
.HasMaxLength(40);
builder.Property(t => t.ProvisionDescription)
.HasMaxLength(500);
// Table & Column Mappings
builder.Property(t => t.ProvisionId).HasColumnName("ProvisionID");
builder.Property(t => t.ProvisionName).HasColumnName("ProvisionName");
builder.Property(t => t.ProvisionDescription).HasColumnName("ProvisionDescription");
builder.ToTable("Provision", "Organization");
}
public void Configure(EntityTypeBuilder<CompanyLeaveProvision> builder)
{
// Primary Key
builder.HasKey(t => t.ProvisionId);
// Properties
builder.Property(t => t.ProvisionId)
.IsRequired();
builder.Property(t => t.CompanyId)
.IsRequired();
// Table & Column Mappings
builder.ToTable("CompanyLeaveProvision", "Organization");
builder.Property(t => t.ProvisionId).HasColumnName("ProvisionID");
builder.Property(t => t.CompanyId).HasColumnName("CompanyID");
builder.HasOne(t => t.Provision).WithOne().HasForeignKey<Provision>(t => t.ProvisionId);
}
My context:
ProvisionContext: DbContext, IContext {
public DbSet<Provision> Provisions { get; set; }
public DbSet<CompanyLeaveProvision> CompanyLeaveProvisions { get; set;}
// OnModelCreating and other code below
}
I have a foreign key constraint on the the Organization.CompanyProvision table that references the ProvisionId property on the Organization.Provision table.
What is happening is the CompanyProvision is being inserted before the base Provision, resulting in this error:
The INSERT statement conflicted with the FOREIGN KEY constraint
"fk_CompanyLeaveProvision_Provision". The conflict occurred in
database "Dev", table "Organization.Provision", column 'ProvisionID'.
To attempt to save, here is the code I am calling:
_context.Entry(command.Provision.Provision).State = EntityState.Added;
_context.Entry(command.Provision).State = EntityState.Added;
await _context.SaveChangesAsync();
Aside from calling SaveChanges() after each _context.Entry(MyEntity).State = EntityState.Added, is there any way around this issue? I would prefer to have these save at once. I know a stored procedure is also an option, but I would prefer not to do that.
Thank you for your help!
It's because this fluent mapping
.HasForeignKey<Provision>(t => t.ProvisionId)
is telling EF Core that Provision is the dependent entity and has FK to the principal entity CompanyLeaveProvision, while the database model is the opposite.
So simply change Provision to CompanyLeaveProvision
.HasForeignKey<CompanyLeaveProvision>(t => t.ProvisionId)

Different Key in Table relations

I have a database with this structure
TASKs SubTasks
=============================
Id (pk) Id (pk)
Name Name
TaskCode ParentTaskCode
Now I need to connect SubTasks Table to Tasks using Tasks.TaskCode as the key in the relation between them, and Entity Framework does not allow me to do that, any ideas :) ?
Note: I do not own this database, so any changes to the structure cannot be done.
You can try something like.
public class Certificates
{
[Key]
public int CertID { get; set; }
public Users User { get; set; }
public Quiz Quiz { get; set; }
}
public class Users
{
public int ID { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class Quiz
{
public int QuizID { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class cpdContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entitiy<Users>()
.HasMany(u => u.Certificates)
.WithRequired(c => c.User) // or WithOptional
.Map(m => m.MapKey("UserID")); //<- DB FK column name
modelBuilder.Entitiy<Quiz>()
.HasMany(u => u.Certificates)
.WithRequired(c => c.Quiz) // or WithOptional
.Map(m => m.MapKey("QuizID")); //<- DB FK column name
}
}
Moreover, In independent association, the foreign key (Association) is defined in the conceptual model.
To define the association in a conceptual model, we must add association set, association and navigation properties.
It is represented as separate object in ObjectStateManager. It has its own EntityState!
When building association you always need entitites from both ends of association
This association is mapped in the same way as entity.
Source
There might be something I do not understand...
Why do you want to use Task.TaskCode as a foreign key of SubTasks ?
If ID is the PK of Tasks, then remove TaskCode an use Task.ID as you fk reference
SubTasks.TaskID ----> Task.ID.
Also, some other advices on naming conventions.
do not pluralize table names
use "<%table%>ID" as name for your pk : exemple TaskID for task.
use fk column as name for a fk : exemple "TaskID" as fk in your subtask table
If you are using MVC for this then you can simply make composite viewmodel for above.
Without changing in EF you can do what you want.
Make class as
public class CompositeTaskSubtask
{
public <namespace>.<tasktablename> taskmodel { get; set; }
public <namespace>.<subtasktablename> subtaskmodel { get; set; }
}
You can try adding a linking table called SubTask with its own primary key. That way taskCode will not need to be primary key.
CREATE TABLE Task
(
taskId int IDENTITY (1, 1) NOT NULL,
name nvarchar(50),
taskCode nvarchar(50),
CONSTRAINT Task_PK PRIMARY KEY(taskId)
)
GO
CREATE TABLE SubTask
(
subTaskId int NOT NULL,
taskId int NOT NULL, -- must have a parent
CONSTRAINT SubTask_PK PRIMARY KEY(subTaskId)
)
GO
ALTER TABLE SubTask ADD CONSTRAINT SubTask_FK1 FOREIGN KEY (taskId) REFERENCES [Task] (taskId) ON DELETE NO ACTION ON UPDATE NO ACTION
GO
ALTER TABLE SubTask ADD CONSTRAINT SubTask_FK2 FOREIGN KEY (subTaskId) REFERENCES [Task] (taskId) ON DELETE NO ACTION ON UPDATE NO ACTION
GO

EF CascadeOnDelete Code First

I have this entity:
public class Account
{
[Key]
[ForeignKey("Company")]
[Required]
public Guid CompanyId { get; set; }
public virtual Company Company { get; set; }
}
And this one:
public class Company : PrimaryKey
{
public string Name { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
public virtual Account Account { get; set; }
}
How do I use fluent api to enable cascade delete, I tried this:
modelBuilder.Entity<Company>().HasOptional<Account>().WithRequired().WillCascadeOnDelete();
But I have no idea what this means. Basically, I want a Company to have an optional Account which will be deleted when the company is deleted.
The mapping you need is:
modelBuilder.Entity<Company>()
.HasOptional(c => c.Account)
.WithRequired(a => a.Company)
.WillCascadeOnDelete();
It's a one-to-one relationship between Company and Account. With this mapping you can remove the [ForeignKey("Company")] attribute and the [Required] attribute anyway because a Guid is not nullable and therefore always required.
Please folow the link Enabling Cascade Delete.
You can configure cascade delete on a relationship by using the
WillCascadeOnDelete method. If a foreign key on the dependent entity
is not nullable, then Code First sets cascade delete on the
relationship. If a foreign key on the dependent entity is nullable,
Code First does not set cascade delete on the relationship, and when
the principal is deleted the foreign key will be set to null.
You can remove these cascade delete conventions by using:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()
The following code configures the relationship to be required and then
disables cascade delete.
modelBuilder.Entity<Course>()
.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID)
.WillCascadeOnDelete(false);
something like it in your example:
modelBuilder
.Entity<Company>()
.HasOptional<Account>()
.HasForeignKey(a => a.CompanyId)
.WillCascadeOnDelete();

Categories

Resources