I have models that have references to each other:
public class Dept
{
[Key]
public int DeptId { get; set; }
[ForeignKey("ManagerId")]
public Emp Manager { get; set; }
public int? ManagerId { get; set; }
public string DeptName { get; set; }
}
public class Emp
{
[Key]
public int EmpId { get; set; }
[Required]
[ForeignKey("DeptId")]
public Dept Dept { get; set; }
public int DeptId { get; set; }
public string Name { get; set; }
}
When I call Add-Migration, I get error:
The ForeignKeyAttribute on property 'Manager' on type 'App.Dept' is not valid.
The foreign key name 'ManagerId' was not found on the dependent type 'App.Emp'.
The Name value should be a comma separated list of foreign key property names.
What should I do to create migration with these tables?
UPD: Implicit optional Manager does not solve the problem:
modelBuilder.Entity<Emp>().HasRequired(_ => _.Dept).WithOptional(_ => _.Manager);
UPD2: Dept:Emp relation is 1:0..1
UPD3: Maybe another relation will be added to Dept model but it will be also 1:0..1:
[ForeignKey("ManagerId")]
public Emp CTO { get; set; }
public int? CTOId { get; set; }
It is not one to many relation: one department have zero or one manager, and zero or one CTO. At the moment I have only one relation, but I want to name the field ManagerId, not a EmpId.
UPD4:
Schema from the beginning of my question with two primary/foreign keys relation (Dept.DeptId/Emp.DeptId, Emp.EmpId/Dept.ManagerId) works in plain SQL. I know workarounds with additional table or without foreign keys, but I need an answer how to make work schema above or why it is not working in EF.
You have mainly three way to configure 1-1 relationships (the case of your error is the 3rd explained case).
Complex types
The first way is with only one table and using complex types. Choosing this configuration has performance impact (often, overall performances are better than other configurations but it depends on the record size and on how many times you have both records).
In your case you need only to mark one of the entities with ComplexType attribute
public class Dept
{
[Key]
public int DeptId { get; set; }
public Emp Manager { get; set; }
public string DeptName { get; set; }
}
[ComplexType]
public class Emp
{
public int EmpId { get; set; } // You can still have this property but it will not be a primary key
public string Name { get; set; }
}
With this model this is the created table
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [Manager_EmpId] int not null
, [Manager_Name] text null
, [DeptName] text null
);
Standard foreign key
The second way is to use a standard foreign key. The model can have navigation properties on both classes, has 2 tables with independent primary keys but only 1 table has the foreign key to the other table (you wrote about this configuration on your question). You obtain this configuration overriding OnModelCreating.
Using this way there are several configurations you can do using fluent API. The main option is where should EF insert the foreign key.
In every configuration you must have Map method (I explain what happens without the Map method in the third way)
The model is always this
public class Dept
{
[Key]
public int DeptId { get; set; }
public Emp Manager { get; set; }
public string DeptName { get; set; }
}
public class Emp
{
[Key]
public int EmpId { get; set; }
public Dept Department { get; set; }
public string Name { get; set; }
}
WithRequiredPrincipal (1-1)
From MSDN:
Configures the relationship to be required:required without a navigation property on the other side of the relationship. The entity type being > configured will be the principal in the relationship. The entity type that the relationship targets will be the dependent and contain a foreign > key to the principal.
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredPrincipal(_ => _.Department)
.Map(_ => _.MapKey("DepartmentId"));
This is the DDL generated
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_c0491d33] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
, [DepartmentId] int not null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_c0491d33] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_DepartmentId] ON [Emps] ([DepartmentId])
ExecuteNonQuery==========
ALTER TABLE [Emps] ADD CONSTRAINT [FK_Emps_Depts_DepartmentId] FOREIGN KEY ([DepartmentId]) REFERENCES [Depts] ([DeptId])
WithRequiredDependent (1-1)
From MSDN:
Configures the relationship to be required:required without a navigation property on the other side of the relationship.
[For me is not clear this explanation, anyway, for the real behaviour see below]
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredDependent(_ => _.Department)
.Map(_ => _.MapKey("EmpId"));
This is the DDL generated
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
, [EmpId] int not null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_bebceea2] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_bebceea2] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_EmpId] ON [Depts] ([EmpId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_EmpId] FOREIGN KEY ([EmpId]) REFERENCES [Emps] ([EmpId])
WithOptional (1-0..1)
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithOptional(_ => _.Department)
.Map(_ => _.MapKey("ManagerId"));
This is the DDL generated
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
, [ManagerId] int not null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_ee5245bb] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_ee5245bb] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_ManagerId] ON [Depts] ([ManagerId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_ManagerId] FOREIGN KEY ([ManagerId]) REFERENCES [Emps] ([EmpId])
You can use other methods to obtain similar configurations. I dont show here every example but we can mix these configurations
HasOptional/WithRequired
HasOptional/WithOptionalDependent
HasOptional/WithOptionalPrincipal
EF Default 0..1-1 1-0..1 1-1 configuration
This is how EF is interpreting your configuration. In this case EF generates 2 tables with dependent primary keys. On one table there is an independent primary key (in your case identity(1,1)) and on the other table you have a primary key that is also the foreign key. This is the default configuration. This is the only way to have foreign keys on both tables (not 2 constraints, no way to have 2 circular contraints, see below)
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredPrincipal(_ => _.Department);
This is the DDL generated
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null identity(1,1)
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_b91ed7c4] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_b91ed7c4] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_EmpId] ON [Emps] ([EmpId])
ExecuteNonQuery==========
ALTER TABLE [Emps] ADD CONSTRAINT [FK_Emps_Depts_EmpId] FOREIGN KEY ([EmpId]) REFERENCES [Depts] ([DeptId])
This should be 1-1 relationship but if we look better there is one missing constraint. The primary key of the Dept table should be a foreign key for the second table. Why EF did not insert that constraint? Because we will violate always a constraint so we could not insert records on tables (also inside a transaction a reference key constraint can be violated).
Changing the configuration to HasRequired/WithRequiredDependent we obtain the table with the independent primary key will be the Emps table
modelBuilder.Entity<Dept>()
.HasRequired(_ => _.Manager)
.WithRequiredDependent(_ => _.Department);
This is the DDL generated
ExecuteNonQuery==========
CREATE TABLE [Depts] (
[DeptId] int not null
, [DeptName] text null
);
ALTER TABLE [Depts] ADD CONSTRAINT [PK_Depts_58ab8622] PRIMARY KEY ([DeptId])
ExecuteNonQuery==========
CREATE TABLE [Emps] (
[EmpId] int not null identity(1,1)
, [Name] text null
);
ALTER TABLE [Emps] ADD CONSTRAINT [PK_Emps_58ab8622] PRIMARY KEY ([EmpId])
ExecuteNonQuery==========
CREATE INDEX [IX_DeptId] ON [Depts] ([DeptId])
ExecuteNonQuery==========
ALTER TABLE [Depts] ADD CONSTRAINT [FK_Depts_Emps_DeptId] FOREIGN KEY ([DeptId]) REFERENCES [Emps] ([EmpId])
You can use other methods to obtain similar configurations. I dont show here every example but we can mix these configurations
HasOptional/WithRequired
HasOptional/WithOptionalDependent
HasOptional/WithOptionalPrincipal
From your class code you have the following
For 1-1 and 1-0..1, same primary key should appear in both tables and in your design its not the case since both tables will have their own primary key
Now, based on the code you put, the configuration should be as follows
modelBuilder.Entity<Dept>()
.HasKey(t => t.DeptId)
.HasOptional(t => t.Manager)
.WithRequired(t => t.Dept);
but this will not mean a 1-1 or 1-0..1 relation.
if you want to convert your codes to become 1-0..1, then you code should be like this
Remove the EmpId from the class Emp
The configuration should look like
modelBuilder.Entity<Emp>()
.HasKey(t => t.DeptId)
.HasRequired(t => t.Dept);
modelBuilder.Entity<Dept>()
.HasKey(t => t.DeptId)
.HasOptional(t => t.Manager)
.WithRequired(t => t.Dept);
modelBuilder.Entity<Dept>()
.HasOptional(t => t.Manager)
.WithMany()
.HasForeignKey(t => t.ManagerId)
.WillCascadeOnDelete(false);
for more information about the relations, you might read this article Configure One-to-Zero-or-One Relationship:
Hope this will help you
Related
I have issue with computed primary key.
In table class I have this property. It is primary computed key.
[Key]
[StringLength(24)]
public string TagId { get; set; } = null!;
In context class I have
entity.Property(e => e.TagId).HasComputedColumnSql("(CONVERT([nvarchar](24),([TagCd]+'.')+case when len([Nbr])=(1) then '0'+CONVERT([nvarchar](4),[Nbr]) else CONVERT([nvarchar](4),[Nbr]) end))", true);
In OnModelCreating method
modelBuilder.Entity<Tag>(entity =>
{
entity.Property(e => e.TagId).ValueGeneratedOnAdd();
});
When I try to save new record entity framewrok still add primary key to insert query and then I get exception
SqlException: The column TagId cannot be modified because it is either a computed column or is the result of a UNION operator
Can someone help me ? What should I do ? It is database first approach.
I using Entity Framework Core, and I have a table:
public class BlogComment
{
public int Id { get; set; }
public BlogPost Post { get; set; }
[StringLength(100)]
public string AuthorName { get; set; }
[StringLength(254)]
public string AuthorEmail { get; set; }
public bool SendMailOnReply { get; set; }
[StringLength(2000)]
public string Content { get; set; }
public DateTime CreatedTime { get; set; }
public int? ReplyToId { get; set; }
public BlogComment ReplyTo { get; set; }
}
From this, EFC generates the following table:
CREATE TABLE [dbo].[BlogComment] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[AuthorEmail] NVARCHAR (254) NULL,
[AuthorName] NVARCHAR (100) NULL,
[Content] NVARCHAR (2000) NULL,
[CreatedTime] DATETIME2 (7) NOT NULL,
[PostId] INT NULL,
[ReplyToId] INT NULL,
[SendMailOnReply] BIT NOT NULL,
CONSTRAINT [PK_BlogComment] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_BlogComment_BlogPost_PostId] FOREIGN KEY ([PostId]) REFERENCES [dbo].[BlogPost] ([Id]),
CONSTRAINT [FK_BlogComment_BlogComment_ReplyToId] FOREIGN KEY ([ReplyToId]) REFERENCES [dbo].[BlogComment] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_BlogComment_PostId]
ON [dbo].[BlogComment]([PostId] ASC);
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_BlogComment_ReplyToId]
ON [dbo].[BlogComment]([ReplyToId] ASC) WHERE ([ReplyToId] IS NOT NULL);
Some comments are send as a reply to another, but not all. When the original comment is deleted, the reply becomes a normal comment. So, following this tutorial, the configuration looks is this:
modelBuilder.Entity<BlogComment>()
.HasOne(p => p.ReplyTo)
.WithOne()
.HasForeignKey<BlogComment>(c => c.ReplyToId)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
The delete method is pretty simple:
var comment = await context.BlogComment.Include(c => c.ReplyTo).SingleAsync(m => m.Id == id);
context.BlogComment.Remove(comment);
await context.SaveChangesAsync();
But I can't run it, I get an error:
System.Data.SqlClient.SqlException: The DELETE statement conflicted with the SAME TABLE REFERENCE constraint "FK_BlogComment_BlogComment_ReplyToId".
How can I fix this?
To wrap up the conversation in the comments:
First, the self reference is a 1:n association:
modelBuilder.Entity<BlogComment>()
.HasOne(p => p.ReplyTo)
.WithMany(c => c.Replies)
.HasForeignKey(c => c.ReplyToId)
.IsRequired(false)
.OnDelete(<we'll get to that>);
So, just for convenience, BlogComment now also has a property
public ICollection<BlogComment> Replies { get; set; }
However, I can't create the table using
.OnDelete(DeleteBehavior.SetNull);
It gives me
Introducing FOREIGN KEY constraint 'FK_BlogComments_BlogComments_ReplyToId' on table 'BlogComments' may cause cycles or multiple cascade paths.
This is a Sql Server restriction we just have to accept, no way to evade it. The only way to get the desired cascade behavior is
.OnDelete(DeleteBehavior.ClientSetNull);
Which is:
For entities being tracked by the DbContext, the values of foreign key properties in dependent entities are set to null. This helps keep the graph of entities in a consistent state while they are being tracked, such that a fully consistent graph can then be written to the database. (...) This is the default for optional relationships.
I.e.: the client executes SQL to nullify the foreign key values. The child records should be tracked though. To remove a BlogComment parent the delete action should look like:
using (var db = new MyContext(connectionString))
{
var c1 = db.BlogComments
.Include(c => c.Replies) // Children should be included
.SingleOrDefault(c => c.Id == 1);
db.BlogComments.Remove(c1);
db.SaveChanges();
}
As you see, you don't have to set ReplyToId = null, that's something EF takes care of.
For me, I had to Include() the entities I needed to be "dealt with" when I deleted an entity. EF cant manage things it is not currently tracking.
var breedToDelete = context.Breed
.Include(x => x.Cats)
.Single(x => x.Id == testBreedId);
context.Breed.Remove(breedToDelete);
context.SaveChanges();
I could get it working by manually setting ReplyTo to null. I'm still looking for a better solution, or an explanation why is it needed. Isn't it what OnDelete(DeleteBehavior.SetNull) supposed to do?
var comment = await context.BlogComment.Include(c => c.ReplyTo).SingleAsync(m => m.Id == id);
var reply = await context.BlogComment.SingleOrDefaultAsync(m => m.ReplyToId == id);
if (reply != null)
{
reply.ReplyTo = null;
reply.ReplyToId = null;
context.Entry(reply).State = EntityState.Modified;
}
context.BlogComment.Remove(comment);
I have a Gig Model as follows:
public class Gig
{
public int Id { get; set; }
[Required]
public ApplicationUser Artist { get; set; }
[Required]
public string ArtistId { get; set; }
public DateTime DateTime { get; set; }
[Required]
[StringLength(255)]
public string Venue { get; set; }
[Required]
public Genre Genre { get; set; }
[Required]
public byte GenreId { get; set; }
}
In EF6, I was able to Eager Load Artist and Genre using the following code
var gigs = _context.Attendances
.Where(a => a.AttendeeId == userId)
.Select(a => a.Gig)
.Include(a => a.Artist)
.Include(a => a.Genre)
.ToList();
But with EF Core, the Artist info or the Genre info is not getting loaded. SQL Profiler shows that there is no INNER JOIN being called on the projection tables.
SELECT [a.Gig].[Id], [a.Gig].[ArtistId], [a.Gig].[DateTime], [a.Gig].[GenreId], [a.Gig].[Venue]
FROM [Attendances] AS [a]
INNER JOIN [Gigs] AS [a.Gig] ON [a].[GigId] = [a.Gig].[Id]
WHERE [a].[AttendeeId] = #__userId_0',N'#__userId_0 nvarchar(450)',#__userId_0=N'469d8515-9a04-46af-9276-09c6fead9e10'
Can someone help me re-write the query for EF Core please to include the projection tables?
UPDATE:
added link to db schema scripts here. posting just the gigs table here:
CREATE TABLE [dbo].[Gigs](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ArtistId] [nvarchar](450) NOT NULL,
[DateTime] [datetime2](7) NOT NULL,
[GenreId] [tinyint] NOT NULL,
[Venue] [nvarchar](255) NOT NULL,
CONSTRAINT [PK_Gigs] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Attendances] WITH CHECK ADD CONSTRAINT [FK_Attendances_AspNetUsers_AttendeeId] FOREIGN KEY([AttendeeId])
REFERENCES [dbo].[AspNetUsers] ([Id])
GO
ALTER TABLE [dbo].[Attendances] CHECK CONSTRAINT [FK_Attendances_AspNetUsers_AttendeeId]
GO
ALTER TABLE [dbo].[Attendances] WITH CHECK ADD CONSTRAINT [FK_Attendances_Gigs_GigId] FOREIGN KEY([GigId])
REFERENCES [dbo].[Gigs] ([Id])
GO
ALTER TABLE [dbo].[Attendances] CHECK CONSTRAINT [FK_Attendances_Gigs_GigId]
GO
ALTER TABLE [dbo].[Gigs] WITH CHECK ADD CONSTRAINT [FK_Gigs_AspNetUsers_ArtistId] FOREIGN KEY([ArtistId])
REFERENCES [dbo].[AspNetUsers] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Gigs] CHECK CONSTRAINT [FK_Gigs_AspNetUsers_ArtistId]
GO
ALTER TABLE [dbo].[Gigs] WITH CHECK ADD CONSTRAINT [FK_Gigs_Genres_GenreId] FOREIGN KEY([GenreId])
REFERENCES [dbo].[Genres] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Gigs] CHECK CONSTRAINT [FK_Gigs_Genres_GenreId]
GO
If you turn on EF Core Logging, you'll see inside the log something like this:
The Include operation for navigation: 'a.Gig.Artist' was ignored because the target navigation is not reachable in the final query results.
and similar for a.Gig.Genre.
Looks like EF Core at this time cannot handle includes for such queries (that don't start from the resulting entity). The only workaround I can propose is to rewrite the query like this:
var gigs = _context.Gigs
.Where(g => g.Attendances.Any(a => a.AttendeeId == userId))
.Include(g => g.Artist)
.Include(g => g.Genre)
.ToList();
or this (translates to better SQL, although the SQL execution plan could be the same):
var gigs = (from g in _context.Gigs
from a in g.Attendances
where a.AttendeeId == userId
select g)
.Include(g => g.Artist)
.Include(g => g.Genre)
.ToList();
I am using code first migrations with an existing database. I used the reverse engineering feature to generate all the initial entities. However, the database was not well designed and one of the tables did not have a primary key designated. Apparently Entity Framework did its best to infer the primary key and didn't get it right. It looks like it decided to make two fields a compound primary key.
I want to actually remove one of the fields that it mistakenly thinks is part of the primary key and it's resulting in errors.
Here is the original schema of the table when the entity was created for it:
CREATE TABLE [dbo].[Table1](
[The_ID] [bigint] IDENTITY(1,1) NOT NULL,
[Field_1] [varbinary](max) NULL,
[Field_2] [bigint] NULL,
[Field_3] [varbinary](max) NULL,
[Field_4] [datetime] NULL,
[Field_5] [bit] NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Table1] ADD CONSTRAINT [DF_Table1_F5] DEFAULT ((0)) FOR [Field_5]
GO
The_ID should have been designated as the primary key but as you can see it was not. Here is class the Visual Studio generated:
public partial class Table1
{
[Key]
[Column(Order = 0)]
public long The_ID { get; set; }
public byte[] Field_1 { get; set; }
public long? Field_2 { get; set; }
public byte[] Field_3 { get; set; }
public DateTime? Field_4 { get; set; }
[Key]
[Column(Order = 1)]
public bool Field_5 { get; set; }
}
It apparently decided to make The_ID and Field_5 into a compound primary key. At least that's how I'm interpreting this code. Now, I actually need to remove Field_5 from the table. I created an migration to do this, but since Entity Framework thinks its part of a primary key its doing weird things like dropping the primary key and re-adding it, which is resulting in errors. Here is the generated migration code:
public override void Up()
{
DropPrimaryKey("dbo.Table1");
AlterColumn("dbo.Table1", "The_ID", c => c.Long(nullable: false, identity: true));
AddPrimaryKey("dbo.Table1", "The_ID");
DropColumn("dbo.Table1", "Field_5");
}
Running this results in the following error:
ALTER TABLE [dbo].[Table1] DROP CONSTRAINT [PK_dbo.Table1]
System.Data.SqlClient.SqlException (0x80131904): 'PK_dbo.Table1' is not a constraint.
So how do I get myself out of this mess?
I tried removing the [Key] attributes from The_ID and Field_5 and creating a dummy migration using
Add-Migration dummy -IgnoreChanges
with the thought that I could then add the [Key] attribute back to The_ID and remove Field_5 but it won't let me create a migration unless at least one field is designated with the [Key] attribute. But if I do that to get the dummy migration in place then I can't do it in a real migration so I'm not able to actually designate The_ID as the primary key using code first migrations.
Any ideas?
If you have a Table without Primary Key in you Database EntityFramework will interpret every not nullable column in the table as part of the Primary Key.
In this case you could just remove the first two lines from the generated Migration, because there is no primary key to drop. EntityFramework just does not know about this fact.
public override void Up()
{
//DropPrimaryKey("dbo.Table1");
//AlterColumn("dbo.Table1", "The_ID", c => c.Long(nullable: false, identity: true));
AddPrimaryKey("dbo.Table1", "The_ID");
DropColumn("dbo.Table1", "Field_5");
}
I've just noticed a rather strange behaviour with how Entity Framework creates a Code-First DB when you have a model with a parent that has a list of children, but also an optional navigation property to one particular child: I end up with an additional nullable foreign key column on the child that I'm not expecting. Can anyone please explain whether this column is actually necessary? And for that matter, can anyone suggest a better way of indicating that a particular child is Selected/Active.
To elaborate:
Given this model:
public class Parent
{
public int Id { get; set; }
public virtual List<Child> Children { get; set; }
// Optional navigation property to one of the child objects.
public int? ActiveChildId { get; set; }
public virtual Child ActiveChild { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
I end up with the following DB:
CREATE TABLE [dbo].[Parents](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ActiveChildId] [int] NULL,
CONSTRAINT [PK_dbo.Parents] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Parents] WITH CHECK ADD CONSTRAINT [FK_dbo.Parents_dbo.Children_ActiveChildId] FOREIGN KEY([ActiveChildId])
REFERENCES [dbo].[Children] ([Id])
GO
ALTER TABLE [dbo].[Parents] CHECK CONSTRAINT [FK_dbo.Parents_dbo.Children_ActiveChildId]
GO
CREATE TABLE [dbo].[Children](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ParentId] [int] NOT NULL,
[Parent_Id] [int] NULL,
CONSTRAINT [PK_dbo.Children] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Children] WITH CHECK ADD CONSTRAINT [FK_dbo.Children_dbo.Parents_Parent_Id] FOREIGN KEY([Parent_Id])
REFERENCES [dbo].[Parents] ([Id])
GO
ALTER TABLE [dbo].[Children] CHECK CONSTRAINT [FK_dbo.Children_dbo.Parents_Parent_Id]
GO
ALTER TABLE [dbo].[Children] WITH CHECK ADD CONSTRAINT [FK_dbo.Children_dbo.Parents_ParentId] FOREIGN KEY([ParentId])
REFERENCES [dbo].[Parents] ([Id])
GO
ALTER TABLE [dbo].[Children] CHECK CONSTRAINT [FK_dbo.Children_dbo.Parents_ParentId]
GO
i.e. there is both a ParentId (NOT NULL) column on the child AND a Parent_Id (NULL) column on the child.
It seems to me that since we've already got a 1:N foreign key relationship Parent to Child, then by adding a one-way 1:[0 or 1] Parent to Child relationship it shouldn't create another foreign key column on the child.
Add the InverseProperty attribute:
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
[InverseProperty( "Children" )]
public virtual Parent Parent { get; set; }
}
Or map the relationship via Fluent API and specify the FK as ParentId:
modelBuilder.Entity<Parent>()
.HasMany( p => p.Children )
.WithRequired( c => c.Parent )
.HasForeignKey( c => c.ParentId );