code first relationships with multiple foreign keys - entity-framework

I have a scenario I'm getting a little muddled with using EF code first. The classes I've created are below:
public class Company
{
public int Id { get; set; }
public List<Contact> Contacts { get; set; }
public List<Job> Jobs { get; set; }
}
public class Contact
{
public int Id { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
public int CompanyId { get; set; }
public List<Job> Jobs { get; set; }
}
public class Job
{
public int Id { get; set; }
[ForeignKey("CompanyContactId")]
public virtual CompanyContact CompanyContact { get; set; }
public int CompanyContactId { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
public int CompanyId { get; set; }
}
However, when I build the DB I get the following error:
Introducing FOREIGN KEY constraint 'FK_Contacts_Company_CompanyId' on table 'Contacts' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
So a little research indicates the answer to this is to use the Fluent API to define the mappings as required but I can't get my head around how to do this or find an example of a similar scenario.
I realise I could remove the Company class from Job and navigate through Contact but I'd prefer not to if possible.
Any help gratefully received

You want to use the EF model builder to set up these relationships.
An example of how you would do this for one of your properties would be the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().HasOptional(e => e.Company).WithMany(c=>c.Contacts);
}
For more of an explanation around how to use the modelbuilder take a look at my article on EF Navigation Properties

Related

Entityframework core how to map Many-to-Many without creating a join table

I am trying this activity to clone from existing .edmx project to code first.
I have two entities. I want to have many to many relation without creating a new table. I am using EF Core 2.0 code first approach.
Please find below the entity i have created. I am not sure whether is this is the right way to do this.
I would like to have the foreign key column on both the tables ie. WorkflowId and WorkCaseId on WorkCase and Workflow tables respectively.
public class WorkCase
{
[Key]
public int WorkCaseId { get; set; }
public int WorkflowId { get; set; }
public int CaseDetailId {get;set;}
public CaseDetail CaseDetail {get;set;}
public WorkFlow WorkFlow { get; set; }
public ICollection<WorkFlow> WorkFlows { get; set; }
}
public class WorkFlow : BaseEntity
{
[Key]
public int WorkFlowId { get; set; }
public string Comment { get; set; }
public DateTime? UpdateDate { get; set; }
public int WorkCaseId { get; set; }
public WorkCase WorkCase { get; set; }
public ICollection<WorkCase> WorkCases { get; set; }
}
My expectation is as below. Can anyone help how to achieve the EF Core configuration:
- Workcase will have the latest workflowid
- workflow will have history for each workcaseid.
Thanks
UPDATE: EF5+ supports many-to-many without explicitly mapping the join table. See this EF announcement
As commented on your question, EF Core <= 2.2 does not yet support many-to-many relationships without a join table. This is an issue tracked in the EF Core repo's backlock, and will maybe make it into version 3.0.
In your case you'll need to introduce a new table that relates to both parent tables. In this case, your model will like something like the following:
public class WorkCase
{
public int WorkCaseId { get; set; }
public int CaseDetailId { get; set; }
public CaseDetail CaseDetail { get; set; }
public ICollection<WorkCaseWorkflow> Workflows { get; set; }
}
public class Workflow
{
public int WorkflowId { get; set; }
public string Comment { get; set; }
public DateTime? UpdateDate { get; set; }
public ICollection<WorkCaseWorkflow> WorkCases { get; set; }
}
public class WorkCaseWorkflow
{
public int WorkCaseId { get; set; }
public WorkCase WorkCase { get; set; }
public int WorkflowId { get; set; }
public Workflow Workflow { get; set; }
}
Then in your DbContext subclass, override the OnModelCreating and add the following code:
protected override void OnModelCreating(ModelBuilder builder)
{
var ww = builder.Entity<WorkCaseWorkflow>();
ww.HasKey(w => new { w.WorkCaseId, WorkflowId });
ww.HasOne(w => w.WorkCase)
.WithMany(wc => wc.Workflows)
.HasForeignKey(w => w.WorkCaseId);
ww.HasOne(w => w.Workflow)
.WithMany(wc => wc.WorkCases)
.HasForeignKey(w => w.WorkflowId);
}
I'm not familiar with your model, but you can move the shared properties to the join table.
However, there is a great series of articles by #Arthur Vickers (a member in the EF Core dev team) on how to easen up many-to-many relationships:
Part 1: The basics
Part 2: Hiding as IEnumerable
Part 3: Hiding as ICollection
Part 4: A more general abstraction

How can I name a navigation property a different name from it's entity name in my EF POCO?

I have a POCO Entity named Employee.
And then I have a second POCO Entity named Case.
I want a navigation property that looks like instead this:
public class Case : BaseEntity
{
public long EmployeeId { get; set; }
public virtual Employee Employee{ get; set; }
like this:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
public virtual Employee InitialContact { get; set; }
I want to name my property InitialContact. Not Employee.
But I get this error when EF tries to create the Database:
Unable to determine the relationship represented by navigation property 'Case.InitialContact' of type 'Employee'. Either manually configure the relationship, or ignore this property from the model.
Update 1:
I got it to work like this:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
[ForeignKey("Id")]
public virtual Employee InitialContact { get; set; }
public DateTime InitalConsultDate { get; set; }
public Guid AppUserId { get; set; }
public virtual AppUser LerSpecialist { get; set; }
}
The primary key is ID in my BaseEntity. Not EmployeeId.
But I have second part to my question.
Here is my Complete Employee POCO:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Hrsa.Core.Generic.Model.Framework.Concrete;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Hrsa.Core.Generic.Model.Lerd
{
public class Employee : BaseEntity
{
[BindNever]
public string Email { get; set; }
[BindNever]
public long OrganizationId { get; set; }
[BindNever]
public string Supervisor { get; set; }
[BindNever]
public string SupervisorEmail { get; set; }
[BindNever]
public string FirstName { get; set; }
[BindNever]
public string LastName { get; set; }
public string Notes { get; set; }
[BindNever]
public long BargainingUnitId { get; set; }
[BindNever]
public long PayPlanId { get; set; }
[BindNever]
public long GradeRankId { get; set; }
[BindNever]
public long PositionTitleId { get; set; }
[BindNever]
public long SeriesId { get; set; }
public bool IsUnionEmployee { get; set; }
public virtual Organization Organization { get; set; }
public virtual BargainingUnit BargainingUnit { get; set; }
public virtual PayPlan PayPlan { get; set; }
public virtual GradeRank GradeRank { get; set; }
public virtual PositionTitle PositionTitle { get; set; }
public virtual Series Series { get; set; }
public virtual ICollection<UnionHours> UnionHours { get; set; }
public virtual ICollection<Case> Cases { get; set; }
[NotMapped]
public string UnionEmployeeYesNo => (IsUnionEmployee) ? "Yes" : "No";
}
}
I want my Employee to have many Cases:
public virtual ICollection<Case> Cases { get; set; }
Here is my complete Cases POCO:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
[ForeignKey("Id")]
public virtual Employee InitialContact { get; set; }
public DateTime InitalConsultDate { get; set; }
public Guid AppUserId { get; set; }
public virtual AppUser LerSpecialist { get; set; }
}
So now my DB looks like this:
So I have my InitialContactId in Cases ok.
But now I need my Case to have many Employees.
So I add this in to my Case POCO:
public virtual ICollection<Employee> Employees { get; set; }
Now it looks like this:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
[ForeignKey("Id")]
public virtual Employee InitialContact { get; set; }
public DateTime InitalConsultDate { get; set; }
public Guid AppUserId { get; set; }
public virtual AppUser LerSpecialist { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
Now when I run it, I get this error again:
Unable to determine the relationship represented by navigation property 'Case.InitialContact' of type 'Employee'. Either manually configure the relationship, or ignore this property from the model.
Update 2:
I found this article for a Many-Many relationship in .Net Core 1:
http://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration
So now I have a bridge lookup entity:
public class EmployeeCase
{
[ForeignKey("Id")]
public long EmployeeId { get; set; }
public Employee Employee { get; set; }
[ForeignKey("Id")]
public long CaseId { get; set; }
public Case Case { get; set; }
}
Employee POCO:
Changed:
public virtual ICollection<Case> Cases { get; set; }
to:
// Mapping - Collection of Cases
public virtual ICollection<EmployeeCase> EmployeeCases { get; set; }
Case POCO:
Changed:
public virtual ICollection<Employee> Employees { get; set; }
to:
// Mapping - Collection of Employees
public virtual ICollection<EmployeeCase> EmployeeCases { get; set; }
In my AppDbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
#region Many-to-Many Employees Cases
modelBuilder.Entity<EmployeeCase>()
.HasKey(ec => new { ec.EmployeeId, ec.CaseId });
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Employee)
.WithMany(e => e.EmployeeCases)
.HasForeignKey(ec => ec.EmployeeId);
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Case)
.WithMany(c => c.EmployeeCases)
.HasForeignKey(ec => ec.CaseId);
#endregion
}
Now when I run I get this error:
An exception of type 'System.Data.SqlClient.SqlException' occurred in Microsoft.EntityFrameworkCore.Relational.dll but was not handled in user code
Additional information: Introducing FOREIGN KEY constraint 'FK_EmployeeCase_Employees_EmployeeId' on table 'EmployeeCase' 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.
Update 3:
Finally got my tables the way I want with this piece of code from:
Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths - why?
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Get rid of Cascading Circular error on ModelBuilding
foreach (var relationShip in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationShip.DeleteBehavior = DeleteBehavior.Restrict;
}
#region Many-to-Many Employees Cases
modelBuilder.Entity<EmployeeCase>()
.HasKey(ec => new { ec.EmployeeId, ec.CaseId });
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Employee)
.WithMany(e => e.EmployeeCases)
.HasForeignKey(ec => ec.EmployeeId);
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Case)
.WithMany(c => c.EmployeeCases)
.HasForeignKey(ec => ec.CaseId);
#endregion
base.OnModelCreating(modelBuilder);
}
Update 4:
This did not work after all.
Remvoving the delete behavior for everything messes up my other relationships and I get errors.
How can I fix this?
This is disgusting.
So wishing I did not go Core.
Entity Framework uses conventions to guess how to map your C# model to database objects.
In your case you violate convention by custom name, so you should explain Entity Framework how to map this stuff.
There are two possible ways: attributes and fluent API. I'd suggest to use the latter one.
See section "Configuring a Foreign Key Name That Does Not Follow the Code First Convention" here: Entity Framework Fluent API - Relationships
I have made it a habit of explicitly defining my relationships as EF does not always get them the way I want. I like to create a Mapping folder that contains my entity maps. The fluent api works great for this and inherits from EntityTypeConfiguration.
Try this.
public class CaseMap : EntityTypeConfiguration<Case>
{
public CaseMap()
{
HasKey(m => m.Id)
HasRequired(m => m.InitialContact)
.WithMany(e => e.Cases)
.HasForeignKey(m => m.InitialContactId);
}
}
Almost forgot. You need to tell your DbContext where to find these mappings. Add this to your DbContexts OnModelCreating method.
modelBuilder.Configurations.AddFromAssembly(typeof(MyContext).Assembly);
This is what worked finally for the Cascading Delete circular references on the many-to-many in EF Core:
// Get rid of Cascading Delete Circular references error.
var type = modelBuilder.Model.GetEntityTypes().Single(t => t.Name == "Hrsa.Core.Generic.Model.Lerd.EmployeeCase");
foreach (var relationship in type.GetForeignKeys())
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
You have to get the Entity representing the many to many lookup only.
And from there restrict the DeleteBehavior.

Defining foreign key constraints with Entity Framework code-first

I have following entity class called Code. It stores categories of different kinds - the data for which I would have otherwise needed to create many small tables e.g. User Categories, Expense Categories, Address types, User Types, file formats etc.
public class Code
{
public int Id { get; set; }
public string CodeType { get; set; }
public string CodeDescription { get; set; }
public virtual ICollection<Expense> Expenses { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
:
: // many more
}
The class Expense looks like this:
public class Expense
{
public int Id { get; set; }
public int CategoryId { get; set; }
public virtual Code Category { get; set; }
public int SourceId { get; set; }
public double Amount { get; set; }
public DateTime ExpenseDate { get; set; }
}
With the above class definitions, I have established 1:many relation between Code and Expense using the CategoryId mapping.
My problem is, I want to map the SourceId field in Expense to the Code object. Which means, Expense object would contain
public Code Source { get; set; }
If I use this, at runtime I get an error about cyclic dependencies.
Can someone please help?
You will need to disable cascading delete on at least one of the two relationships (or both). EF enables cascading delete by convention for both relationships because both are required since the foreign key properties are not nullable. But SQL Server doesn't accept multiple cascading delete paths onto the same table that are introduced by the two relationships. That's the reason for your exception.
You must override the convention with Fluent API:
public class Code
{
public int Id { get; set; }
//...
public virtual ICollection<Expense> Expenses { get; set; }
//...
}
public class Expense
{
public int Id { get; set; }
public int CategoryId { get; set; }
public virtual Code Category { get; set; }
public int SourceId { get; set; }
public virtual Code Source { get; set; }
//...
}
Mapping with Fluent API;
modelBuilder.Entity<Expense>()
.HasRequired(e => e.Category)
.WithMany(c => c.Expenses)
.HasForeignKey(e => e.CategoryId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Expense>()
.HasRequired(e => e.Source)
.WithMany()
.HasForeignKey(e => e.SourceId)
.WillCascadeOnDelete(false);

Entity Framework 4.3.1 how to create associations

my code like below
public class User
{
public int ID { get; set; }
public int BillingAddressID { get; set; }
public Address BillingAddress { get; set; }
public IList<Shipment> Shipments { get; set; }
}
public class Address
{
public int ID { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
public class Shipment
{
public int ID { get; set; }
public string State { get; set; }
public int DeliveryAddressID { get; set; }
public Address DeliveryAddress { get; set; }
public User ShipUser { get; set; }
//[ForeignKey("ShipUser")]
public int ShipUserID { get; set; }
//public int UserId { get; set; }
}
public class TestContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<Shipment> Shipments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Shipment>().HasRequired(u => u.ShipUser)
.WithMany(d => d.Shipments)
.HasForeignKey(c => c.ShipUserID)
.WillCascadeOnDelete(false);
}
}
if i remove the override method,i will get an error "SqlException: Introducing FOREIGN KEY constraint 'FK_Shipments_Users_ShipUserID' on table 'Shipments' 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."
if i remove ShipUserID in Shipment Class,it will work ok,when i see the table that is created by ef,i found a column named Shipment_UserID in table Shipment.I don`t know why.
if rename the class indenty key to UserID,it also work ok.
I try it anyway,but I don`t know the reason, I need some books about EF associations.
If you don't have mapping specified without cascadeDelete=false for one relationship it will create multiple cascade paths if you have tow relationships to user from Shipment.
By convention you can use public
Public User ShipUser { get; set; }
public int ShipUserID { get; set; }
it will use ShipUserID as foreign key by convention.
If you remove ShipUserID Ef need to create his own foreign key to keep the relationship . that is your ' Shipment_UserID'
rename the class indenty key to UserID I don't understand what you meant.
Here is a good tutorial to start with

Foreign Keys in Entity Framework - Cycles or multiple cascade paths error

Using EF Code First i have the following, for example:
public class Blog
{
public int BlogID { get; set; }
public string Content { get; set; }
public virtual User User { get; set; }
public virtual ICollection<BlogMeta> BlogMeta { get; set; }
}
public class BlogMeta
{
public int BlogMetaID { get; set; }
public string Content { get; set; }
public virtual User User { get; set; }
public virtual Blog Blog { get; set; }
}
This successfully generates the tables Blog and BlogMeta and creates the foreign key relationship with the User table. After reading this i changed this to the following:
public class Blog
{
public int BlogID { get; set; }
public string Content { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
public virtual ICollection<BlogMeta> BlogMeta { get; set; }
}
public class BlogMeta
{
public int BlogMetaID { get; set; }
public string Content { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
public int BlogID { get; set; }
public virtual Blog Blog { get; set; }
}
and now it doesn't work. It generates the tables and then throws the following error when trying to create the relationships:
Introducing FOREIGN KEY constraint 'BlogMeta_User' on table 'BlogMeta' may cause cycles or multiple cascade paths.
So what is the advantage of introducing the public int UserID and why does it fail when doing so?
EDIT:
Ok, so i've come across this answer which outlines the difference between Independent Associations and Foreign Key Associations... which it turns out is what i was talking about. So this leaves the question, why does it throw the above error when using foreign key associations?
As Ladislav mentioned, you are defining multiple cascade paths for BlogMeta entity. You'd have to disable cascade for one of your relationships.
You can add the following method to your context class, to diable cascade for your User-BlogMeta relationship:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BlogMeta>().HasRequired(bm => bm.User).WithMany().WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
You can indicate the other end of relationship (WithMany(u => u.BlogMetas)) if you have defined a colletion of BlogMeta in your User class.