Code First Cascade Delete - c#

Morning, I used Code First to build my database, and inheritance hierarchy of Table per Type (TPT) approach was implemented. As below is a sample model of my project:
public enum Type
{
A = 0,
B = 1
}
public abstract class Device
{
public int DeviceId { get; set; }
public Type Type { get; set; }
}
[Table("DeviceA")]
public class DeviceA : Device
{
public int Value { get; set; }
}
[Table("DeviceB")]
public class DeviceB : Device
{
public int Value { get; set; }
}
And I have a table, which is related to both A & B. The model is as shown below:
public class Sir
{
public int SirId { get; set; }
[Required]
public virtual DeviceA DeviceA { get; set; }
public virtual DeviceB DeviceB { get; set; }
}
And the OnModelCreating function as below:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Sir>()
.HasRequired(s => s.DeviceA)
.WithMany()
.WillCascadeOnDelete(true);
modelBuilder.Entity<Sir>()
.HasOptional(s => s.DeviceB)
.WithMany()
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
Here comes the problem, when I attempt to delete a device in the superclass. A SQL error prompted:
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.DeviceA_dbo.Devices_DeviceId". The conflict occurred in database "CodeFirst", table "dbo.DeviceA", column 'DeviceId'.
As far as I understand, according to MSDN documentation in http://msdn.microsoft.com/en-us/data/jj591620.aspx (Under the header section of Enabling Cascade Delete), it states that . 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.
But still, I fail to cascade delete a record A in the superclass after trying for hours.
Please advise. Thanks !

Try mapping the foreign keys:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Sir>()
.HasRequired(s => s.DeviceA)
.WithMany()
.Map(m => m.MapKey("DeviceA_Id"))
.WillCascadeOnDelete(true);
modelBuilder.Entity<Sir>()
.HasOptional(s => s.DeviceB)
.WithMany()
.Map(m => m.MapKey("DeviceB_Id"))
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}

Related

Can you help me Many to One Relation for it?

modelBuilder.Entity<Food>(food =>
{
food.ToTable("foods");
food.Property(e => e.FoodCategoryId).HasColumnName("food_category_id").IsRequired();
food.HasOne(e => e.FoodCategory).WithMany().HasForeignKey(e =>
e.FoodCategoryId).HasConstraintName("fk_foods_food_categories_id");
});
I wrote one to many relation.But i need many to one
One to many and many to one are the same definition but this article Configuring One To Many Relationships in Entity Framework Core can help you to do this.
The following model represents companies and employees with an inverse navigation property defined in the dependent entity (Employee) but no matching foreign key property in the dependent
note: you can access the Foreign key from navigation property
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Company Company { get; set; }
}
A company has many employees, each with one company. That relationship is represented as follows:
// This method belongs to your context class which is inherited from DbContext
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.HasMany(c => c.Employees)
.WithOne(e => e.Company);
}
It can also be configured by starting with the other end of the relationship:
// This method belongs to your context class which is inherited from DbContext
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasOne(e => e.Company)
.WithMany(c => c.Employees);
}

Rename foreign key column not working in fluent api

I am trying to make a self referencing table
public class Category
{
// PK
public int CategoryId { get; set; }
// Property
public string CategoryName { get; set; }
// FK
public int? ParentCategoryId { get; set; }
public virtual ICollection<Category> ParentCategories { get; set; }
public virtual ICollection<Product> Products { get; set; } // Product is defined later
}
and the configuration:
public class CategoryConfiguration : EntityTypeConfiguration<Category>
{
public CategoryConfiguration():base()
{
HasKey(c => new { c.CategoryId });
HasOptional(c => c.ParentCategories)
.WithMany()
.HasForeignKey(c => c.ParentCategoryId );
}
}
the idea is to use ParentCategoryId as the column name, but it's not working. Instead it generated a column named: Category_CategoryId.
I have tried to use .Map(c => c.MapKey("ParentCategoryId")) and the result was the same.
I don't think it is the reason of self referencing because the same thing happen in the many-to-many relationship :
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
and the Product configuration
public class ProductConfiguration:EntityTypeConfiguration<Product>
{
public ProductConfiguration():base()
{
// Many-to-Many
HasMany(c => c.Categories)
.WithMany()
.Map(p =>
{
p.MapLeftKey("ProductRefId");
p.MapRightKey("CategoryRefId");
p.ToTable("ProductCategory");
});
}
}
The table name is ProductCategories instead of ProductCategory
The foreign key is Product_ProductId and Category_CategoryId
They all not what is expecting.
how can i solve the problem? Please help.
Thank you!
Update 1
strange thing is if I define it via DbModelBuilder, then it works
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>()
.HasOptional(c => c.ParentCategories)
.WithMany()
.HasForeignKey(c => c.ParentCategoryId);
}
the foreign key become ParentCategoryId as Expected.
Problem solved, I made a mistake. I didn't hook the configuration into DbContext.
by add these into the DbContext, the columns are renamed as expected.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new CategoryConfiguration());
modelBuilder.Configurations.Add(new ProductConfiguration());
}

Disable cascade deletion with EF code first doesn't work?

I have the following classes and I've created the database tables using EF Code-first. However, I found the deletion cascade is on and tried to remove it.
public class Category
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(100)]
public string Title { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
public class Event
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[ForeignKey("Category")]
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}
And I have added the following code in the DbContext class.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Use singular table names
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
Database.SetInitializer<MyContext>(null);
modelBuilder.Entity<Category>()
.HasMany(c => c.Events)
.WithRequired()
.HasForeignKey(e => e.CategoryId)
.WillCascadeOnDelete(false);
}
However, the generated migration code generate two AddForeignKey states with one with cascadeDeletion and another one without it.
AddForeignKey("dbo.Event", "CategoryId", "dbo.Category", "Id");
AddForeignKey("dbo.Event", "CategoryId", "dbo.Category", "Id", cascadeDelete: true);
CreateIndex("dbo.Event", "CategoryId");
CreateIndex("dbo.Event", "CategoryId");
The .WithRequired() call you use configures the relationship to be optional:required without a navigation property on the other side of the relationship so that results in one AddForeignKey call.
Then you have a Category navigation property that hasn't been configured in the FluentAPI so EF generates another AddForeignKey call that uses the default configuration.
Try using the override of WithRequired that configures the relationship to be optional:required with a navigation property on the other side of the relationship
modelBuilder.Entity<Category>()
.HasMany(c => c.Events)
.WithRequired(e => e.Category)
.HasForeignKey(e => e.CategoryId)
.WillCascadeOnDelete(false);

Entity Framework 4.1 InverseProperty Attribute and ForeignKey

I will create two references between Employee and Team entities with foreign keys.
So I defined two entities as follow
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
[ForeignKey("FirstTeam")]
public int FirstTeamId { get; set; }
[InverseProperty("FirstEmployees")]
public virtual Team FirstTeam { get; set; }
[ForeignKey("SecondTeam")]
public int SecondTeamId { get; set; }
[InverseProperty("SecondEmployees")]
public virtual Team SecondTeam { get; set; }
}
public class Team
{
public int Id { get; set; }
public string TeamName { get; set; }
[InverseProperty("FirstTeam")]
public virtual ICollection<Employee> FirstEmployees { get; set; }
[InverseProperty("SecondTeam")]
public virtual ICollection<Employee> SecondEmployees { get; set; }
}
I thought it is correct theoretically, but it shows the Exception as follow :
{"Introducing FOREIGN KEY constraint 'Employee_SecondTeam' on table 'Employees' 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."}
Can anybody help me?
Thanks in advance
Kwon
It is theoretically correct but SQL server (not Entity framework) doesn't like it because your model allows single employee to be a member of both First and Second team. If the Team is deleted this will cause multiple delete paths to the same Employee entity.
This cannot be used together with cascade deletes which are used by default in EF code first if you define foreign key as mandatory (not nullable).
If you want to avoid the exception you must use fluent mapping:
public Context : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<Team> Teams { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Employee>()
.HasRequired(e => e.SecondTeam)
.WithMany(t => t.SecondEmployees)
.HasForeignKey(e => e.FirstTeamId)
.WillCascadeOnDelete(false);
...
}
}
This will result in scenario where you must delete members of SecondTeam manually before you delete the team.
All is correct in previous answer, but one thing is wrong
modelBuilder.Entity<Employee>()
.HasRequired(e => e.SecondTeam)
.WithMany(t => t.SecondEmployees)
.HasForeignKey(e => e.SecondTeamId) // mistake
.WillCascadeOnDelete(false);
FirstTeamId instead of SecondTeamId will cause that in SecondTeam navigation property will be always FirstTeam

Entity Framework 4.1 Code First Foreign Key Id's

I have two entities referenced one to many. When entity framework created the table it creates two foreign keys, one for the key I have specified with the fluent interface and the other for the ICollection. How do I get rid of the duplicate foreign key?
public class Person
{
public long RecordId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Username { get; set; }
public long DepartmentId { get; set; }
public virtual Department Department { get; set; }
}
public class Department
{
public long RecordId { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> People { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasRequired(p => p.Department)
.WithMany()
.HasForeignKey(p => p.DepartmentId)
.WillCascadeOnDelete(false);
}
Thanks!
You must specify the many-end of the association explicitely:
modelBuilder.Entity<Person>()
.HasRequired(p => p.Department)
.WithMany(d => d.People)
.HasForeignKey(p => p.DepartmentId)
.WillCascadeOnDelete(false);
Otherwise EF will assume that there are two associations: One which is not exposed in Department with the foreign key DepartmentId and navigation property Department in the Person class as you have defined in the Fluent code - and another association which belongs to the exposed navigation property People but with another not exposed end in Person and a foreign key automatically created by EF. That's the other key you see in the database.
The default Code First conventions detect your DepartmentId foreign key, since it is, well, conventional. I think you should remove the Fluent definition:
modelBuilder.Entity<Person>()
.HasRequired(p => p.Department)
.WithMany()
.WillCascadeOnDelete(false);
best thing is to remove departmentid property from Person class and add the following statement. MapKey will create foreign key column with the name you specify
modelBuilder.Entity<Person>().HasRequired(p => p.Department)
.WithMany().Map(x=>x.MapKey("DepartmentId"))
.WillCascadeOnDelete(false);

Categories