Relationship in Fluent API in Entity Framework Core - c#

I need to create a relationship between two tables that share a common column of type string - how can I do that?
This is what I did. Does this work?

You need to specify the foreign key (preferably always) and the principal key (if it is different to the primary key).
In order to use a principal key other than Primary Key, you need to create a UNIQUE Constraint. In EF you can do this by specifying HasIndex and IsUnique, otherwise you will get an error because the selected principal key is not a unique column.
See this example:
//HaweyTajer is the principal table
modelbuilder.Entity<HaweyTajer>(entity =>
{
//Mark the primary key
entity.HasKey(x => x.Id);
//Build the relation (Consider adding OnDelete())
entity.HasMany(x => x.Certivicates)
.WithOne(x => x.HaweyTaker)
.HasPrincipalKey(x => x.AZbaraNum)
.HasForeignKey(x => x.AZbaraNum);
//Build the index on the column inside the principal table
entity.HasIndex(x => x.AZbaraNum)
.IsUnique();
});
You might as well change the column type to not nvarchar(250) not null or add a filter to the index with HasFilter().
Reference: https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/fluent/relationships
Hope this helps.

Related

EF Core: Possible FK cycles or multiple cascade paths [duplicate]

I have a Booking class that has a booking contact (a Person) and a set of navigation properties (People) that links through a join table to another set of navigation properties (Bookings) in Person. How do I generate the Booking table with cascading deletes enabled for the booking contact relationship? When I leave it out of the fluent API code (default setting of cascade delete enabled) I get the following error message from migration:
Introducing FOREIGN KEY constraint
'FK_dbo.BookingPeople_dbo.People_PersonID' on table 'BookingPeople'
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.
modelBuilder.Entity<Person>()
.HasMany<Booking>(s => s.aBookings)
.WithRequired(s => s.Contact)
.HasForeignKey(s => s.ContactId);
modelBuilder.Entity<Booking>()
.HasMany(t => t.People)
.WithMany(t => t.Bookings)
.Map(m => {
m.ToTable("BookingPeople");
m.MapLeftKey("BookingID");
m.MapRightKey("PersonID");
});
The problem is you have multiple paths of cascade deletes that could end trying to delete the same row in the BookingPeople table in DB.
You can avoid such ambiguous delete paths by either disabling cascading delete in the one-to-many relationship using Fluent API:
modelBuilder.Entity<Booking>()
.HasRequired(s => s.Contact)
.WithMany(s => s.aBookings)
.HasForeignKey(s => s.ContactId)
.WillCascadeOnDelete(false);
Or by defining the relationship as optional (with a nullable foreign key, but you can not configure the relationship with cascade delete using Fluent Api).
modelBuilder.Entity<Booking>()
.HasOptional(s => s.Contact)
.WithMany(s => s.aBookings)
.HasForeignKey(s => s.ContactId);// ContactId is a nullable FK property
Also, you can remove the cascade delete convention by using:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Or in the case of the many-to-many relationship:
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
If you need to delete all the Bookings asociated with a Person when you delete it, my advice is configure the one-to-many relationship as optional, and override the SaveChanges method:
public override int SaveChanges()
{
Bookings.Local
.Where(r => r.ContactId == null)
.ToList()
.ForEach(r => Bookings.Remove(r));
return base.SaveChanges();
}
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. This way, you can find the orphans in the SaveChanges method and delete them

A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column 'Id' in TPT inheritance

I'm using EF code first, I have following entities:
I'm using TPT method for inheritance, to create one-to-one or zero relation, wrote following code:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasOptional(x => x.ProductionInstruction)
.WithRequired(x => x.Order);
}
I write following code to save a ProductionInstruction:
var pi = new ProductionInstruction();
/* set pi properties */
ctx.ProductionInstructions.Add(pi);
ctx.SaveChanges();
I get following error when ctx.SaveChanges() run:
A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'Id'.
Is there any way to implement 1..0-1 between my two entities, without above error?
In Entity Framework, a one-to-one mapping with a required principal is implemented by giving the dependent a primary key that's also a foreign key to the principal. The dependent copies its primary key from the principal.
In your case, EF wants ProductionInstruction.Id to be a foreign key to Order.Id, and its value should be copied from the Order it belongs to. However, because of the inheritance, ProductionInstruction.Id is an identity column (store-generated), so it can't be set in code.
You either have to remove the inheritance, so ProductionInstruction.Id can be mapped as not store-generated, or change the mapping to optional on both sides. The latter will give ProductionInstruction a separate foreign key to Order.

Entity Framework Code first: cycles or multiple cascade paths

I have a Booking class that has a booking contact (a Person) and a set of navigation properties (People) that links through a join table to another set of navigation properties (Bookings) in Person. How do I generate the Booking table with cascading deletes enabled for the booking contact relationship? When I leave it out of the fluent API code (default setting of cascade delete enabled) I get the following error message from migration:
Introducing FOREIGN KEY constraint
'FK_dbo.BookingPeople_dbo.People_PersonID' on table 'BookingPeople'
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.
modelBuilder.Entity<Person>()
.HasMany<Booking>(s => s.aBookings)
.WithRequired(s => s.Contact)
.HasForeignKey(s => s.ContactId);
modelBuilder.Entity<Booking>()
.HasMany(t => t.People)
.WithMany(t => t.Bookings)
.Map(m => {
m.ToTable("BookingPeople");
m.MapLeftKey("BookingID");
m.MapRightKey("PersonID");
});
The problem is you have multiple paths of cascade deletes that could end trying to delete the same row in the BookingPeople table in DB.
You can avoid such ambiguous delete paths by either disabling cascading delete in the one-to-many relationship using Fluent API:
modelBuilder.Entity<Booking>()
.HasRequired(s => s.Contact)
.WithMany(s => s.aBookings)
.HasForeignKey(s => s.ContactId)
.WillCascadeOnDelete(false);
Or by defining the relationship as optional (with a nullable foreign key, but you can not configure the relationship with cascade delete using Fluent Api).
modelBuilder.Entity<Booking>()
.HasOptional(s => s.Contact)
.WithMany(s => s.aBookings)
.HasForeignKey(s => s.ContactId);// ContactId is a nullable FK property
Also, you can remove the cascade delete convention by using:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Or in the case of the many-to-many relationship:
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
If you need to delete all the Bookings asociated with a Person when you delete it, my advice is configure the one-to-many relationship as optional, and override the SaveChanges method:
public override int SaveChanges()
{
Bookings.Local
.Where(r => r.ContactId == null)
.ToList()
.ForEach(r => Bookings.Remove(r));
return base.SaveChanges();
}
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. This way, you can find the orphans in the SaveChanges method and delete them

Entity Framework change Id type

I've changed type of Id property from Int to Guid, no other entities reference that Id and I've manually deleted all records of that entity from database, my generated migration class looks like this:
public override void Up()
{
AlterColumn("dbo.RoutineExercises", "RoutineExerciseId", c => c.Guid(nullable: false));
}
public override void Down()
{
AlterColumn("dbo.RoutineExercises", "RoutineExerciseId", c => c.Int(nullable: false, identity: true));
}
And I'm getting this error when I run update-database command:
The object 'PK_dbo.RoutineExercises' is dependent on column 'RoutineExerciseId'. ALTER TABLE ALTER COLUMN RoutineExerciseId failed because one or more objects access this column.
And my FluentAPI configuration looks like this:
modelBuilder.Entity<RoutineExercise>().HasKey(r => r.RoutineExerciseId);
modelBuilder.Entity<RoutineExercise>()
.HasRequired(r => r.Exercise)
.WithMany()
.HasForeignKey(r => r.ExerciseId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<RoutineExercise>()
.HasRequired(r => r.Routine)
.WithMany()
.HasForeignKey(r => r.RoutineId)
.WillCascadeOnDelete(true);
How can I fix this without dropping whole database ?
You might try doing the migration in stages - I don't know for sure but your fluent configuration is resulting in the creation of the primary key based on the field you want to change and the migration wizard can't factor in the need to drop the primary key convert the field to a guid and then rebuild a new primary key.
So my suggestion would be to do the migration in baby steps:
1. Remove the haskey from routineexerciseid and move it to another field or make a new composite key- ef needs a primary key
2. With the primary key moved you should be able to alter the column
3. Finally reinstate the primary key on that column.
Alternatively
Create a completely new column as a guid as the primary key, drop the old column and then if necessary rename the new column as required
I know this is an old question, but in case anyone else is looking for this answer, I just found it out myself. You need to drop the primary key before alters.
DropPrimaryKey("dbo.RoutineExercises",new[] { "RoutineExerciseId"});
It is easier to do a Migration Reset than changing a lot of things and possibly messing up your database. I do recommend making a back up of your data before proceeding with this though.
The process is outlined here:
https://weblog.west-wind.com/posts/2016/jan/13/resetting-entity-framework-migrations-to-a-clean-slate

EF4 CTP5 - Many To One Column Renaming

How do I override the default convention for the foreign key column in EF4 to specify a different column name?
For example, I have one entity with a property called Parent that references to other one of the same type.
EF4 tries to resolve the relation by looking for the foreign key named EntityId, but in my DB schema it is Entity_Id. How do I tell EF that the FK column name is not EntityId?
I've tried the following:
modelBuilder.Entity<SomeEntity>()
.HasOptional(m => m.Parent)
.WithMany()
.IsIndependent()
.Map(m => m.MapKey(k => k.Id, "Entity_Id")));
But I get an exception saying: Sequence contains more than one matching element.
Any help on this?
Thanks!
First: Upgrade to EF 4.1 RTW. CTP 5 is outdated and contains potentially many bugs which are fixed now.
If you have done the upgrade the following should work:
modelBuilder.Entity<SomeEntity>()
.HasOptional(m => m.Parent)
.WithMany()
.Map(c => c.MapKey("Entity_Id"));

Categories

Resources