137

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.

RationalGeek
  • 9,425
  • 11
  • 62
  • 90
  • 1
    I dont think this is actually possible with just EF, you probably need to use some raw SQL in a manual migration to set it up – undefined Jan 02 '14 at 15:35
  • @LukeMcGregor that is what I feared. If you make that an answer I'll be glad to accept it, assuming it is correct. :-) – RationalGeek Jan 02 '14 at 16:22
  • Is there any specific reason for not having the navigation property? Would making the navigation property private work for you - wouldn't be visible outside the entity but would please EF. (Note I have not tried this but I think this should work - take a look at this post about mapping private properties http://romiller.com/2012/10/01/mapping-to-private-properties-with-code-first/) – Pawel Jan 02 '14 at 23:26
  • 13
    Well I don't want it because I don't need it. I don't like having to put extra unnecessary stuff in a design just to satisfy the requirements of a framework. Would it kill me to put in the nav prop? No. In fact that's what I've done for the time being. – RationalGeek Jan 03 '14 at 02:46
  • You always need navigation property on at least one side to build a relation. For more information http://stackoverflow.com/a/7105288/105445 – Wahid Bitar Mar 13 '14 at 12:50
  • @RationalGeek Could you please change the accepted answer? The current one is incorrect, and the highest voted answer rightfully contradicts it. With an incorrect answer as the accepted one, people could be misled without seeing the correct answer. – Timo Aug 13 '20 at 11:31

6 Answers6

187

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().

starball
  • 20,030
  • 7
  • 43
  • 238
David Liang
  • 20,385
  • 6
  • 44
  • 70
  • 19
    This is the correct answer for EF Core, and for those practicing DDD, this is a MUST. – Thiago Silva Feb 03 '19 at 02:07
  • 1
    I'm not clear on what has changed to remove the navigational property. Can you please clarify? – andrew.rockwell Feb 22 '19 at 20:31
  • 3
    @andrew.rockwell: See the `HasOne()` and `.WithMany()` on thie child configuration. They don't reference the navigation properties at all, since there is no navigation properties defined anyway. I will try to make it clearer with my updates. – David Liang Feb 22 '19 at 21:56
  • Let's say the Parent class has the following property: `List Children`. Is there a way to map this one-to-many relationship with this approach? Without having a list of Child objects. – Absolom Apr 09 '19 at 13:27
  • @Absolom: I am not sure and I don't have time to test it (been so busy recently). Have you tried it yourself? My first thought is no because the modal builder searches by the types. Being an `int` is just too general. But I might be wrong. The only way right now I can think of to achieve what you want is to select the IDs from the children objects as a readonly property. – David Liang Apr 09 '19 at 20:09
  • @DavidLiang: I started playing with EF yesterday, so everything is new to me :) But after reading, it seems that you cannot have a list of primitive types, it must be a list of entities. So I created a basic entity AggregateReference that contains a single Id property that represent the Children Ids. Not ideal, but does the trick. Thanks! – Absolom Apr 09 '19 at 20:39
  • @ThiagoSilva Hey! I would like to know why you think it is a MUST if DDD is used. What do I violate if I use navigation properties? You mean I pollute my domain entity with data layer relevant properties? thanks for your answer! – mirind4 May 08 '20 at 17:22
  • @mirind4: I just started DDD so I am not an expert of it but here is my opinion: the whole point of identifying bounded contexts and aggregates is to help you define the domain models that make sense with the context and ensure they're consistent together. You should only make changes to the aggregate via the Aggregate Root. If you use navigation properties, you can easily step outside of the bounded contexts and mess around other aggregates you might define in other contexts. – David Liang May 09 '20 at 06:16
  • 2
    @mirind4 unrelated to the specific code sample in the OP, if you're mapping different aggregate root entities to their respective DB tables, according to DDD, those root entites should only reference one another via their identity, and should not contain a full reference to the other AR entity. In fact, in DDD it's also common to make the identity of entities a value object instead of using props with primitive types like int/log/guid (especially AR entities), avoiding primitive obsession and also allowing different ARs to reference entities via the value object ID type. HTH – Thiago Silva May 18 '20 at 19:15
  • Works perfect with .Net5 and as stated before a must have for DDD to reference aggregates from other aggregates etc. Should be marked as the accepted answer! – Ilias.P Nov 16 '20 at 10:34
  • How would you save new Parent and new Child in one operation - considering that Child refers to new parent ID? Add Parent - then Save Changes - then Add Child - then Save Changes again? – Jam Aug 28 '21 at 11:04
  • @Jam: in the case of the original question? Or in my case, where parent and the child are two aggregate roots? In the case of the original question, to be honest, I don't know, as I haven't tried it myself, but I guess you would have to wrap the whole operation in transaction, create the parent first, get its ID, and create its children with Parent.ID? In my case, I don't have the requirement to save them in one operation, as they're ... two separate aggregate roots. – David Liang Sep 06 '21 at 08:37
  • What about one-2-one? – Efe Nov 15 '21 at 22:52
  • 1
    Kudos for using clear domain-specific naming (parent, child, parentId). This makes the code absolutely easy to understand. – Dima Feb 09 '22 at 16:03
  • In my case I had a child class with multiple parent types and didn't want to create a navigation property for each. This worked for me (EF Core 6) but I had to include .WithMany(p => p.Children) as each parent has a collection of child objects. Otherwise EF would assume the foreign key incorrectly. – David Adams Aug 10 '22 at 23:26
68

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.

Slauma
  • 175,098
  • 59
  • 401
  • 420
  • 2
    The simplification comes from the automation: I don't have access to other environments to which my code is deployed. Being able to perform these changes in code is nice for me. But I like the snark :) – pomeroy Dec 30 '16 at 19:45
  • I think you also just set an attribute on the entity, so on parent id, just add `[ForeignKey("ParentTableName")]`. That will link the property to whatever the key is on the parent table. Now you've got a hard-coded table name though. – Triynko Feb 27 '18 at 17:12
  • 3
    It's clearly not impossible, see the other comment below Why is this even marked as correct answer – Igor Be Jun 22 '19 at 15:20
  • > Why is this even marked as correct answer -- I guess because this answer is from 2014, and the actual correct answer from Jonatan Dragon is from 2019 ;) – Serhii Shushliapin Mar 05 '21 at 06:59
41

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; }
    }
}
Jonatan Dragon
  • 4,675
  • 3
  • 30
  • 38
  • 2
    This solution is really nice as it makes the definition of entities much leaner, simple and loosely coupled with the ORM that is being used. – Sirch Dcmp Jul 17 '21 at 06:43
22

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.

tenbits
  • 7,568
  • 5
  • 34
  • 53
5

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.

Radded Weaver
  • 51
  • 1
  • 2
0

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.

rattler
  • 379
  • 2
  • 5
  • 15