I'm having some issues creating foreign key relationships between my tables using the C# Entity Framework. I have these tables:
CREATE TABLE [dbo].[Course] (
[Id] INT IDENTITY (1, 1) NOT NULL,,
CONSTRAINT [PK_Course] PRIMARY KEY CLUSTERED ([ID] ASC)
);
CREATE TABLE [dbo].[PreqEdge] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[ParentID] INT NOT NULL,
[ChildID] INT NOT NULL,
CONSTRAINT [PK_PreqEdge] PRIMARY KEY ([Id]),
CONSTRAINT [FK_Dependant] FOREIGN KEY ([ParentID]) REFERENCES [dbo].[Course] ([Id]),
CONSTRAINT [FK_Depends] FOREIGN KEY ([ChildID]) REFERENCES [dbo].[Course] ([Id])
);
The corresponding models look like so:
public partial class Course
{
public int Id { get; set; }
public virtual ICollection<PreqEdge> Parents { get; set; }
public virtual ICollection<PreqEdge> Children { get; set; }
public Course()
{
Parents = new HashSet<PreqEdge>();
Children = new HashSet<PreqEdge>();
}
}
public partial class PreqEdge
{
public int Id { get; set; }
public int ParentID { get; set; }
public int ChildID { get; set; }
public virtual Course Parent { get; set; }
public virtual Course Child { get; set; }
}
Whenever I use my database context to access them I get this error:
Introducing FOREIGN KEY constraint 'FK_dbo.PreqEdges_dbo.Courses_ChildID' on table 'PreqEdges' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
The purpose of these tables is to connect two Courses together like the edge of a graph in order to create a chain of prerequisites for that course that can be traversed either forwards or backwards based on whether you filter by ChildID or ParentID.
I understand that Entity Framework by default uses cascade deletes on references and that I might be able to solve this by overriding the OnModelCreating method in my DbContext, but I'm not sure how to specify using a DbModelBuilder this kind of relationship where it could be foreign keyed with a table by either one of two keys in that table.
Is this possible to do in the Entity Framework without manually writing SQL calls?
EDIT: Changed DependantID and DependsID to ParentID and ChildID for clarity
You will have to check for circular references with your model setup like that. In this model every course can connect with every course. It might be easier to reduce the flexibility of your model somehow, if possible.
Here is Object model as an example of various circular reference situations you can get:
Inspiration how to reduce the chance of circular reference:
You can divide Courses into groups by CourseType and e.g. SpecializedCourse cannot be a Parent of any Course but can be a Child of a GeneralCourse.
I found a link with the correct solution. For this example you would do something like this:
public class Course
{
public int Id { get; set; }
public string CouresNumber { get; set; }
public virtual ICollection<PreqEdge> Parents { get; set; }
public virtual ICollection<PreqEdge> Children { get; set; }
}
public class PreqEdge
{
public int Id { get; set; }
public int ParentId { get; set; }
public int ChildId { get; set; }
public virtual Course Parent { get; set; }
public virtual Course Child { get; set; }
}
public class CourseContext : DbContext
{
public DbSet<Course> Courses { get; set; }
public DbSet<PreqEdge> PreqEdges { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<PreqEdge>()
.HasRequired(e => e.Parent)
.WithMany(c => c.Parents)
.HasForeignKey(e => e.ParentId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<PreqEdge>()
.HasRequired(e => e.Child)
.WithMany(c => c.Children)
.HasForeignKey(e => e.ChildId)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
}
Another mistake I made was to try to mix code first and database first.
Try two ways.
1) set lazy loading off in constructor of DbContext class
this.Configuration.LazyLoadingEnabled = false;
It will not allow to load referencing objects
or
2) use [notmapped] on PreqEdge referencing properties as
[notmapped]
public virtual Course Parent { get; set; }
[notmapped]
public virtual Course Child { get; set; }
This is just to break the cycle.
Related
I have two models:
Child
public class Child {
public int Id { get; set; }
public string Name { get; set; }
}
Parent
public class Parent {
public int Id { get; set; }
public int? FirstChildId { get; set; }
public Child FirstChild { get; set; }
public int? SecondChildId { get; set; }
public Child SecondChild { get; set; }
}
There is a strict relationship that one child has only one parent and one parent has zero-or-one FirstChild and zero-or-one SecondChild.
As far as I know, if we want to make a relationship to the same table (model) twice then we need to have a one-to-many relationship instead of one-to-one.
Therefore, I've modified the Child class by adding Many relationships.
public class Child {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Parent> ParentsFirstChild { get; set; }
public ICollection<Parent> ParentsSecondChild { get; set; }
}
So far I have the next Fluent API modelBuilder that works
modelBuilder.Entity<Child>()
.HasMany(f => f.ParentsFirstChild)
.WithOptional(p => p.FirstChild)
.HasForeignKey(p => p.FirstChildId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Child>()
.HasMany(f => f.ParentsSecondChild)
.WithOptional(p => p.SecondChild)
.HasForeignKey(p => p.SecondChildId)
.WillCascadeOnDelete(false);
The problem is that if we'd put
.WillCascadeOnDelete(true)
It produces the error
Introducing FOREIGN KEY constraint
FK_dbo.Parent_dbo.Child_ParentsFirstChildId on table Parent may cause
cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON
UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I see that EF thinks that a Child could be related to other Parents. That's why it could be cyclic, right?
Question
How to create a mapping through either DataAnnotations or Fluent API, so that in case of presence (both optional) one or both children (same class/model) during deleting of a parent, children will be removed too?
Good afternoon, I ran into the problem of creating relationships when learning asp.core, did the registration and authorization on the site through Identity, and made the classes:
public class User : IdentityUser
{
[Key]
public string Id { get; set; }
public IEnumerable<User> Pupils { get; set; }
public int SubLevel { get; set; }
public int Score { get; set; }
public virtual ICollection<ProxyUserGroup> ProxyUserGroups { get;set; } = new List<ProxyUserGroup>();
}
[Table("ProxyUserGroups")]
public class ProxyUserGroup
{
[Key]
[Column(Order = 0)]
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
[Key]
[Column(Order = 1)]
public int UserGroupId { get; set; }
[ForeignKey("UserGroupId")]
public virtual UserGroup UserGroup { get; set; }
}
[Table("UserGroups")]
public class UserGroup
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProxyUserGroup> ProxyUserGroups { get; set; } = new List<ProxyUserGroup>();
}
Then I did the migration to the database (before that I did the hands of the table but could not overcome the error and decided to do the migration)
Table ProxyUserGroup
create table ProxyUserGroups (
UserId nvarchar(450) not null
constraint FK_ProxyUserGroups_User_UserId
references AspNetUsers on delete cascade,
UserGroupId int not null
constraint FK_ProxyUserGroups_UserGroups_UserGroupId
references UserGroups on delete cascade,
constraint PK_ProxyUserGroups
primary key (UserId, UserGroupId),
constraint AK_ProxyUserGroups_UserGroupId_UserId
unique (UserGroupId, UserId) )
Table UserGroups
create table UserGroups (
Id int identity
constraint PK_UserGroups
primary key,
Name nvarchar(max) )
And context
protected override void OnModelCreating(Microsoft.EntityFrameworkCore.ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProxyUserGroup>()
.HasKey(t => new { t.UserId, t.UserGroupId });
modelBuilder.Entity<ProxyUserGroup>()
.HasOne<User>(e => e.User)
.WithMany(t => t.ProxyUserGroups)
.HasForeignKey(e => e.UserId);
modelBuilder.Entity<ProxyUserGroup>()
.HasOne<UserGroup>(e => e.UserGroup)
.WithMany(t => t.ProxyUserGroups)
.HasForeignKey(e => e.UserGroupId);
}
After launching the site displays:
InvalidOperationException: Entity type 'ProxyUserGroup' has a composite key defined with data annotations. To set a composite primary key, use fluent API.
But if you go to the site in incognito mode, then everything is fine until you try to log in and then throws it away:
InvalidOperationException: Entity type 'ProxyUserGroup' has a composite key defined with data annotations. To set a composite primary key, use fluent API.
User user = await userManager.FindByEmailAsync (model.Email); // error is still here
From the first error I realized that in modelBuilder.Entity ().HasKey (t => new {t.UserId, t.UserGroupId}); the primary key is not generated (a composite of two external keys), but the second one still has a problem with IdentityUser (which was not previously). What exactly is wrong and what needs to be corrected? Thank you in advance.(Sorry for my English).
Entity type 'ProxyUserGroup' has a composite key defined with data annotations. To set a composite primary key, use fluent API.
This is becuase EF Core does not support creating a composite key using the Key attribute. You have to use the only Fluent API HasKey() function in EF Core. So remove Key attributes from ProxyUserGroup class as follows:
[Table("ProxyUserGroups")]
public class ProxyUserGroup
{
public string UserId { get; set; }
public virtual User User { get; set; }
public int UserGroupId { get; set; }
public virtual UserGroup UserGroup { get; set; }
}
I use Entity Framework 6.1.3 Code First to map to an existing database. I've googled many questions about this error, however all errors connected with inheritance, but I have not used inheritance. I've tried to delete my database and recreate all tables, but error is not gone.
I have really simple Database just with two tables - Schedules and Seances:
CREATE TABLE [dbo].[Schedules] (
[IdSchedule] INT IDENTITY (1, 1) NOT NULL,
[Cinema] VARCHAR (50) NULL,
[Movie] VARCHAR (50) NULL,
[DateSchedule] DATETIME NULL,
[IdSeance] INT NOT NULL,
PRIMARY KEY CLUSTERED ([IdSchedule] ASC)
);
and:
CREATE TABLE [dbo].[Seances] (
[IdSeance] INT IDENTITY (1, 1) NOT NULL,
[DateSeance] DATETIME NOT NULL,
PRIMARY KEY CLUSTERED ([IdSeance] ASC)
);
and Model classes:
public class Schedule
{
[Key]
public int IdSchedule { get; set; }
public string Cinema { get; set; }
public string Movie { get; set; }
public DateTime DateSchedule { get; set; }
public List<Seance> Seances { get; set; }
}
public class Seance
{
[Key]
public int IdSeance { get; set; }
public DateTime DateSeance { get; set; }
}
and EFDBContext class:
public class EFDbContext : DbContext
{
public EFDbContext()
{
Database.SetInitializer<EFDbContext>(null);
}
public DbSet<Schedule> Schedules { get; set; }
public DbSet<Seance> Seances { get; set; }
}
However, when I try to execute these rows of code:
var e1= EFDbContext.Schedules.ToList();//it works okay
var e2 = EFDbContext.Seances.ToList();// An exception occurs
In the second row an exception occurs:
"Invalid column name 'Schedule_IdSchedule'."} System.Exception {System.Data.SqlClient.SqlException}
The most interesting thing that I do not have column name Schedule_IdSchedule. Where does Entity Framework take this column?
How can I resolve it?
When you see unwanted columns in the pattern of "table_fkcolumn" that means EF stuck that in there itself because it couldn't interpret your model. If you want one schedule to many seances, try this:
public class Schedule
{
public Schedule()
{
Seances = new List<Seance>();
}
[Key]
public int IdSchedule { get; set; }
public string Cinema { get; set; }
public string Movie { get; set; }
public DateTime DateSchedule { get; set; }
public virtual ICollection<Seance> Seances { get; set; }
}
public class Seance
{
[Key]
public int IdSeance { get; set; }
public DateTime DateSeance { get; set; }
public int IdSchedule { get; set; }
[ForeignKey("IdSchedule")]
public virtual Schedule Schedule{ get; set; }
}
Alternatively, you can use a fluent configuration in which case you can remove the annotations and override OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Schedule>()
.HasMany(u => u.Seances)
.WithRequired()
.HasForeignKey(h => h.IdSchedule);
}
I think you renamed your table from schedule to schedules.
try to refresh your dbcontext.
of it still doesn't work, delete table schedule from your dbcontext schema and readd it. it will work.
Your code first model does not mirror your DB schema. I can see that Schedule class contains a List of Seances, so i assume that the relationship between those two classes is one-to-many:
Schedule 1-* Seance
Instead in your DB schema the Schedules table contains a field that is not mapped in your model (IdSeance), which is also required (NOT NULL constraint). Also this appear to be a foreign key for the table Seances, but no FOREIGN KEY constraint is declared. This DB schema is more similar to this kind of relationship (many-to-one):
Schedule *-1 Seance
Which is the opposite.
Before continue please figure out the correct relationship between your entities and remember to map exactly every field you need in your class that exists in the DB. Also remember to map those fields as foreign key if they are not following the EF convention for navigation properties. You can do it using attributes or the fluent API.
I am receiving the following error when attempting to create the database:
One or more validation errors were detected during model generation:
Interaction_CauseElement_Source: : Multiplicity is not valid in Role
'Interaction_CauseElement_Source' in relationship
'Interaction_CauseElement'. Because the Dependent Role properties are
not the key properties, the upper bound of the multiplicity of the
Dependent Role must be '*'.
Interaction_EffectElement_Source: : Multiplicity is not valid in Role
'Interaction_EffectElement_Source' in relationship
'Interaction_EffectElement'. Because the Dependent Role properties are
not the key properties, the upper bound of the multiplicity of the
Dependent Role must be '*'.
I've seen this error in other Stack Overflow posts, but in the examples I found, the OP was trying for a 1-to-1 relationship in both directions between the tables. That is not what I am looking for.
Here is my model:
public class Element
{
[Key]
public int ID { get; set; }
[Required, MaxLength(64)]
public string Name { get; set; }
[MaxLength(200)]
public string Description { get; set; }
}
public class Interaction
{
[Key]
public int ID { get; set; }
[Index, Required]
public int CauseID { get; set; }
[Index, Required]
public int EffectID { get; set; }
[MaxLength(64)]
public string Location { get; set; }
[ForeignKey("CauseID")]
public virtual Element CauseElement { get; set; }
[ForeignKey("EffectID")]
public virtual Element EffectElement { get; set; }
}
Items in the Elements table are unique. A pair of elements can interact with each other in any number of locations. The CauseID/EffectID pair is not going to be unique.
The only other place I am changing the model is in the OnModelCreating method. I had received this error:
Introducing FOREIGN KEY constraint
'FK_dbo.Interactions_dbo.Elements_Cause' on table
'Interactions' 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.
And had to create a cascade policy for the model. This code fixed that error:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Prevent cyclic cascade on elements table
modelBuilder.Entity<Interaction>()
.HasRequired(i => i.CauseElement)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Interaction>()
.HasRequired(i => i.EffectElement)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
But then I received the cryptic "Multiplicity" error. It seems like it wants me to make public virtual Element CauseElement into a collection like public virtual ICollection<Element> CauseElement, but that would not properly model the relationship.
I found the solution. This article on EntityFrameworkTutoral.net helped out. Because I need TWO references from the Interaction class to the Element class, this relationship is too complex to model in EF with only the attributes.
I had to update the model and then use the fluent API to tell EF how to treat the relationships. I updated my model to the following:
public class Element
{
public Element()
{
CauseElements = new List<Interaction>();
EffectElements = new List<Interaction>();
}
[Key]
public int ID { get; set; }
[Required, MaxLength(64)]
public string Name { get; set; }
#region Navigation
public virtual ICollection<Interaction> CauseElements { get; set; }
public virtual ICollection<Interaction> EffectElements { get; set; }
#endregion
}
public class Interaction
{
[Key]
public int ID { get; set; }
[Index]
public int CauseID { get; set; }
[Index]
public int EffectID { get; set; }
[MaxLength(64)]
public string Location { get; set; }
#region Navigation
[ForeignKey("CauseID")]
public virtual Element CauseElement { get; set; }
[ForeignKey("EffectID")]
public virtual Element EffectElement { get; set; }
#endregion
}
And in my DbContext class I used the fluent API to create the link between the Interaction.CauseElement and Element.CauseElements and which property was the foreign key for the Interaction table (and the same with the Effect relationship):
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Prevent cyclic cascade on elements table
modelBuilder.Entity<Interaction>()
.HasRequired(i => i.CauseElement)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Interaction>()
.HasRequired(i => i.EffectElement)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
//Create the links between the element, the key, and the collection
modelBuilder.Entity<Interaction>()
.HasRequired<Element>(i => i.CauseElement)
.WithMany(e => e.CauseElements)
.HasForeignKey(i => i.CauseID);
modelBuilder.Entity<Interaction>()
.HasRequired<Element>(i => i.EffectElement)
.WithMany(e => e.EffectElements)
.HasForeignKey(i => i.EffectID);
base.OnModelCreating(modelBuilder);
}
It seems that Entity Framework tries to automatically infer the relationships between the tables when you have a simple 1-to-many relationship. If I removed EffectElement from the Interaction class (and EffectElements from Element), EF was able to create the relationship easily. But when I added it back, I received the error again.
Since that Element type showed up twice in the Interaction class, it didn't know how to create the relationship. I had to explicitly define it in the OnModelCreating method.
You reversed the responsibilities of the "ForeignKey" attribute. It goes on the ID field, specifying the property for which it serves as the foreign key. You want something as below:
// To-One on Element
[ForeignKey("Element")]
public int ElementId { get; set; }
public virtual Element Element { get; set; }
Also, this is actually a one-to-one relationship. A one-to-many relationship in this case would be:
// To-Many on Element
public virtual ICollection<Element> Elements{ get; set; }
We want to use one to one optional relationship using Entity Framework Code First. We have two entities.
public class PIIUser
{
public int Id { get; set; }
public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
PIIUser may have a LoyaltyUserDetail but LoyaltyUserDetail must have a PIIUser.
We tried these fluent approach techniques.
modelBuilder.Entity<PIIUser>()
.HasOptional(t => t.LoyaltyUserDetail)
.WithOptionalPrincipal(t => t.PIIUser)
.WillCascadeOnDelete(true);
This approach didn't create LoyaltyUserDetailId foreign key in PIIUsers table.
After that we tried the following code.
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(t => t.PIIUser)
.WithRequiredDependent(t => t.LoyaltyUserDetail);
But this time EF didn't create any foreign keys in these 2 tables.
Do you have any ideas for this issue?
How can we create one to one optional relationship using entity framework fluent api?
EF Code First supports 1:1 and 1:0..1 relationships. The latter is what you are looking for ("one to zero-or-one").
Your attempts at fluent are saying required on both ends in one case and optional on both ends in the other.
What you need is optional on one end and required on the other.
Here's an example from the Programming E.F. Code First book
modelBuilder.Entity<PersonPhoto>()
.HasRequired(p => p.PhotoOf)
.WithOptional(p => p.Photo);
The PersonPhoto entity has a navigation property called PhotoOf that points to a Person type. The Person type has a navigation property called Photo that points to the PersonPhoto type.
In the two related classes, you use each type's primary key, not foreign keys. i.e., you won't use the LoyaltyUserDetailId or PIIUserId properties. Instead, the relationship depends on the Id fields of both types.
If you are using the fluent API as above, you do not need to specify LoyaltyUser.Id as a foreign key, EF will figure it out.
So without having your code to test myself (I hate doing this from my head)... I would translate this into your code as
public class PIIUser
{
public int Id { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public PIIUser PIIUser { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(lu => lu.PIIUser )
.WithOptional(pi => pi.LoyaltyUserDetail );
}
That's saying LoyaltyUserDetails PIIUser property is required and PIIUser's LoyaltyUserDetail property is optional.
You could start from the other end:
modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);
which now says PIIUser's LoyaltyUserDetail property is optional and LoyaltyUser's PIIUser property is required.
You always have to use the pattern HAS/WITH.
HTH and FWIW, one to one (or one to zero/one) relationships are one of the most confusing relationships to configure in code first so you are not alone! :)
Just do like if you have one-to-many relationship between LoyaltyUserDetail and PIIUser so you mapping should be
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(m => m.PIIUser )
.WithMany()
.HasForeignKey(c => c.LoyaltyUserDetailId);
EF should create all foreign key you need and just don't care about WithMany !
There are several things wrong with your code.
A 1:1 relationship is either: PK<-PK, where one PK side is also an FK, or PK<-FK+UC, where the FK side is a non-PK and has a UC. Your code shows you have FK<-FK, as you define both sides to have an FK but that's wrong. I recon PIIUser is the PK side and LoyaltyUserDetail is the FK side. This means PIIUser doesn't have an FK field, but LoyaltyUserDetail does.
If the 1:1 relationship is optional, the FK side has to have at least 1 nullable field.
p.s.w.g. above did answer your question but made a mistake that s/he also defined an FK in PIIUser, which is of course wrong as I described above. So define the nullable FK field in LoyaltyUserDetail, define the attribute in LoyaltyUserDetail to mark it the FK field, but don't specify an FK field in PIIUser.
You get the exception you describe above below p.s.w.g.'s post, because no side is the PK side (principle end).
EF isn't very good at 1:1's as it's not able to handle unique constraints. I'm no expert on Code first, so I don't know whether it is able to create a UC or not.
(edit) btw: A 1:1 B (FK) means there's just 1 FK constraint created, on B's target pointing to A's PK, not 2.
public class User
{
public int Id { get; set; }
public int? LoyaltyUserId { get; set; }
public virtual LoyaltyUser LoyaltyUser { get; set; }
}
public class LoyaltyUser
{
public int Id { get; set; }
public virtual User MainUser { get; set; }
}
modelBuilder.Entity<User>()
.HasOptional(x => x.LoyaltyUser)
.WithOptionalDependent(c => c.MainUser)
.WillCascadeOnDelete(false);
this will solve the problem on REFERENCE and FOREIGN KEYS
when UPDATING or DELETING a record
Try adding the ForeignKey attribute to the LoyaltyUserDetail property:
public class PIIUser
{
...
public int? LoyaltyUserDetailId { get; set; }
[ForeignKey("LoyaltyUserDetailId")]
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
...
}
And the PIIUser property:
public class LoyaltyUserDetail
{
...
public int PIIUserId { get; set; }
[ForeignKey("PIIUserId")]
public PIIUser PIIUser { get; set; }
...
}
This is of no use to the original poster, but for anyone still on EF6 who needs the foreign key to be different from the primary key, here's how to do it:
public class PIIUser
{
public int Id { get; set; }
//public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
modelBuilder.Entity<PIIUser>()
.HasRequired(t => t.LoyaltyUserDetail)
.WithOptional(t => t.PIIUser)
.Map(m => m.MapKey("LoyaltyUserDetailId"));
Note that you can't use the LoyaltyUserDetailId field because, as far as I can tell, it can only be specified using the fluent API. (I've tried three ways of doing it using the ForeignKey attribute and none of them worked).
The one thing that is confusing with above solutions is that the Primary Key is defined as "Id" in both tables and if you have primary key based on the table name it wouldn't work, I have modified the classes to illustrate the same, i.e. the optional table shouldn't define it's own primary key instead should use the same key name from main table.
public class PIIUser
{
// For illustration purpose I have named the PK as PIIUserId instead of Id
// public int Id { get; set; }
public int PIIUserId { get; set; }
public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
// Note: You cannot define a new Primary key separately as it would create one to many relationship
// public int LoyaltyUserDetailId { get; set; }
// Instead you would reuse the PIIUserId from the primary table, and you can mark this as Primary Key as well as foreign key to PIIUser table
public int PIIUserId { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
And then followed by
modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);
Would do the trick, the accepted solution fails to clearly explain this, and it threw me off for few hours to find the cause