EF Code First cascade delete doesn't work - entity-framework

I have 4 tables:
User table
public enum SEX { Male, Female }
public abstract class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public SEX Sex { get; set; }
}
Doctor table inherites from User
[Table("Doctor")]
public class Doctor : User
{
public string Department { get; set; }
public string Occupation { get; set; }
public string CabinetNumber { get; set; }
public virtual List<Treat> Treats { get; set; }
}
Patient table inherites from User
[Table("Patient")]
public class Patient : User
{
public int InsuranceNumber { get; set; }
public int CardNumber { get; set; }
public virtual List<Treat> Treats { get; set; }
}
public class Treat
{
public int TreatId { get; set; }
public int DoctorUserId { get; set; }
public int PatientUserId { get; set; }
public virtual Doctor Doctor { get; set; }
public virtual Patient Patient { get; set; }
}
public class HospitalContext: DbContext
{
public HospitalContext() : base("DBConnectionString") {
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<HospitalContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Treat>()
.HasRequired(x => x.Doctor)
.WithMany( x => x.Treats)
.HasForeignKey( x => x.DoctorUserId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Treat>()
.HasRequired(x => x.Patient)
.WithMany( x => x.Treats)
.HasForeignKey( x => x.PatientUserId)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { get; set; }
public DbSet<Treat> Treats { get; set; }
}
I have found much answers here but no one from them works. I have spend a few hours trying to make it work. I know that Entity Framework must enable cascade delete when there is one-to-many relation, but it didn't

Entity Framework doesn't apply cascade deletion with TPT (Table Per Type) inheritance. You can solve this with Code Fist migrations:
CreateTable(
"dbo.Treats",
c => new
{
TreatId = c.Int(nullable: false, identity: true),
DoctorUserId = c.Int(nullable: false),
PatientUserId = c.Int(nullable: false),
})
.PrimaryKey(t => t.TreatId)
.ForeignKey("dbo.Doctor", t => t.DoctorUserId, cascadeDelete: true)
.ForeignKey("dbo.Patient", t => t.PatientUserId, cascadeDelete: true)
.Index(t => t.DoctorUserId)
.Index(t => t.PatientUserId);
The important part is cascadeDelete: true. You have to manually add it after migration code generation. After that you will have cascade deletion in your database:
FOREIGN KEY ([DoctorUserId]) REFERENCES [dbo].[Doctor] ([UserID]) ON DELETE CASCADE,
FOREIGN KEY ([PatientUserId]) REFERENCES [dbo].[Patient] ([UserID]) ON DELETE CASCADE

Related

Entity Framework 1:1 mapping foreign key

My entity AppUser has an optional UserProfile, and UserProfile as a required AppUser. I would like to have a foreign key to each other.
public class AppUser
{
public int Id { get; set; }
public string Name { get; set; }
public UserProfile UserProfile { get; set; }
public int? UserProfileId { get; set; }
}
public class UserProfile
{
public int Id { get; set; }
public string SomeUserProfileValue { get; set; }
public AppUser AppUser { get; set; }
public int AppUserId { get; set; }
}
I got this mapping:
modelBuilder.Entity<AppUser>().HasOptional(x => x.UserProfile).WithRequired(x => x.AppUser)
This generate the following migration. I notice there is no foreign key from AppUser to UserProfile. Also the foreignkey in UserProfile is defined on UserProfile.Id ... I want it on UserProfile.AppUserId.
public override void Up()
{
CreateTable(
"dbo.AppUsers",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
UserProfileId = c.Int(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.UserProfiles",
c => new
{
Id = c.Int(nullable: false),
SomeUserProfileValue = c.String(),
AppUserId = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.AppUsers", t => t.Id)
.Index(t => t.Id);
}
So I tried to change the mapping configuration as follow
modelBuilder.Entity<AppUser>().HasOptional(x => x.UserProfile).WithRequired(x => x.AppUser)
.Map(c => c.MapKey("AppUserId"));
But now when I try to add the migration i get the error:
AppUserId: Name: Each property name in a type must be unique. Property name 'AppUserId' is already defined.
This seems to complain that I have a field AppUserId already defined in my model.
This is how we define our entities, we always include both the class and the id fields, gives more flexibility as to which to use under different circumstances.
So I'm a bit stuck here... is there any way to have this 1:1 bidirectional relation while having both class and the id fields defined in the model ?
And why there is no nullable foreign key generated in the AppUser table ?
I've generally found better results with DataAnnotations, myself. So:
public class AppUser
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? UserProfileId { get; set; }
[ForeignKey = "UserProfileId"]
public UserProfile UserProfile { get; set; }
}
public class UserProfile
{
[Key]
public int Id { get; set; }
public string SomeUserProfileValue { get; set; }
public int AppUserId { get; set; }
[ForeignKey = "AppUserId"]
public AppUser AppUser { get; set; }
}

Entity Framework Core: How to solve Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths

I'm using Entity Framework Core with Code First approach but recieve following error when updating the database:
Introducing FOREIGN KEY constraint 'FK_AnEventUsers_Users_UserId' on table 'AnEventUsers' 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 entities are these:
public class AnEvent
{
public int AnEventId { get; set; }
public DateTime Time { get; set; }
public Gender Gender { get; set; }
public int Duration { get; set; }
public Category Category { get; set; }
public int MinParticipants { get; set; }
public int MaxParticipants { get; set; }
public string Description { get; set; }
public Status EventStatus { get; set; }
public int MinAge { get; set; }
public int MaxAge { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public ICollection<AnEventUser> AnEventUsers { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class User
{
public int UserId { get; set; }
public int Age { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Gender Gender { get; set; }
public double Rating { get; set; }
public ICollection<AnEventUser> AnEventUsers { get; set; }
}
public class AnEventUser
{
public int AnEventId { get; set; }
public AnEvent AnEvent { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class ApplicationDbContext:DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AnEventUser>()
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<AnEventUser>()
.HasKey(t => new { t.AnEventId, t.UserId });
modelBuilder.Entity<AnEventUser>()
.HasOne(pt => pt.AnEvent)
.WithMany(p => p.AnEventUsers)
.HasForeignKey(pt => pt.AnEventId);
modelBuilder.Entity<AnEventUser>()
.HasOne(eu => eu.User)
.WithMany(e => e.AnEventUsers)
.HasForeignKey(eu => eu.UserId);
}
public DbSet<AnEvent> Events { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<AnEventUser> AnEventUsers { get; set; }
}
The issue I thought was that if we delete a User the reference to the AnEvent will be deleted and also the reference to AnEventUser will also be deleted, since there is a reference to AnEventUser from AnEvent as well we get cascading paths. But I remove the delete cascade from User to AnEventUser with:
modelBuilder.Entity<AnEventUser>()
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
But the error doesn't get resolved, does anyone see what is wrong? Thanks!
In your sample code in OnModelCreating you have declared modelBuilder.Entity<AnEventUser>().HasOne(e => e.User)... twice: at start of method and at end.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AnEventUser>() // THIS IS FIRST
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<AnEventUser>()
.HasKey(t => new { t.AnEventId, t.UserId });
modelBuilder.Entity<AnEventUser>()
.HasOne(pt => pt.AnEvent)
.WithMany(p => p.AnEventUsers)
.HasForeignKey(pt => pt.AnEventId);
modelBuilder.Entity<AnEventUser>() // THIS IS SECOND.
.HasOne(eu => eu.User) // THIS LINES
.WithMany(e => e.AnEventUsers) // SHOULD BE
.HasForeignKey(eu => eu.UserId); // REMOVED
}
Second call overrides first. Remove it.
This is what I did from the answer of Dmitry,
and It worked for me.
Class:
public class EnviornmentControls
{
public int Id { get; set; }
...
public virtual Environment Environment { get; set; }
}
And it's Mapping
public EnviornmentControlsMap(EntityTypeBuilder<EnviornmentControls> entity)
{
entity.HasKey(m => m.Id);
entity.HasOne(m => m.Environment)
.WithMany(m => m.EnviornmentControls)
.HasForeignKey(m => m.EnvironmentID)
.OnDelete(DeleteBehavior.Restrict); // added OnDelete to avoid sercular reference
}
These solutions didn't work for my case, but I found a way. I am not quite sure yet if it is safe but there's just something that's happening with deleting. So I modified the generated Migration File instead of putting an override.
onDelete: ReferentialAction.Cascade
The reason I did this because all the overriding mentioned above is not working for me so I manually removed the code which relates to Cascading of Delete.
Just check which specific relation being mentioned at the error so you can go straightly.
Hope this will be able to help for some people who's having the same issue as mine.
public Guid? UsuarioId { get; set; }
builder.Entity<Orcamentacao>()
.HasOne(x => x.Usuario)
.WithMany(x => x.Orcamentacaos)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false)
.HasForeignKey(x => x.UsuarioId);

EF code first - add inverse propterty with optional element

I have the following classes
public class Order {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public DateTime Date { get; set; }
[Required]
[MaxLength(100)]
public string From { get; set; }
public int? TreatGuestEntryID { get; set; }
[ForeignKey("TreatGuestEntryID")]
public TreatedGuestEntry TreatGuestEntry { get; set; }
...
public class TreatedGuestEntry {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[MaxLength(200)]
public string Company { get; set; }
public string TypeOfTreat { get; set; }
This works as expected - in my Orders table it creates the foreign key.
Now I want to add an inverse property in TreatedGuestEntry for the order.
The best (at least somehow working) result I get when I add
modelBuilder.Entity<TreatedGuestEntry>()
.HasOptional(a => a.Order)
.WithOptionalDependent(a => a.TreatGuestEntry)
.Map(a=>a.MapKey("TreatGuestEntryID"));
and further rename the key of TreatedGuestEntry to TreatGuestEntryID.
But I get no relation in the database and also TreatGuestEntryID in the table Order is no longer a key (FK).
My approach in simple words:
In my Order I want an optional TreatedGuestEntry (and I need access to the foreign key) - and further in the related TreatedGuestEntry I want to access the Order.
In your case, the FK TreatGuestEntryID is not a PK, it means that it is a 1:n relationship. So, you have to put a Collection of Order on the other side:
public class Order
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public DateTime Date { get; set; }
[Required]
[MaxLength(100)]
public string From { get; set; }
public int? TreatGuestEntryID { get; set; }
public TreatedGuestEntry TreatGuestEntry { get; set; }
}
public class TreatedGuestEntry
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[MaxLength(200)]
public string Company { get; set; }
public string TypeOfTreat { get; set; }
public ICollection<Order> Orders { get; set; }
}
Mapping:
modelBuilder.Entity<Order>()
.HasOptional(i => i.TreatGuestEntry)
.WithMany(i => i.Orders)
.HasForeignKey(i => i.TreatGuestEntryID)
.WillCascadeOnDelete(false);
Generated Migration:
CreateTable(
"dbo.Orders",
c => new
{
ID = c.Int(nullable: false, identity: true),
Date = c.DateTime(nullable: false),
From = c.String(nullable: false, maxLength: 100),
TreatGuestEntryID = c.Int(),
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.TreatedGuestEntries", t => t.TreatGuestEntryID)
.Index(t => t.TreatGuestEntryID);
CreateTable(
"dbo.TreatedGuestEntries",
c => new
{
ID = c.Int(nullable: false, identity: true),
Company = c.String(maxLength: 200),
TypeOfTreat = c.String(),
})
.PrimaryKey(t => t.ID);

EF code first from database 0..1 to many relationship

I am trying to generated an entity framework code first model from an existing database (without changing the database schema). This database has been used in the past to generate edmx models and I am trying to achieve the equivalent model using Fluent Api or data annotations.
The relationship I have been unable to reproduce is 0..1 to many using a join table (not a nullable foreign key).
So it would look something like this:
TableA
{
ID (PrimaryKey)
TableB (0 or 1)
}
JoinTable
{
TableA_FK (PrimaryKey, ForeignKey),
TableB_FK (ForeignKey)
}
TableB
{
ID (PrimaryKey)
TableAs (Many)
}
Is this achievable in the code first style or will I have to generate an edmx model in order to use this database in EF without changing its schema?
Many thanks,
Phil
Here is an example without using a JoinTable class. The join table is configured through the fluent api.
class DataContext : DbContext
{
public DataContext(string connectionString)
: base(connectionString)
{ }
public DbSet<TableA> TableA { get; set; }
public DbSet<TableB> TableB { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TableA>().ToTable("TableA");
modelBuilder.Entity<TableB>().ToTable("TableB");
modelBuilder.Entity<TableB>()
.HasMany(x => x.TableAs)
.WithMany()
.Map(m =>
{
m.ToTable("JoinTable");
m.MapLeftKey("TableA_FK");
m.MapRightKey("TableB_FK");
});
}
}
class TableA
{
public int ID { get; set; }
public TableB TableB { get; set; }
}
class TableB
{
public int ID { get; set; }
public ICollection<TableA> TableAs { get; set; }
}
This will generate the following migration script, which looks like the schema you have.
public override void Up()
{
CreateTable(
"dbo.TableA",
c => new
{
ID = c.Int(nullable: false, identity: true),
TableB_ID = c.Int(),
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.TableB", t => t.TableB_ID)
.Index(t => t.TableB_ID);
CreateTable(
"dbo.TableB",
c => new
{
ID = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.ID);
CreateTable(
"dbo.JoinTable",
c => new
{
TableA_FK = c.Int(nullable: false),
TableB_FK = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.TableA_FK, t.TableB_FK })
.ForeignKey("dbo.TableB", t => t.TableA_FK, cascadeDelete: true)
.ForeignKey("dbo.TableA", t => t.TableB_FK, cascadeDelete: true)
.Index(t => t.TableA_FK)
.Index(t => t.TableB_FK);
}
If I've understood correctly, the following code using only data annotations should create your model.
public class TableA
{
public int ID { get; set; }
public JoinTable JoinTable { get; set; }
}
public class TableB
{
public int ID { get; set; }
public List<JoinTable> JoinTables{ get; set; }
}
public class JoinTable
{
[Key, ForeignKey("TableA")]
public int TableA_FK { get; set; }
[ForeignKey("TableB")]
public int TableB_FK { get; set; }
public TableA TableA { get; set; }
public TableB TableB { get; set; }
}
Interestingly, EF does not perform a round trip back to the original, if you generate the code-first models from the database model that this code creates then EF simplifies the model and removes the join table and creates a nullable foreign key.
Let me know if this works.
I may be wrong, but I believe you're missing some concepts here...
Why you have a JoinTable if it's doesn't have any column besides its foreign keys? It doesn't make sense... IHMO a nullable foreign key in TableA would be correct way.
When you work with Code-First it means that everything in your database will be represented by CODE. There's no reason to have a table in your database but not in your code...
EDMX handles that relationship because it uses "Associations" https://msdn.microsoft.com/en-us/data/jj713299#Overview
...backing to the code-first, you can represent your database like this:
public class JoinTable
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int TableA_FK { get; set; }
public int TableB_FK { get; set; }
//a future property here
public virtual TableA TableA { get; set; }
public virtual TableB TableB { get; set; }
}
public partial class TableA
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TableAId { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public virtual JoinTable JoinTable { get; set; }
}
public partial class TableB
{
public TableB()
{
JoinTable = new HashSet<JoinTable>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TableBId { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public virtual ICollection<JoinTable> JoinTable { get; set; }
}
}
public partial class Model1 : DbContext
{
public Model1()
: base("name=Model1")
{
}
public virtual DbSet<JoinTable> JoinTable { get; set; }
public virtual DbSet<TableA> TableA { get; set; }
public virtual DbSet<TableB> TableB { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<TableA>()
.HasOptional(e => e.JoinTable)
.WithRequired(e => e.TableA);
modelBuilder.Entity<TableB>()
.HasMany(e => e.JoinTable)
.WithRequired(e => e.TableB)
.HasForeignKey(e => e.TableB_FK)
.WillCascadeOnDelete(false);
}
}

EF Fluent API One-to-One relationship

I'm trying to configure an optional-required simple relationship, but EF doesn't seem to scaffold the right migration:
public class Applicant
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int ContactInfoId { get; set; }
public string PreferredCultureId { get; set; }
public virtual ApplicantContact Contact { get; set; }
public virtual Culture PreferredCulture { get; set; }
}
public ApplicantConfiguration()
{
HasKey(a => a.Id);
Property(a => a.FirstName).IsRequired().HasMaxLength(50);
Property(a => a.LastName).IsRequired().HasMaxLength(50);
HasOptional(a => a.Contact)
.WithRequired(c => c.Applicant)
.WillCascadeOnDelete();
HasOptional(a => a.PreferredCulture)
.WithMany()
.HasForeignKey(a => a.PreferredCultureId);
}
CreateTable(
"Main.Applicants",
c => new
{
Id = c.Int(nullable: false, identity: true),
FirstName = c.String(nullable: false, maxLength: 50),
LastName = c.String(nullable: false, maxLength: 50),
ContactInfoId = c.Int(nullable: false),
PreferredCultureId = c.String(maxLength: 128),
})
.PrimaryKey(t => t.Id)
.ForeignKey("General._Cultures", t => t.PreferredCultureId)
.Index(t => t.PreferredCultureId);
Why is ContactInfoId is not being generated as a foreign key and nullable, as it is the optional side of the relationship ?
In your domain class try
public class Applicant
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ApplicantContact Contact { get; set; }
public virtual Culture PreferredCulture { get; set; }
}
public class ContactInfo
{
// whatever contact info fields you have
}
public class Culture
{
// culture fields
}
then in your context have
public DbSet<ContactInfo> ContactInfos { get; set; }
public DbSet<Applicant> Applicants { get; set; }
public DbSet<Culture> Cultures { get; set; }
The Id fields should get automatically if they are int.