Many-to-many relationship on the same table using a junction table and a primary key in EF - entity-framework

I have the following tables:
Sub_Option: Sub_Option_ID as PK, Name
Sub_Option_To_Sub_Option: Sub_Option_To_Sub_Option_ID as PK, Sub_Option_ID_Primary, Sub_Option_ID_Secondary
I would like to be able to access all the secondary sub options associated with the primary sub option via EF and vice-versa. Directly using .Map won't work as the junction table Sub_Option_To_Sub_Option has a primary key.
public class Sub_Option
{
public int Sub_Option_ID { get; set; }
public string Name { get; set; }
}
corresponding to Table
CREATE TABLE Sub_Option(
Sub_Option_ID int,
Name varchar(255)
);
and Table
CREATE TABLE Sub_Option_To_Sub_Option(
Sub_Option_To_Sub_Option int PK,
Sub_Option_ID_Primary int,
Sub_Option_ID_Secondary int
);

This should work i think:
public class OptionToOption
{
[Key]
public int ID { get; set; }
[ForeignKey("PrimaryOption")]
public int PrimaryID { get; set; }
[ForeignKey("SecondaryOption")]
public int SecondaryID { get; set; }
public virtual Option PrimaryOption { get; set; }
public virtual Option SecondaryOption { get; set; }
}
public class Option
{
public Option()
{
OptionToOption = new HashSet<OptionToOption>();
}
[Key]
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<OptionToOption> OptionToOption { get; set; }
}
And in fluent api map like this (don't even think it's necessary to do this though):
modelBuilder.Entity<Option>()
.HasMany(e => e.OptionToOption)
.WithRequired(e => e.PrimaryOption)
.HasForeignKey(e => e.PrimaryID);
modelBuilder.Entity<Option>()
.HasMany(e => e.OptionToOption)
.WithRequired(e => e.SecondaryOption)
.HasForeignKey(e => e.SecondaryID);

Related

Foreign keys to same Entity with one required and another optional

I'm trying to create two entities as below and add referential constraints to them using Fluent API.
The design it to enforce required Primary Contact with optional Secondary Contact with added requirement that Primary and Secondary may be referring to two different contacts in the ContactInfo entity.
public class Employee
{
public int EmployeeId { get; set; }
public int PrimaryContactId { get; set; }
public int SecondaryContactId { get; set; }
public virtual ContactInfo PrimaryContact { get; set; }
public virtual ContactInfo SecondaryContact { get; set; }
}
public class ContactInfo
{
public int ContactId { get; set; }
public string PhoneNumer { get; set; }
public string Email { get; set; }
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration ()
{
var employeeEntity = this;
employeeEntity.HasKey(e => e.EmployeeId).ToTable("Employees");
employeeEntity.Property(e => e.EmployeeId).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
employeeEntity.HasRequired(e => e.PrimaryContact).WithMany().HasForeignKey(e => e.PrimaryContactId).WillCascadeOnDelete(true);
employeeEntity.HasRequired(e => e.SecondaryContact ).WithMany().HasForeignKey(e => e.SecondaryContact Id).WillCascadeOnDelete(false);
}
}
It seems to create with required constraints as expected but when I try to add an employee with SecondaryContact set to null, the row created for this new Employee has SecondaryContactId set to same as PrimaryContactId which is not my intention.
I'm not able to understand whether the design is correct in the first place, or the configuration needs to be tweaked to get the right results.
Looks like you set both contacts to be required for your employee object.
If you want to have SecondaryContract optional change:
employeeEntity
.HasRequired(e => e.SecondaryContact)
.WithMany()
.HasForeignKey(e => e.SecondaryContactId)
.WillCascadeOnDelete(false);
to
employeeEntity
.HasOptional(e => e.SecondaryContact)
.WithMany()
.HasForeignKey(e => e.SecondaryContactId)
.WillCascadeOnDelete(false);
Addtionally change:
public int SecondaryContactId { get; set; }
to
public int? SecondaryContactId { get; set; }
since your SecondaryContactId is optional.
You can add foreign Key annotations and also set the keys to be nullable if you expect either to be empty.
public class Employee
{
public int EmployeeId { get; set; }
public int? PrimaryContactId { get; set; }
public int? SecondaryContactId { get; set; }
[ForeignKey("PrimaryContactId")]
public virtual ContactInfo PrimaryContact { get; set; }
[ForeignKey("SecondaryContactId")]
public virtual ContactInfo SecondaryContact { get; set; }
}
Make the secondary contact id to be a nullable type.
public int? SecondaryContactId { get; set; }

"Introducing FOREIGN KEY constraint"

Using: VS 2013, Entity Framework Code First, ASP.NET Web Project MVC
I have 2 models, in one need 2 FK for the same table:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class B
{
public int Id { get; set; }
public int Id1 { get; set; }
[ForeignKey("Id1")]
public virtual A A1 { get; set; }
public int Id2 { get; set; }
[ForeignKey("Id2")]
public virtual A A2 { get; set; }
}
After enable-migration and Add-Migration Test, when I run Update-Database, I get this message:
Introducing FOREIGN KEY constraint 'FK_dbo.B_dbo.A_Id2' on table 'B' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
use this code
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class B
{
public int Id { get; set; }
public int Id1 { get; set; }
[ForeignKey("Id1")]
public virtual A A1 { get; set; }
public int Id2 { get; set; }
[ForeignKey("Id2")]
public virtual A A2 { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>()
.HasRequired(e => e.A1)
.WithMany()
.HasForeignKey(c => c.Id1)
.WillCascadeOnDelete(false)
.HasRequired(e => e.A2)
.WithMany()
.HasForeignKey(c => c.Id2)
.WillCascadeOnDelete(false)
;
}
alse you could use inverseProperty attribute.

Multiple foreign keys to same primary key table

I have a table with multiple fields that are foreign keys to a primary key in another table. For example :
Fixture Id (PK)
HomeTeamId (FK to Team.TeamId)
AwayTeamId (FK to Team.TeamId)
HomeTeamCoachId (FK to Coach.CoachId)
AwayTeamCoachId (FK to Coach.CoachId)
Would it be better to separate this data into 2 tables HomeTeam and AwayTeam with a foreign key to FixtureId? This is currently what was generated by Entity Framework :
FixtureId PK
HomeTeamId int
AwayTeamId int
HomeTeamCoachId int
AwayTeamCoachId int
AwayTeam_TeamId FK
HomeTeam_TeamId FK
AwayTeamCoach_CoachId FK
HomeTeamCoach_CoachId FK
This was generated through this class :
public partial class Fixture
{
public int FixtureId { get; set; }
//foreign key
public int AwayTeamId { get; set; }
//navigation properties
public virtual Team AwayTeam { get; set; }
//foreign key
public int HomeTeamId { get; set; }
//navigation properties
public virtual Team HomeTeam { get; set; }
//foreign key
public int AwayCoachId { get; set; }
//navigation properties
public virtual Coach AwayCoach { get; set; }
//foreign key
public int HomeCoachId { get; set; }
//navigation properties
public virtual Coach HomeCoach { get; set; }
}
Can anybody tell me if this is the correct way to do this?
EDIT : In reply to Slauma
So my classes would basically look like this? Or does the configuration in OnModelCreating mean I don't need some of the foreign key related code in my Fixture class?
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Entity Type Configuration
modelBuilder.Configurations.Add(new TeamConfiguration());
modelBuilder.Configurations.Add(new CoachConfiguration());
modelBuilder.Configurations.Add(new FixtureConfiguration());
modelBuilder.Entity<Fixture>()
.HasRequired(f => f.AwayTeam)
.WithMany()
.HasForeignKey(f => f.AwayTeamId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Fixture>()
.HasRequired(f => f.HomeTeam)
.WithMany()
.HasForeignKey(f => f.HomeTeamId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Fixture>()
.HasRequired(f => f.AwayCoach)
.WithMany()
.HasForeignKey(f => f.AwayCoachId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Fixture>()
.HasRequired(f => f.HomeCoach)
.WithMany()
.HasForeignKey(f => f.HomeCoachId)
.WillCascadeOnDelete(false);
}
public partial class Fixture
{
public int FixtureId { get; set; }
public string Season { get; set; }
public byte Week { get; set; }
//foreign key
public int AwayTeamId { get; set; }
//navigation properties
public virtual Team AwayTeam { get; set; }
//foreign key
public int HomeTeamId { get; set; }
//navigation properties
public virtual Team HomeTeam { get; set; }
//foreign key
public int AwayCoachId { get; set; }
//navigation properties
public virtual Coach AwayCoach { get; set; }
//foreign key
public int HomeCoachId { get; set; }
//navigation properties
public virtual Coach HomeCoach { get; set; }
public byte AwayTeamScore { get; set; }
public byte HomeTeamScore { get; set; }
}
Apparently EF doesn't detect your int properties like AwayTeamId as the foreign key for the navigation properties like AwayTeam because the primary key property in Team is not Id but TeamId. It would probably detect the FKs if they are named like AwayTeamTeamId or if the primary key property in Team has the name Id.
If you don't want to change those property names according to EF convention you can define the FKs with data annotations:
[ForeignKey("AwayTeam")]
public int AwayTeamId { get; set; }
public virtual Team AwayTeam { get; set; }
// the same for the other three FKs
Or Fluent API:
modelBuilder.Entity<Fixture>()
.HasRequired(f => f.AwayTeam)
.WithMany()
.HasForeignKey(f => f.AwayTeamId)
.WillCascadeOnDelete(false);
// the same for the other three FKs
I have disabled cascading delete because it will be enabled by default for a required relationship. But because you have two required relationships to the Team table (and the Coach table as well) it would result in two cascading delete paths from Fixture to Team and Coach. Multiple cascading delete paths are forbidden in SQL Server, so you must disable cascading delete for at least one of the two relationships between Fixture and Team (and between Fixture and Coach).
I tried out this way & working
Primary key Table
public class TravelCity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CityId { get; set; }
public string CityName { get; set; }
public string CityDesc { get; set; }
public string Status { get; set; }
}
Table Having Foreign Key
public class TravelDetails
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int64 TravelId { get; set; }
public Int32 FromLocation { get; set; }
[ForeignKey("FromLocation"),InverseProperty("CityId")]
public virtual TravelCity TravelCityFrom { get; set; }
public Int32 ToLocation { get; set; }
[ForeignKey("ToLocation"), InverseProperty("CityId")]
public virtual TravelCity TravelCityTo { get; set; }
public Int32 CurrentCity { get; set; }
[ForeignKey("ToLocation"), InverseProperty("CityId")]
public virtual TravelCity TravelCityCurrent{ get; set; }
}
Try out this way it will surly work..
Cheers:)

Entity Framework's DbModel: How to map a one to many relationship using a connection table?

I'm trying to map via DbModel this relationship present on the database.
CREATE TABLE core.Institutes
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(128) NOT NULL,
OldID INT NULL
)
GO
CREATE TABLE core.InstitutePlaces
(
FKInstituteID INT NOT NULL PRIMARY KEY REFERENCES core.Institutes(ID),
FKPlaceID INT NOT NULL REFERENCES core.Places(ID)
)
GO
CREATE TABLE core.Places
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(128) NOT NULL,
FKParentID INT NULL REFERENCES core.Places(ID),
OldID INT NULL
)
GO
on this model
public class Place
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Place Parent { get; set; }
}
public class Institute
{
public int Id { get; set; }
public string Name { get; set; }
public Place Place { get; set; }
}
we're using something like this to do the mapping
modelBuilder.Entity<Institutes.Institute>().HasOptional(i => i.Place);
but it doesn't work :(
This scenario is perfectly managed by the EDML file, so the problem is only about the mapping.
Something like this will give you (almost) the desired schema with one caveat: Code First does not create a 1:1 relationship in entity splitting scenarios which your desired schema (creating a 1:* association using a join table) is a special case of it.
public class Place
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Place Parent { get; set; }
}
public class Institute
{
[DatabaseGenerated(DatabaseGenerationOption.None)]
public int Id { get; set; }
public string Name { get; set; }
public int? PlaceId { get; set; }
public Place Place { get; set; }
}
public class Context : DbContext
{
public DbSet<Place> Places { get; set; }
public DbSet<Institute> Institutes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Institute>().Map(mc =>
{
mc.Properties(p => new { p.Id, p.Name });
mc.ToTable("Institutes");
})
.Map(mc =>
{
mc.Properties(p => new { p.Id, p.PlaceId });
mc.ToTable("InstitutePlaces");
});
modelBuilder.Entity<Place>()
.HasOptional(p => p.Parent)
.WithMany()
.HasForeignKey(p => p.ParentId);
}
}
I had to switch off identity generation due to a bug that I explained here.

EF4 Mapping one-to-many on existing database without navigation

I'm using ModelBuilder to map an existing database to POCOs. I have courses, students, and meetings. Here are the tables
CREATE TABLE Courses (
CourseID int,
Name string)
CREATE TABLE Students(
StudentID int,
Name string)
CREATE TABLE Courses_Students (
CourseID int,
StudentID int)
CREATE TABLE Meetings (
MeetingID int,
CourseID int,
MeetDate datetime)
And the POCOs
public class Course {
public int CourseID { get; set; }
public string Name { get; set; }
public virtual ICollection<CourseMeeting> Meetings { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
public class Student {
public int StudentID { get; set; }
public string Name { get; set; }
}
public class Meeting {
public int MeetingID { get; set; }
public int CourseID { get; set; }
public DateTime MeetDate { get; set; }
}
The table mapping works great:
modelBuilder.Entity<Course>().MapSingleType().ToTable("Courses");
modelBuilder.Entity<Student>().MapSingleType().ToTable("Students");
modelBuilder.Entity<Meeting>().MapSingleType().ToTable("Meetings");
And the many-to-many mapping with a join table and without a navigation property works (i.e. there is no Students.Courses property specified on WithMany())
modelBuilder.Entity<Course>()
.HasMany(c => c.Students)
.WithMany()
.Map(StoreTableName.FromString("Courses_Students"),
(c, s) => new { CourseID = c.CourseID, StudentID = s.StudentID});
But I'm having trouble mapping the other relationship that doesn't have a join table. This obviously isn't right:
modelBuilder.Entity<Course>().HasMany(c => c.Meetings).WithMany();
Because it wants a join table: Invalid object name 'dbo.Course_Meetings'. I can add a Course property to the Meeting object and then use
modelBuilder.Entity<Course>()
.HasMany(c => c.Meetings)
.WithOptional(m => m.Course)
.HasConstraint((c, m) => c.CoursID == me.CourseID);
But I'd like to do this without the navigation property. Is this possible with EF4 and an existing database?
It's assuming it needs the join table (and thus looking for it) because you haven't mapped the property in the original declaration.
Try manually mapping the properties on the actual table like this..
public class Meeting {
public int MeetingID { get; set; }
public int CourseID { get; set; }
public DateTime MeetDate { get; set; }
public Course { get; set; }
}
and then configure it as follows:
modelBuilder.Entity<Meeting>(m => new {
MeetingId = m.Meeting,
MeetDate = m.MeetDate,
CourseId = m.Course.Id
})
.HasRequired(m => m.Course)
.WithMany()