Trying to create simple CRUD app using Asp Net Core. I have 2 entities:
Department and Employee( one to many ). I need to delete record from Department table. But when Im trying to delete record using OnDelete(DeleteBehavior.Restrict) or OnDelete(DeleteBehavior.ClientSetNull) i have exception:
UPDATE or DELETE in table"Departments" violates foreign key constraint
"FK_Employees_Departments_DepartmentCode" table"Employees"
How can i fix this problem ?
Entity Employee:
public class Employee
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required(ErrorMessage = "Input fullname of employee")]
public string FullName { get; set; }
[Required(ErrorMessage = "Input date of birth")]
public DateTime DateOfBirth { get; set; }
[Required(ErrorMessage = "Input code")]
public string Code { get; set; }
[Required(ErrorMessage = "Input fullname of employee")]
public int Salary { get; set; }
public string DepartmentCode { get; set; }
public Department Department { get; set; }
}
Entity Department:
public class Department
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required(ErrorMessage = "Input name of department")]
public string Name { get; set; }
[Required(ErrorMessage = "Input code of department")]
public string Code { get; set; }
public ICollection<Employee> Employees { get; set; }
public Department()
{
Employees = new List<Employee>();
}
}
Context class settings:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Department>()
.HasMany<Employee>(d => d.Employees)
.WithOne(e => e.Department)
.HasForeignKey(e => e.DepartmentCode)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Department>()
.HasKey(d => d.Code);
modelBuilder.Entity<Employee>()
.HasKey(e => e.Code);
modelBuilder.Entity<Department>()
.HasIndex(d => d.Name).IsUnique();
}
Of all fields in the DeleteBehavior enum only two actually add cascaded foreign key behavior to the database: Cascade and SetNull. All other options create foreign keys with no action on delete, but differ in what EF will do to its tracked entities.
In your case it should probably be SetNull because I assume that Employees can exist without Department. This setting will allow you to delete a Department object without loading its Employees. The database will set their DepartmentCode to null.
The delete behavior configured in EF can only be applied tho Entities that are tracked by EF change tracking. So you would need to load all Employees that belong to the department to make this work as expected.
BUT The database foreign key definition also defines the on delete action (cascading, set null, do nothing) So even if you code within your context a set null strategy, the DB still might apply different strategy for on delete. EF core defaults to cascade delete.
Related
I'm trying add migration using EF core 2 code first method. The issue is that, the entities with foreign key relationship are created with a foreign key id suffixed with '1' at the end and a redundant column with the same name but without the 1 at the end which is not a foreign key.
Examples are my 2 classes, Store and StoreVisit as shown below:
Store
[Table("Store")]
public class Store
{
public Store()
{
StoreVisits = new HashSet<StoreVisit>();
}
[Key]
public int StoreId { get; set; }
[StringLength(30)]
public string ShopName { get; set; }
[StringLength(50)]
public string ShopKeeper { get; set; }
public string ContactNo { get; set; }
[StringLength(70)]
public string Address { get; set; }
[StringLength(20)]
public string Street { get; set; }
[StringLength(50)]
public string City { get; set; }
public IEnumerable<StoreVisit> StoreVisits { get; set; }
}
Store Visit
[Table("StoreVisit")]
public class StoreVisit
{
[Key]
public int StoreVisitId { get; set; }
[StringLength(50)]
public string Location { get; set; }
[StringLength(50)]
public string Notes { get; set; }
[DataType(DataType.Time)]
public DateTime StartTime { get; set; }
[DataType(DataType.Time)]
public DateTime EndTime { get; set; }
public Store Store { get; set; }
}
The Visit class is created in the database with the column shown in the image below:
As you can see, the StoreVisit table has columns "StoreId1" which is the actual foreign key and "StoreId" which is not a foreign key.
I have even configured the relationship with Fluent API as below:
modelBuilder.Entity<Store>()
.HasMany(c => c.StoreVisits)
.WithOne(e => e.Store)
.IsRequired();
Can someone help.
Note that Entity Framework Core is smart enough to detect relationships among your classes which will be turned into database tables with relationships if you use its conventions. So this is redundant to use annotations like [Key] above StoreId property.
Second thing, As an advice, try to use simple and clean names for classes or properties as they can be potentially similar to those automatically created by EF. For example, in your case I prefer to avoid using store inside StoreVisit class name again (e.g in case of many to many relationship, derived table is named StoreVisit like one that you employed just without 's', Although your case is one to many),
And Final tip is the reason for appearing redundant StoreId column. Actually, In your case, this is not necessary to use Fluent API as EF can detect the relationship. In addition, you've written wrong configuration for modelBuilder. So remove it and let EF to generate it (unless you plan to have fully defined relationship to consume its advantages in your code).
The StoreId is one that you told EF to generate it (as required)
in modelBuilder.
The StoreId1 is EF Auto generated column (Foreign Key) based on one
to many relationship. '1' is appended in order to avoid column name duplication.
A foreign key needs to be defined on the class.
[Table("StoreVisit")]
public class StoreVisit
{
[Key]
public int StoreVisitId { get; set; }
public int StoreId { get; set; }
[StringLength(50)]
public string Location { get; set; }
[StringLength(50)]
public string Notes { get; set; }
[DataType(DataType.Time)]
public DateTime StartTime { get; set; }
[DataType(DataType.Time)]
public DateTime EndTime { get; set; }
public Store Store { get; set; }
}
It also would hurt to add the foreign key reference to the Fluent API.
modelBuilder.Entity<Store>()
.HasMany(c => c.StoreVisits)
.WithOne(e => e.Store)
.HasForeignKey(e => e.StoreId)
.IsRequired();
I have one to one relationship with foreign keys but the Cascade Delete is not enabled for some reason. The sample code is below.
public class AppRegistration
{
public int AppRegistrationId { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Username")]
public string UserName { get; set; }
[Required]
[StringLength(100)]
public string Password { get; set; }
[StringLength(20)]
public string StudentOrAgent { get; set; }
// navigation properties
public virtual AppStatus AppStatus { get; set; }
public virtual Agreement Agreement { get; set; }
public virtual AnotherTable AnotherTable { get; set; }
}
The dependent table with a foreign key is below.
public class Agreement
{
[Key]
[ForeignKey("AppRegistration")]
public int AppRegistrationId { get; set; }
public DateTime DateAgreed { get; set; }
public virtual AppRegistration AppRegistration { get; set; }
}
When I try to delete an entry from the generated AppRegistrations table I get a Reference constraint conflict.
I tried putting [Required] on the navigation property in the dependent table but it doesn't do anything - the Update-Database command shows the No pending code-based migrations. message. Any ideas? Thanks.
Update:
I'm getting the following error message:
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.AppStatus_dbo.AppRegistrations_AppRegistrationId". The conflict occurred in database "MVCapp", table "dbo.AppStatus", column 'AppRegistrationId'.
I decided to work out the cascade delete problem in a separate sample project. I found the following blog & MSDN pages very useful.
http://blog.bennymichielsen.be/2011/06/02/entity-framework-4-1-one-to-one-mapping/
http://msdn.microsoft.com/en-us/library/gg671256%28v=VS.103%29.aspx
http://msdn.microsoft.com/en-us/library/gg671273%28v=VS.103%29.aspx
Using the Code First approach create the following Model.
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public virtual Book Book { get; set; }
}
public class Book
{
public int CategoryId { get; set; }
public string BookTitle { get; set; }
public string BookAuthor { get; set; }
public string BookISBN { get; set; }
public virtual Category Category { get; set; }
}
(I realize the entity names suggest one-to-many relationship, but I am trying to model 1-to-1 relationship, as in my original question at the top.)
So, in the above model each Category can only have one Book.
In your DbContext-derived class add the following.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Book>()
.HasKey(t => t.CategoryId);
modelBuilder.Entity<Category>()
.HasRequired(t => t.Book)
.WithRequiredPrincipal(t => t.Category)
.WillCascadeOnDelete(true);
}
(The following namespaces are required for the above code: System.Data.Entity, System.Data.Entity.ModelConfiguration.Conventions.)
This properly creates the 1-to-1 relationship. You'll have a primary key in each table and also a foreign key in Book table with ON DELETE CASCADE enabled.
In the above code, on the Category entity I used WithRequiredPrincipal() with t => t.Category argument, where the argument is the foreign key column in the dependent table.
If you use WithRequiredPrincipal() without an argument you'll get an extra column in the Book table and you'll have two foreign keys in the Book table pointing to CategoryId in Category table.
I hope this info helps.
UPDATE
Later on I found answer directly here:
http://msdn.microsoft.com/en-us/data/jj591620#RequiredToRequired
A reason why you're not getting cascading delete is because your relationship is optional.
If you want the relationship required i.e. an AppRegistration has to have one Agreement you can use (cascading delete configured automatically):
public class Agreement
{
...
[Required]
public AppRegistration AppRegistration{ get; set; }
}
If you want the relationship to be optional with cascading delete you can configure this using Fluent API:
modelBuilder.Entity<AppRegistration>()
.HasOptional(a => a.Agreement)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
I have an app that was created using EF. The problem is that I noticed some extraneous foreign keys columns created in one of the tables. Dropping these columns causes an [SqlException (0x80131904): Invalid column name 'Material_Id' error.
Here is a simplified version of the class structure...
public class Hazard
{
public int Id { get; set; }
public string Name { get; set; }
}
public abstract class HazardAnalysis
{
public int Id { get; set; }
public int HazardId { get; set; }
public virtual Hazard Hazard { get; set; }
}
public class ProductHazard : HazardAnalysis
{
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
The table that was generated looked like this...
dbo.Hazards
Id int
Name string
Product_Id int
Since the relationship between ProductHazards and Hazards is 1:many, the Product_Id field should not be there. Dropping this column generates the Invalid column name 'Product_Id' error.
I've scoured the model for hours and can't find any valid reason for this column to exist.
Is there any way to update the model after manually dropping a column? I obviously don't want to drop and recreate the database.
I've also noticed that the productId of the current product is inserted in the dbo.Hazards Product_Id table whenever a new ProductHazard is created. Since there is a many-to-one relationship between ProductHazards and Hazards, when a new ProductHazard is created, the Product_Id field is updated with the ProductId of the new ProductHazard, which seems bizarre.
Any advice would be greatly appreciated.
Here is the DbSet code:
public DbSet<Hazard> Hazards { get; set; }
public DbSet<HazardAnalysis> HazardAnalyses { get; set; }
and also...
modelBuilder.Entity<HazardAnalysis>()
.HasRequired(e => e.Hazard)
.WithMany()
.HasForeignKey(e => e.HazardId)
.WillCascadeOnDelete(false);
You need to define the many part of the relationship. In this case, you need to add a collection property to your Hazard object, like below:
public class Hazard
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HazardAnalysis> HazardAnalyses { get; set; }
}
I have following classes
public class Employer
{
[Key]
public Int64 EmployerID { get; set; }
public String CompanyName { get; set; }
public virtual List<Employee> Employees { get; set; }
}
public class Employee
{
[Key]
public Int64 EmployeeID { get; set; }
public String EmployeeName { get; set; }
public virtual Employer EmployerInfo { get; set; }
}
In the Database context I have set the relation as
modelBuilder.Entity<Employer>()
.HasMany(p => p.Employees)
.WithRequired()
.Map(x => x.MapKey("EmployerID"));
After executing some actions, database gets created with Employee table having EmployerID as foreign key and one extra key EmployerInfo_EmployerID.
Now when I fetch employer data, I am getting employee details with it.
But when I tried to fetch employee data I am getting EmployerInfo as null. This is because I need relationship from Employee to EmployerInfo.
How do I set the bi-directional relationship in this context?
You need to update your fluent so your relationship mapping contains both ends:
modelBuilder.Entity<Employer>()
.HasMany(p => p.Employees)
.WithRequired(e => e.EmployerInfo)
.Map(x => x.MapKey("EmployerID"));
Okay, this should be really easy, but I've been tearing my hair out. Here's my POCO (which has to do with machine parts, so a part can be contained within a parent part):
public class Part
{
public int ID { get; set; }
public string Name { get; set; }
public Part ParentPart { get; set; }
}
When the database table is created, the column names are "ID", "Name", and "PartID". How do I change the name of that last column to "ParentPartID"?
Basically, you want to rename the foreign key in an Independent Association and this is the fluent API code that will do it:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Part>()
.HasOptional(p => p.ParentPart)
.WithMany()
.IsIndependent()
.Map(m => m.MapKey(p => p.ID, "ParentPartID"));
}
However, due to a bug in CTP5, this code throw as exception in self referencing associations (which is your association type). The workaround would be to change your association to a Foreign Key Association as follows:
public class Part
{
public int ID { get; set; }
public string Name { get; set; }
public int ParentPartID { get; set; }
[ForeignKey("ParentPartID")]
public Part ParentPart { get; set; }
}