Foreign Key creation in EF Core code first - c#

I have 2 models:
public class GaOrgOrders
{
public virtual Guid Id { get; set; }
[ForeignKey("GaOrganizations")]
public virtual Guid OrgId { get; set; }
[ForeignKey("GaApps")]
public virtual Guid AppId { get; set; }
[ForeignKey("GaOrgUserOrganizations")]
public virtual Guid OrgUserId { get; set; }
[ForeignKey("GaServicesTariffs")]
public virtual Guid ServiceTariffId { get; set; }
public virtual bool IsTemporary { get; set; }
public virtual Apps GaApps { get; set; }
public virtual Organizations GaOrganizations { get; set; }
public virtual OrgUserOrganizations GaOrgUserOrganizations { get; set; }
public virtual GaServicesTariffs GaServicesTariffs { get; set; }
}
and
public class GaOrganizations
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual bool IsDeleted { get; set; }
}
When I try to update database and add these tables I get an error:
Introducing FOREIGN KEY constraint 'FK_GaOrgOrders_GaOrganizations_OrgId' on table 'GaOrgOrders' 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.
If I remove "ForeignKey" attribute from OrgId property - error disappears, but it creates in database a second field(GaOrganizationsId) and makes it a FK. Other Keys work fine. What's wrong with OrgId?

EF doesn't understand the relationship between the two entities so you might need to clarify using fluent API in your "OnModelCreating" method.
Using something like
modelBuilder.Entity<ClassB>().HasRequired(x => x.ClassA).WithOptional(x => x.ClassB);
where ClassA and ClassB are my entities having a 1 to 0 or 1 relationship between them.

Related

EF Core 2.2.6: Unable to map 2 foreign keys to the same table

I am having issues trying to map two fields that are foreign keys into the same table. The use case is for a modifier and creator. My class already has the Ids, and then I wanted to add the full User object as virtual.
I am using a base class so that each of my tables have the same audit fields:
public class Entity
{
public long? ModifiedById { get; set; }
public long CreatedById { get; set; } = 1;
[ForeignKey("CreatedById")]
public virtual User CreatedByUser { get; set; }
[ForeignKey("ModifiedById")]
public virtual User ModifiedByUser { get; set; }
}
The child class is very simple:
public class CircleUserSubscription : Entity
{
[Required]
public long Id { get; set; }
public long SponsorUserId { get; set; }
[ForeignKey("SponsorUserId")]
public virtual User User { get; set; }
public long TestId { get; set; }
[ForeignKey("TestId")]
public virtual User Test { get; set; }
}
This is a standard junction table.
When I try to generate the migration, I am getting errors that I don't understand fully.
Unable to determine the relationship represented by navigation property 'CircleUserSubscription.User' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I tried what this answer had, but the code is basically the same: https://entityframeworkcore.com/knowledge-base/54418186/ef-core-2-2---two-foreign-keys-to-same-table
An inverse property doesn't make sense since every table will have a reference to the user table.
For reference, here is the User entity:
public class User : Entity
{
public long Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
I am hoping you all can help me out, TIA :)
EDIT: One thing to note, all of this worked fine when the entity class was as follows:
public class Entity
{
public long? ModifiedById { get; set; }
public long CreatedById { get; set; } = 1;
}
It was only after I added the entity that things went awry.

Entity Framework relations questions

I have three classes
public class SPR
{
public int ID { get; set; }
public string SubmittedBy { get; set; }
public virtual ICollection<SPRItem> AllItems { get; set; }
}
public class SPRItem
{
[Key]
public int ID { get; set; }
public string manufacturer { get; set; }
[ForeignKey("SPRItemDetails")]
public virtual SPRItemDetails ItemDetails { get; set; }
public string requestedMinimumQuantity { get; set; }
public virtual SPR SPR { get; set; }
}
public class SPRItemDetails
{
public int ID { get; set; }
public string ItemNumber { get; set; }
public virtual SPRItem SPRItem { get; set; }
}
So the SPR class has a collection of SPRItem and which has the ItemDetails object.
I have a web API method which maps the data to the SPR object and fills in the SPRItem list and ItemDetails object. But whenever I am trying to save it using Entity Framework code first I am getting this error
{"Message":"An error has occurred.","ExceptionMessage":"Unable to determine the principal end of an association between the types 'SharePoint.MultiSPR.Service.Models.SPRItemDetails' and 'SharePoint.MultiSPR.Service.Models.SPRItem'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
This is my Context
public System.Data.Entity.DbSet<SharePoint.MultiSPR.Service.Models.SPR> SPRs { get; set; }
public System.Data.Entity.DbSet<SharePoint.MultiSPR.Service.Models.SPRItem> SPRItem { get; set; }
public System.Data.Entity.DbSet<SharePoint.MultiSPR.Service.Models.SPRItemDetails> SPRItemDetails { get; set; }
Can someone please tell me how to configure the relations correctly.
Thanks
In a 1:1 relation you always have to indicate the principal and the dependent entity. The principal entity is the one that is most independent of the other, in this case SPRItem, presumably.
Next thing to decide is whether the relationship should be optional or required. I think, judging by the entity names, an SPRItemDetails will never exist without an SPRItem, so the relationship is 1:0..1 (not 0..1:0..1). Here's how to configure that:
modelBuilder.Entity<SPRItem>()
.HasOptional(si => si.ItemDetails)
.WithRequired(id => id.SPRItem);
This creates (or requires) an SPRItemDetails table having a primary key that's also a foreign key to SPRItem.

Introducing FOREIGN KEY constraint key on table table may cause cycles or multiple cascade paths. Specify ON DELETE ... Error

i was trying to run the Update-Database command in Nugget Package Manager console but wasnt successful as i kept getting the error
Introducing FOREIGN KEY constraint 'FK_dbo.TeamToLeaders_dbo.Teams_TeamId' on table 'TeamToLeaders' 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. See previous errors..
I want to set up relationship in which there is a class called Team.cs that contains the below properties
public class Team
{
public int TeamId { get; set; }
public string TeamName { get; set; }
public Decimal MonthlyTarget { get; set; }
public ICollection<SalesAgent> Agents { get; set; }
}
which means a team has many Agents and there is another class called SalesAgent.cs which contain info about agents
public class SalesAgent
{
[Key]
public int AgentId { get; set; }
public string AgentFirstName { get; set; }
public string AgentLastName { get; set; }
public string HomeAddress { get; set; }
public bool IsActive { get; set; }
public string AgentPhone { get; set; }
public Decimal MonthlyTarget { get; set; }
public int TeamId { get; set; }
public virtual Team Team { get; set; }
}
Now i want a class which i would be able add the relationship between a team and an agent i.e in essence i want to be able to assign a team leader to each team so i set up the class below
public class TeamToLeader
{
[Key]
public int TeamToLeaderId { get; set; }
[ForeignKey("Team")]
public int TeamId { get; set; }
public int AgentId { get; set; }
public virtual Team Team { get; set; }
[ForeignKey("AgentId")]
public virtual SalesAgent Agent { get; set; }
}
Upon running "Update-Database Command" I get an error that The ForeignKeyAttribute on property 'AgentId' on type 'SalesForce.Models.TeamToLeader' is not valid. The navigation property 'SalesAgent' was not found on the dependent type 'SalesForce.Models.TeamToLeader'. The Name value should be a valid navigation property name.
So i changed the model to
public class TeamToLeader
{
[Key]
public int TeamToLeaderId { get; set; }
[ForeignKey("Team")]
public int TeamId { get; set; }
[ForeignKey("SalesAgent")]
public int AgentId { get; set; }
public virtual Team Team { get; set; }
public virtual SalesAgent Agent { get; set; }
}
and that resulted in this error
Introducing FOREIGN KEY constraint 'FK_dbo.TeamToLeaders_dbo.Teams_TeamId' on table 'TeamToLeaders' 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. See previous errors.
Help please.
You should diasble OneToManyCascadeDeleteConvention to force EF not to use cascade delete. In DbContext add:
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
...
Or you can make foreign keys nullable:
public class TeamToLeader
{
[Key]
public int? TeamToLeaderId { get; set; }
[ForeignKey("Team")]
public int? TeamId { get; set; }
[ForeignKey("SalesAgent")]
public int AgentId { get; set; }
public virtual Team Team { get; set; }
public virtual SalesAgent Agent { get; set; }
}
Depends which behavior you prefer.
You can also use fluent API:
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<TeamToLeader>().HasRequired(i => i.Agent).WithMany().WillCascadeOnDelete(false);
}
...
Note that your model Team has many SalesAgent and many TeamToLeader.
There should be TeamToLeaders collection in your Team and SalesAgent model :
...
public virtual ICollection<TeamToLeader> TeamToLeaders { get; set; }
...
I'm not sure if you need Team to many SalesAgent relation anymore.
As this link, and this link saids...
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.
SQL server doesn't allow multiple delete paths to the same entity.
This link said that it can be solved by disabling OneToManyCascadeDeleteConvention and ManyToManyCascadeDeleteConvention, but those deleting operations SHOULD BE done by codes manually.

EF 6 how to set two foreign keys to same table

I have a table UserForms that has two foreign keys to a Countries table, but on creating my controller and create view (for the UserForms model) the two fields linking to the foreign keys do not appear. What should I do to sort this problem? Below are the two models:
public class UserForms
{
public int Id { get; set; }
public string FullNames { get; set; }
public Countries IndividualsCountry { get; set; }
public Countries BusinessCountry { get; set; }
}
public class Countries
{
public Countries()
{
this.STRBusinessCountry = new HashSet<UserForms>();
this.STRIndividualsCountry = new HashSet<UserForms>();
}
public int Id { get; set; }
public string NameOfCountry { get; set; }
[InverseProperty("IndividualsCountry")]
public virtual ICollection<UserForm> STRIndividualsCountry { get; set; }
[InverseProperty("BusinessCountry")]
public virtual ICollection<UserForm> STRBusinessCountry { get; set; }
}
The comment left by #T.Glatzer is correct. You should expose foreign key properties on your dependent entities:
public class UserForms
{
public int Id { get; set; }
public string FullNames { get; set; }
public int IndividualsCountryId { get; set; }
[ForeignKey("IndividualsCountryId")]
public virtual Countries IndividualsCountry { get; set; }
public int BusinessCountryId { get; set; }
[ForeignKey("BusinessCountryId")]
public virtual Countries BusinessCountry { get; set; }
}
Here I used int, but if either of these navigation properties are optional, you would just substitute int? or System.Nullable<int> instead (which will create an int NULL column in the database rather than an int NOT NULL).
Although EF does not require you to expose navigation properties, it is generally a good practice to. Trust me. It will help you avoid unexpected exceptions later on. In fact, some EF exception messages actually recommend exposing foreign key properties on the entity classes to help EF better figure out how to map relationships. Here is an example of one such exception. Note "Additional Information" section:
{"The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_dbo.DependentTable_dbo.PrincipalTable_Id". The conflict
occurred in database "DatabaseName", table "dbo.PrincipalTable", column
'Id'. The statement has been terminated."}
Additional information: An error occurred while saving entities that
do not expose foreign key properties for their relationships. The
EntityEntries property will return null because a single entity cannot
be identified as the source of the exception. Handling of exceptions
while saving can be made easier by exposing foreign key properties in
your entity types. See the InnerException for details.
#danludwig thanks for expounding #T.Glatzer answer this has worked for me! thank you. my final code that is now working is
public class UserForms
{
public int Id { get; set; }
public string FullNames { get; set; }
[ForeignKey("IndividualsCountry")]
public int? IndividualsCountryId { get; set; }
[ForeignKey("BusinessCountry")]
public int? BusinessCountryId { get; set; }
public virtual Countries IndividualsCountry { get; set; }
public virtual Countries BusinessCountry { get; set; }
}
public class Countries
{
public Countries()
{
this.STRBusinessCountry = new HashSet<UserForms>();
this.STRIndividualsCountry = new HashSet<UserForms>();
}
public int Id { get; set; }
public string NameOfCountry { get; set; }
[InverseProperty("IndividualsCountry")]
public virtual ICollection<UserForms> STRIndividualsCountry { get; set; }
[InverseProperty("BusinessCountry")]
public virtual ICollection<UserForms> STRBusinessCountry { get; set; }
}

Entity Framework Cascade Delete with Multiple Column Key

I have these models set up:
public class Advisor
{
public virtual int AdvisorId { get; set; }
public virtual int UserId { get; set; }
public User User { get; set; }
public ICollection<AdvisorStudentMap> AdvisorStudentMaps { get; set; }
}
public class AdvisorStudentMap
{
[Required]
public virtual int AdvisorId { get; set; }
[Required]
public virtual int UserId { get; set; }
}
public class User
{
public virtual int UserId { get; set; }
public virtual string UserName { get; set; }
public virtual string FirstName { get; set; }
public ICollection<AdvisorStudentMap> AdvisorStudentMaps { get; set; }
}
In my OnModelCreating I have:
modelBuilder.Entity<AdvisorStudentMap>()
.HasKey(t => new {t.AdvisorId, t.UserId});
In my fluent api how do I set it up so that when I delete an advisor, it deletes the AdvisorStudentMap as well? I keep getting the error: Message=Introducing FOREIGN KEY constraint 'FK_dbo.AdvisorStudentMaps_dbo.Users_UserId' on table 'AdvisorStudentMaps' 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. See previous errors.
Update - also in OnModelCreating I have:
modelBuilder.Entity<Advisor>()
.HasRequired(t => t.AdvisorStudentMaps).WithMany().WillCascadeOnDelete(true);
With that I get the error 'Cascading foreign key 'FK_dbo.Advisors_dbo.AdvisorStudentMaps_AdvisorId_UserId' cannot be created where the referencing column 'Advisors.AdvisorId' is an identity column.
You appear to be trying to model a many-to-many relationship between Students and Advisors. This would be the way you'd normally do that:
public class Advisor
{
//Key fields don't need to be marked virtual
public int AdvisorId { get; set; }
//If you want the property to lazy load then you should mark it virtual
public virtual ICollection<Student> Students{ get; set; }
//Advisors have a UserProfile
public int UserProfileId{get;set;}
public virtual UserProfile UserProfile {get; set;}
}
public class Student
{
public int StudentId { get; set; }
public virtual ICollection<Advisor> Advisors { get; set; }
//Students also have a UserProfile
public int UserProfileId{get;set;}
public virtual UserProfile UserProfile {get; set;}
}
public class UserProfile
{
public int UserProfileId{get;set;}
//NB not marked virtual - that is only needed on navigation properties when
//we want to use lazy loading
public string UserName {get;set}
public string FirstName {get;set}
}
Entity framework will automatically create the join table to model the relationship. You don't need the AdvisorStudentMap entity unless you need to add attributes to the relationship.
As for the cascade on delete problem. If you delete a User then this can cascade to the Student table and the Advisor table. There is a cascade path from Student to StudentAdvisorMap and another from Advisor to StudentAdvisorMap. Hence multiple cascade paths. Sql Server does not allow this. You will have to explicitly implement the deletes in your code to avoid this
I figured this out with help from this link: http://blog.cdeutsch.com/2011/09/entity-framework-code-first-error-could.html
As he says on the blog, it isn't very intuitive but here is the syntax that made it work:
modelBuilder.Entity<AdvisorStudentMap>()
.HasRequired(u=>u.User)
.WithMany(m=>m.AdvisorStudentMaps)
.WillCascadeOnDelete(false);

Categories