EF Code First foreign key without navigation property
Entity FrameworkEf Code-FirstEntity Framework Problem Overview
Let's say I have the following entities:
public class Parent
{
public int Id { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
}
What is the code first fluent API syntax to enforce that ParentId is created in the database with a foreign key constraint to the Parents table, without the need to have a navigation property?
I know that if I add a navigation property Parent to Child, then I can do this:
modelBuilder.Entity<Child>()
.HasRequired<Parent>(c => c.Parent)
.WithMany()
.HasForeignKey(c => c.ParentId);
But I don't want the navigation property in this particular case.
Entity Framework Solutions
Solution 1 - Entity Framework
Although this post is for Entity Framework
not Entity Framework Core
, It might be useful for someone who wants to achieve the same thing using Entity Framework Core (I am using V1.1.2).
I don't need navigation properties (although they're nice) because I am practicing DDD and I want Parent
and Child
to be two separate aggregate roots. I want them to be able to talk to each other via foreign key not through infrastructure-specific Entity Framework
navigation properties.
All you have to do is to configure the relationship on one side using HasOne
and WithMany
without specifying the navigation properties (they're not there after all).
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}
protected override void OnModelCreating(ModelBuilder builder)
{
......
builder.Entity<Parent>(b => {
b.HasKey(p => p.Id);
b.ToTable("Parent");
});
builder.Entity<Child>(b => {
b.HasKey(c => c.Id);
b.Property(c => c.ParentId).IsRequired();
// Without referencing navigation properties (they're not there anyway)
b.HasOne<Parent>() // <---
.WithMany() // <---
.HasForeignKey(c => c.ParentId);
// Just for comparison, with navigation properties defined,
// (let's say you call it Parent in the Child class and Children
// collection in Parent class), you might have to configure them
// like:
// b.HasOne(c => c.Parent)
// .WithMany(p => p.Children)
// .HasForeignKey(c => c.ParentId);
b.ToTable("Child");
});
......
}
}
I am giving out examples on how to configure entity properties as well, but the most important one here is HasOne<>
, WithMany()
and HasForeignKey()
.
Hope it helps.
Solution 2 - Entity Framework
With EF Code First Fluent API it is impossible. You always need at least one navigation property to create a foreign key constraint in the database.
If you are using Code First Migrations you have the option to add a new code based migration on the package manager console (add-migration SomeNewSchemaName
). If you changed something with your model or mapping a new migration will be added. If you didn't change anything force a new migration by using add-migration -IgnoreChanges SomeNewSchemaName
. The migration will only contain empty Up
and Down
methods in this case.
Then you can modify the Up
method by adding the follwing to it:
public override void Up()
{
// other stuff...
AddForeignKey("ChildTableName", "ParentId", "ParentTableName", "Id",
cascadeDelete: true); // or false
CreateIndex("ChildTableName", "ParentId"); // if you want an index
}
Running this migration (update-database
on package manage console) will run a SQL statement similar to this (for SQL Server):
ALTER TABLE [ChildTableName] ADD CONSTRAINT [FK_SomeName]
FOREIGN KEY ([ParentId]) REFERENCES [ParentTableName] ([Id])
CREATE INDEX [IX_SomeName] ON [ChildTableName] ([ParentId])
Alternatively, without migrations, you could just run a pure SQL command using
context.Database.ExecuteSqlCommand(sql);
where context
is an instance of your derived context class and sql
is just the above SQL command as string.
Be aware that with all this EF has no clue that ParentId
is a foreign key that describes a relationship. EF will consider it only as an ordinary scalar property. Somehow all the above is only a more complicated and slower way compared to just opening a SQL management tool and to add the constraint by hand.
Solution 3 - Entity Framework
In case of EF Core you don't necessarily need to provide a navigation property. You can simply provide a Foreign Key on one side of the relationship. A simple example with Fluent API:
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
namespace EFModeling.Configuring.FluentAPI.Samples.Relationships.NoNavigation
{
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne<Blog>()
.WithMany()
.HasForeignKey(p => p.BlogId);
}
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
}
}
Solution 4 - Entity Framework
Small hint for those, who want to use DataAnotations and don't want to expose Navigation Property - use protected
public class Parent
{
public int Id { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
protected virtual Parent Parent { get; set; }
}
Thats it - the foreign key with cascade:true
after Add-Migration
will be created.
Solution 5 - Entity Framework
I'm using .Net Core 3.1, EntityFramework 3.1.3.
I have been searching around and the Solution I came up with was using the generic version of HasForeginKey<DependantEntityType>(e => e.ForeginKeyProperty)
.
you can create a one to one relation like so:
builder.entity<Parent>()
.HasOne<Child>()
.WithOne<>()
.HasForeginKey<Child>(c => c.ParentId);
builder.entity<Child>()
.Property(c => c.ParentId).IsRequired();
Hope this helps or at least provides some other ideas on how to use the HasForeginKey
method.
Solution 6 - Entity Framework
My reason for not using navigation properties is class dependencies. I separated my models to few assemblies, which can be used or not used in different projects in any combinations. So if I have entity which has nagivation property to class from another assembly, I need to reference that assembly, which I want to avoid (or any project which uses part of that complete data model will carry everything with it).
And I have separate migration app, which is used for migrations (I use automigrations) and initial DB creation. This project references everything by obvious reasons.
Solution is C-style:
- "copy" file with target class to migration project via link (drag-n-drop with
alt
key in VS) - disable nagivation property (and FK attribute) via
#if _MIGRATION
- set that preprocessor definition in migration app and don't set in model project, so it will not reference anything (don't reference assembly with
Contact
class in example).
Sample:
public int? ContactId { get; set; }
#if _MIGRATION
[ForeignKey(nameof(ContactId))]
public Contact Contact { get; set; }
#endif
Of course you should same way disable using
directive and change namespace.
After that all consumers can use that property as usual DB field (and don't reference additional assemblies if they aren't needed), but DB server will know that it is FK and can use cascading. Very dirty solution. But works.