Cannot create foreign key constraint on self-joining Many-to-Many relationship - ef-core-2.0

I have created the following classes:
public class Character
{
public int ID { get; set; }
public string Title { get; set; }
public ICollection<Relationship> RelatedTo { get; set; }
public ICollection<Relationship> RelatedFrom { get; set; }
}
public class Relationship
{
public int ToID { get; set; }
public int FromID { get; set; }
public Character CharacterFrom { get; set; }
public Character CharacterTo { get; set; }
public string Details { get; set; }
}
In my Context I have this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Relationship>()
.HasKey(r => new { r.ToID, r.FromID });
modelBuilder.Entity<Relationship>()
.HasOne(r => r.CharacterFrom)
.WithMany(r => r.RelatedTo)
.HasForeignKey(r => r.FromID)
.OnDelete(DeleteBehavior.ClientSetNull);
modelBuilder.Entity<Relationship>()
.HasOne(r => r.CharacterTo)
.WithMany(r => r.RelatedFrom)
.HasForeignKey(r => r.ToID)
.OnDelete(DeleteBehavior.ClientSetNull);
}
I think that it is right but I cannot apply the migration due to the following error:
Cannot create the foreign key "FK_Relationship_Character_FromID" with the SET NULL referential action, because one or more referencing columns are not nullable.
I've tried every combination of DeleteBehaviour for OnDelete. None of them work. I don't believe I can make the ICollections nullable and it doesn't seem right that I'd want to. I've spent two hours on this searching for answers. Every tutorial or explanation on EF Core that I've tried to follow seems to take a slightly different approach and be subtly incompatible with every other one. Please help!

The error is telling you that you cannot use DeleteBehavior.ClientSetNull (or DeleteBehavior.SetNull) because the corresponding FK property is not nullable - both ToID and FromID are of type int, hence does not allow setting to null (neither client nor server).
To turn off the cascade delete (in order to break the multiple cascade paths I guess) for required FK relationships, use DeleteBehavior.Restrict instead.

Related

FOREIGN KEY constraint may cause cycles or multiple cascade paths [error message]

I add provincesModel's pr_id as a foreign Key to clinicsModel. So the visual studio display me this error message
Introducing FOREIGN KEY constraint 'FK_dbo.ClinicsModels_dbo.ProvincesModels_pr_id' on table 'ClinicsModels' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I try to solve the issue by browsing on web to find some solution but I can't get how to solve this. So I remove the foreign key relation of pr_id from clinicsModel and run the project but still visual studio shows me the same error message. I also recreate the ClinicsModel and try to update database using Update-Database -Verebose Migrations using package manager console but still it shows the same error message.
Here's my code:
for ProvinceModel
namespace finalFyp.Models
{
public class ProvincesModel
{
[Key]
public int pr_id { get; set; }
public string pr_name { get; set; }
public ICollection<CitiesModel> cities { get; set; }
}
}
ClinicsModel:
public class ClinicsModel
{
[Key]
public int clinic_id { get; set; }
public string clinic_name { get; set; }
public string clinic_address { get; set; }
//Forigen Keys
public int ct_id { get; set; }
public CitiesModel city { get; set; }
}
}
As the error occurred when I redirecting to http://localhost:3110/Doctors/index
Here's the snapshot of of error message.
DoctorsModel:
public class DoctorsModel
{
[Key]
public int d_id { get; set; }
public string d_name { get; set; }
public string contact { get; set; }
public string cnic { get; set; }
public string email { get; set; }
public string gender { get; set; }
//Forigen Key
public ICollection<DocExperiencesModel> experiences { get; set; }
public ICollection<DocSpecialization> specializations { get; set; }
public ICollection<QualificationsModel> qualifications { get; set; }
public ICollection<DoctorProfileModel> profiles { get; set; }
}
For ease of understanding Here's the schema of my database.
Please guide me what I suppose to do? I will be very thankful to him/her.
From first glance, the error occured when pr_id added as foreign key to ClinicsModel because ProvincesModel has one-to-many relationship against ClinicsModel table which involves pr_id primary key field. Since all foreign keys which referencing pr_id are not nullable, all one-to-many relationships where ProvincesModel involved are having cascade delete enabled by default. Hence, it means when a ClinicsModel entity data is deleted, it will have 2 cascade delete paths: through CitiesModel and directly to ProvincesModel as shown in image below.
As a result, it establishing circular relationship with more than one cascade delete path from ClinicsModel to ProvincesModel which causes error 1785 in SQL Server.
To resolve relationships problem, pr_id foreign keys should declared as Nullable<int>:
public int? pr_id { get; set; }
Likewise, if ct_id (and other int foreign key properties which subjected to possible circular relationships) also returning same error, declare them with same way as above:
public int? ct_id { get; set; }
NB: If Fluent API (and Code First) is being used, try adding these lines below:
// taken from /a/20915232
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
// modified from /a/17127512
// used on all entities with circular relationships
var builder = new DbModelBuilder();
builder.Entity<CitiesModel>()
.HasRequired(x => x.ct_id)
.WithMany()
.WillCascadeOnDelete(false);
builder.Entity<ProvincesModel>()
.HasRequired(x => x.pr_id)
.WithMany()
.WillCascadeOnDelete(false);
Similar issues:
Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths - why?
Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths
Error message 1785 occurs when you create a FOREIGN KEY constraint that may cause multiple cascade paths

Entity Framework Code First - map same entity twice for different purpose

I have two models, One ApplicationUser which holds all users in the system and I have a Quotation model which will hold all Quotations made. now I want to store two mappings to ApplicationUser inside Quotations. So that I can map to created User as well as cancelled User. My model looks like this
public class Quotation
{
public int QuotationID { get; set; }
public DateTime QuotationDate { get; set; }
public DateTime QuotationCancelDate { get; set; }
public int ApplicationUserID { get; set; }
public virtual ApplicationUser CreatedUser { get; set; }
[ForeignKey("ApplicationUserID")]
public ApplicationUser CancelledUser { get; set; }
}
But this throws an error
Quotation_CancelledUser_Target_Quotation_CancelledUser_Source: : The types of all properties in the Dependent Role of a referential constraint must be the same as the corresponding property types in the Principal Role. The type of property 'ApplicationUserID' on entity 'Quotation' does not match the type of property 'Id' on entity 'ApplicationUser' in the referential constraint 'Quotation_CancelledUser'.
So I guess , The approach I am taking is wrong. Can anyone point out the correct way to achieve this?
The problem you are observing is called "Multiple Cascade Path". A Multiple Cascade Path happens when a cascade path goes from column col1 in table A to table B and also from column col2 in table A to table B.
The exception is caused by SQL Server when code first attempted to add table that has columns appearing more than once of another table.
In SQL Server, a table cannot appear more than one time in a list of all the cascading referential actions that are started by either a DELETE or an UPDATE statement. For example, the tree of cascading referential actions must only have one path to a particular table on the cascading referential actions tree.
You will need to use FluentAPI to configure the relationship. I am using EF5 currently and do not know if this can be accomplished in EF6/7.
So modifying your code sample, it would look like:
public class Quotation
{
public int QuotationID { get; set; }
public DateTime QuotationDate { get; set; }
public DateTime QuotationCancelDate { get; set; }
public int CreatedUserID { get; set; }
// Navigation property
public virtual ApplicationUser CreatedUser { get; set; }
public int CancelledUserID { get; set; }
// Navigation property
public virtual ApplicationUser CancelledUser { get; set; }
}
// Created a simple class for example
public class ApplicationUser
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
Now in you context class you can write:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Disable the default PluralizingTableNameConvention
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// Add configuration here
modelBuilder.Entity<Quotation>()
.HasKey(e => e.QuotationID);
modelBuilder.Entity<ApplicationUser>()
.HasKey(e => e.Id);
modelBuilder.Entity<Quotation>()
.HasRequired(a => a.CreatedUser)
.WithMany()
.HasForeignKey(u => u.CreatedUserID);
modelBuilder.Entity<Quotation>()
.HasRequired(a => a.CancelledUser)
.WithMany()
.HasForeignKey(u => u.CancelledUserID);
}
For more information with example refer this link.

BreezeJs - Using expand() when two foreign keys are related to a common entity

Say, I have two entities, Movement (id, #fromLocationId, #toLocationId), fromLocationId and toLocationId being two foreign keys of the second entity Location (id, name). I would like to write a Breeze query that retrieves all movements with location names related to fromLocationId and toLocationId. This is what I got thus far:
var query = breeze.EntityQuery('movement').expand('location');
When I debug it and check the first record, for example, I find out that it has a location() and location1() properties. I can retrieve the location name of fromLocationId from data[0].location().name() but cannot do the same with that of toLocationId, as location1() is null. I even tried var query = breeze.EntityQuery('movement').expand('location, location1'); but it is still not working.
Any ideas on how to solve this? Thanks in advance.
EDIT
Here are the .NET classes:
[Table("Location")]
public partial class Location
{
public Location()
{
Movements = new HashSet<Movement>();
Movements1 = new HashSet<Movement>();
}
public int Id { get; set; }
[StringLength(250)]
public string Name { get; set; }
public virtual ICollection<Movement> Movements { get; set; }
public virtual ICollection<Movement> Movements1 { get; set; }
}
[Table("Movement")]
public partial class Movement
{
public int Id { get; set; }
public int FromLocationId { get; set; }
public int ToLocationId { get; set; }
public virtual Location Location { get; set; }
public virtual Location Location1 { get; set; }
}
In the DbContext class, the relationships look like this:
modelBuilder.Entity<Location>()
.HasMany(e => e.Movements)
.WithRequired(e => e.Location)
.HasForeignKey(e => e.FromLocationId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Location>()
.HasMany(e => e.Movements1)
.WithRequired(e => e.Location1)
.HasForeignKey(e => e.ToLocationId)
.WillCascadeOnDelete(false);
Thanks.
OK, I found out what I needed to do. Here is the solution:
var query = breeze.EntityQuery('movement').expand('location, location')
I guess, Breeze understands this as, the first location in the expand() will be related to fromLocationId and the second location will be related to toLocationId. In other words, add as many entity as foreign keys related to that common entity.
Hope it helps someone else.

Fluent API mapping other side if a 1-1 relationship

I have a relationship set up between 2 tables using code first and Fluent API. This works and the schema is generated with the correct fields and key assignments. However, my problem is I need to be able to say SupplyPoint.SupplyPointPricing in my resultant Model. What I have below only gives me the other way around.
I figure there must be a way to keep the structure I have but just MAP SupplyPoint.
public partial class SupplyPoint
{
[Key]
public int SupplyPointId { get; set; }
public string SupplyPointName { get; set; }
}
public class SupplyPointPricing
{
public int SupplyPointPricingId { get; set; }
public int? SupplyPointId { get; set; }
[ForeignKey("SupplyPointId")]
public virtual SupplyPoint SupplyPoint { get; set; }
}
Then I use Fluent API and this gives me the 1-1 between the tables and the Schema I expect
modelBuilder.Entity<SupplyPointPricing>()
.HasOptional(a => a.SupplyPoint)
.WithMany()
.HasForeignKey(u => u.SupplyPointId);
Last time I had this problem I had to change the design around and have a foreign key in the SupplyPoint table. On that previous occasion that was OK since it was a 1-1 required where there are always a matching record. This time around I need to keep this structure since there is 1-0 between SupplyPoint and SupplyPointPricing.
This is how I have always done 1-to-1 relationships with my models, without using Fluent mapping:
public partial class SupplyPoint
{
[Key]
public int SupplyPointId { get; set; }
public virtual SupplyPointPricing SupplyPointPricing { get; set; }
}
public class SupplyPointPricing
{
[Key, ForeignKey("SupplyPoint")]
public int SupplyPointId { get; set; }
public virtual SupplyPoint SupplyPoint { get; set; }
}
As you can see, the SupplyPointPricing does not have an Id of its own, but only the Id of the SupplyPoint, which acts as a key in the 1-to-1 relatioship.
The only thing I'm not sure of is if the SupplyPointId can be a nullable int.
This will not create a foreign key on the SupplyPoint, so if you don't have a Pricing, the SupplyPoint.SupplyPointPricing property will be null.

"principal end of an association" - EF

Unable to determine the principal end of an association between the
types 'XYZ.Models.Attachment' and 'XYZ.Models.Accounts.User'. The
principal end of this association must be explicitly configured using
either the relationship fluent API or data annotations.
Exception has been thrown by the target of an invocation.
That error I get, when I try to update-database with my EF Models.
Part of User.cs:
[Table("Users")]
public class User
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public int MainPhotoId { get; set; }
[ForeignKey("MainPhotoId")]
public virtual Attachment Photo { get; set; }
}
Attachment.cs
[Table("Attachments")]
public class Attachment
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AttachmentId { get; set; }
public string name { get; set; }
public int UserId { get; set; }
public DateTime AddDate { get; set; }
public bool del { get; set; }
[ForeignKey("UserId")]
public virtual User Author { get; set; }
}
Why I get this error? And how to resolve it?
Regards
Mapping conventions detect a one-to-one relationship between User.Photo and Attachment.Author and cannot infer what the principal and what the dependent end is. Hence the exception.
Actually, according to your comments, you want two relationships and not a single one-to-one relationship. You can achieve that only by overriding the convention with Fluent API and you probably need to make one of the relationships optional because otherwise you have a circular mutual dependency between User and Attachment. You can, for example, make the User.Photo property optional by choosing a nullable foreign key:
public int? MainPhotoId { get; set; }
Then the mapping would look like this:
modelBuilder.Entity<User>()
.HasOptional(u => u.Photo)
.WithMany()
.HasForeignKey(u => u.MainPhotoId);
modelBuilder.Entity<Attachment>()
.HasRequired(a => a.Author)
.WithMany()
.HasForeignKey(a => a.UserId);
With this mapping you can remove the [ForeignKey] attributes because the definition of the FK properties is part of the Fluent API mapping (HasForeignKey).

Resources