Code First Fluent API Relationships

Models i will use to explain

using System.Collections.Generic;

namespace DataLayer
{
    public class Blog
    {
        public int BlogKey { get; set; }
        public string Title { get; set; }
        public string BloggerName { get; set; }
        public Post Post { get; set; }
    }
}

using System;

namespace DataLayer
{
    public class Post
    {
        public int PostKey { get; set; }
        public string Title { get; set; }
        public DateTime DateCreated { get; set; }
        public string Content { get; set; }
        public Blog Blog { get; set; }
    }
}

using System.Data.Entity.ModelConfiguration;

namespace DataLayer
{
    public class BlogConfiguration : EntityTypeConfiguration<Blog>
    {
        public BlogConfiguration()
        {
            ToTable("Blog", "dbo");
            HasKey(k => k.BlogKey);
        }
    }

    public class PostConfiguration : EntityTypeConfiguration<post>
    {
        public PostConfiguration()
        {
            ToTable("Post", "dbo");
            HasKey(k => k.PostKey);
        }
    }
}

On Trying to insert a row in Blog table it threw an error saying Unable to determine the principal end of an association between the types 'DataLayer.Post' and 'DataLayer.Blog'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.

This brings us to what do we mean by Principal & Dependent ends of an association. Now as per this Stack Overflow question we come to an understanding that In a 1-1 relation one end must be principal and second end must be dependent. Principal end is the one which will be inserted first and which can exist without the dependent one. Dependent end is the one which must be inserted after the principal because it has a foreign key to the principal

Many refers to *, Required refers to 1, Optional refers to 0..1(zero or one)

List of relationships

HasMany() WithMany() Map()
HasMany() WithMany() MapToStoredProcedures()
HasMany() WithOptional() HasForeignKey() WillCascadeOnDelete
HasMany() WithOptional() Map() WillCascadeOnDelete
HasMany() WithOptional() WillCascadeOnDelete
HasMany() WithRequired() HasForeignKey() WillCascadeOnDelete
HasMany() WithRequired() Map() WillCascadeOnDelete
HasMany() WithRequired() WillCascadeOnDelete
HasOptional() WithMany() HasForeignKey() WillCascadeOnDelete()
HasOptional() WithMany() Map() WillCascadeOnDelete()
HasOptional() WithMany() WillCascadeOnDelete()
HasOptional() WithOptionalDependent() Map() WillCascadeOnDelete()
HasOptional() WithOptionalDependent() WillCascadeOnDelete()
HasOptional() WithOptionalPrincipal() Map() WillCascadeOnDelete()
HasOptional() WithOptionalPrincipal() WillCascadeOnDelete()
HasOptional() WithRequired() Map() WillCascadeOnDelete()
HasOptional() WithRequired() WillCascadeOnDelete()
HasRequired() WithMany() HasForeignKey() WillCascadeOnDelete()
HasRequired() WithMany() Map() WillCascadeOnDelete()
HasRequired() WithMany() WillCascadeOnDelete()
HasRequired() WithOptional() Map() WillCascadeOnDelete()
HasRequired() WithOptional() WillCascadeOnDelete()
HasRequired() WithRequiredPrincipal() Map() WillCascadeOnDelete()
HasRequired() WithRequiredPrincipal() WillCascadeOnDelete()
HasRequired() WithRequiredDependent() Map() WillCascadeOnDelete()
HasRequired() WithRequiredDependent() WillCascadeOnDelete()
  1. HasMany().WithMany() a blog can have many posts, a post can come from many blogs
    ways to write
    • HasMany().WithMany()
    
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Blog
        {
            public int BlogKey { get; set; }
            public string Title { get; set; }
            public string BloggerName { get; set; }
    
            //Navigational Property
            public virtual ICollection<Post> Posts { get; set; }
        }
    }
    
    using System;
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Post
        {
            public int PostKey { get; set; }
            public string Title { get; set; }
            public DateTime DateCreated { get; set; }
            public string Content { get; set; }
    
            //Navigational Property
            public virtual ICollection<Blog> Blogs { get; set; }
        }
    }
    
    using System.Data.Entity.ModelConfiguration;
    
    namespace DataLayer
    {
        public class BlogConfiguration : EntityTypeConfiguration<Blog>
        {
            public BlogConfiguration()
            {
                ToTable("Blog", "dbo");
                HasKey(k => k.BlogKey);
                HasMany(p => p.Posts).WithMany(p => p.Blogs);
            }
        }
    
        public class PostConfiguration : EntityTypeConfiguration<post>
        {
            public PostConfiguration()
            {
                ToTable("Post", "dbo");
                HasKey(k => k.PostKey);
            }
        }
     }
    
    
    

    To have the mapping in another table and with a different column names use HasMany().WithMany().Map()
    Updating the Configuration
    
    using System.Data.Entity.ModelConfiguration;
    
    namespace DataLayer
    {
        public class BlogConfiguration : EntityTypeConfiguration<Blog>
        {
            public BlogConfiguration()
            {
                ToTable("Blog", "dbo");
                HasKey(k => k.BlogKey);
                HasMany(p => p.Posts).WithMany(p => p.Blogs).Map(m=> m.ToTable("AnotherBlogsPostsTable", "dbo").MapLeftKey("BlogId").MapRightKey("PostId"));
            }
        }
    
        public class PostConfiguration : EntityTypeConfiguration<Post>
        {
            public PostConfiguration()
            {
                ToTable("Post", "dbo");
                HasKey(k => k.PostKey);
            }
        }
    }
    
  2. HasMany().WithOptional() BlogId will be a null foreign key column
    ways to write
    • HasMany(p => p.Posts).WithOptional(p => p.Blog).HasForeignKey(p => p.BlogId).WillCascadeOnDelete(false);
    • HasOptional(p => p.Blog).WithMany(p => p.Posts).HasForeignKey(p => p.BlogId).WillCascadeOnDelete(false);
    
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Blog
        {
            public int BlogKey { get; set; }
            public string Title { get; set; }
            public string BloggerName { get; set; }
            public virtual ICollection<Post> Posts { get; set; }
        }
    }
    
    using System;
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Post
        {
            public int PostKey { get; set; }
            public string Title { get; set; }
            public DateTime? DateCreated { get; set; }
            public string Content { get; set; }
            public int? BlogId { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }
    
    
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.ModelConfiguration;
    
    namespace DataLayer
    {
        public class BlogConfiguration : EntityTypeConfiguration<Blog>
        {
            public BlogConfiguration()
            {
                ToTable("Blog", "dbo");
                HasKey(k => k.BlogKey).Property(p=>p.BlogKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                //HasMany(p => p.Posts).WithOptional(p => p.Blog).HasForeignKey(p => p.BlogId).WillCascadeOnDelete(false);
            }
        }
    
        public class PostConfiguration : EntityTypeConfiguration<Post>
        {
            public PostConfiguration()
            {
                ToTable("Post", "dbo");
                HasKey(k => k.PostKey).Property(p=>p.PostKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                HasOptional(p => p.Blog).WithMany(p => p.Posts).HasForeignKey(p => p.BlogId).WillCascadeOnDelete(false);
            }
        }
    }
    
    
    Notice the nullable BlogId property of Post class

    To have the foreign key in database and not exposed use HasOptional(p => p.Blog).WithMany(p => p.Posts).Map(m=> m.MapKey("AnotherBlogId"));
    BlogId can be removed now from the model. If you have it, it will be just another non-null property of the model.
    
    using System;
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Post
        {
            public int PostKey { get; set; }
            public string Title { get; set; }
            public DateTime? DateCreated { get; set; }
            public string Content { get; set; }
            //public int? BlogId { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }
    
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.ModelConfiguration;
    
    namespace DataLayer
    {
        public class BlogConfiguration : EntityTypeConfiguration<Blog>
        {
            public BlogConfiguration()
            {
                ToTable("Blog", "dbo");
                HasKey(k => k.BlogKey).Property(p=>p.BlogKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                //HasMany(p => p.Posts).WithOptional(p => p.Blog).HasForeignKey(p => p.BlogId).WillCascadeOnDelete(false);
            }
        }
    
        public class PostConfiguration : EntityTypeConfiguration<Post>
        {
            public PostConfiguration()
            {
                ToTable("Post", "dbo");
                HasKey(k => k.PostKey).Property(p=>p.PostKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                HasOptional(p => p.Blog).WithMany(p => p.Posts).Map(m=> m.MapKey("AnotherBlogId"));
            }
        }
    }
    
  3. HasMany().WithRequired() BlogId will be non-null foreign key
    ways to write
    • HasMany(p => p.Posts).WithRequired(p => p.Blog).HasForeignKey(k => k.BlogId).WillCascadeOnDelete(false);
    • HasRequired(p => p.Blog).WithMany(p => p.Posts).HasForeignKey(k => k.BlogId).WillCascadeOnDelete(false);
    
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Blog
        {
            public int BlogKey { get; set; }
            public string Title { get; set; }
            public string BloggerName { get; set; }
            public virtual ICollection<Post> Posts { get; set; }
        }
    }
    
    using System;
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Post
        {
            public int PostKey { get; set; }
            public string Title { get; set; }
            public DateTime? DateCreated { get; set; }
            public string Content { get; set; }
            public int BlogId { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }
    
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.ModelConfiguration;
    
    namespace DataLayer
    {
        public class BlogConfiguration : EntityTypeConfiguration<Blog>
        {
            public BlogConfiguration()
            {
                ToTable("Blog", "dbo");
                HasKey(k => k.BlogKey).Property(p=>p.BlogKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                //HasMany(p => p.Posts).WithRequired(p => p.Blog).HasForeignKey(k => k.BlogId).WillCascadeOnDelete(false);
            }
        }
    
        public class PostConfiguration : EntityTypeConfiguration<Post>
        {
            public PostConfiguration()
            {
                ToTable("Post", "dbo");
                HasKey(k => k.PostKey).Property(p=>p.PostKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                HasRequired(p => p.Blog).WithMany(p => p.Posts).HasForeignKey(k => k.BlogId).WillCascadeOnDelete(false);
            }
        }
    }
    
    HasMany().WithRequired().Map() works in the same way that, in case we need to create a foreign key in database that we do not want to expose in the model
  4. HasOptional().WithOptional() PostId in Blog and BlogId in Post will have foreign key in one of them depending on which is chosen to be Principal and Dependent. Let's for this example have Blog as Principal
    ways to write
    • HasOptional(p => p.Post).WithOptionalPrincipal(p => p.Blog).WillCascadeOnDelete(false);
    • HasOptional(p => p.Blog).WithOptionalDependent(p => p.Post).WillCascadeOnDelete(false);
    
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Blog
        {
            public int BlogKey { get; set; }
            public string Title { get; set; }
            public string BloggerName { get; set; }
            public virtual Post Post { get; set; }
        }
    }
    
    
    using System;
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Post
        {
            public int PostKey { get; set; }
            public string Title { get; set; }
            public DateTime? DateCreated { get; set; }
            public string Content { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }
    
    
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.ModelConfiguration;
    
    namespace DataLayer
    {
        public class BlogConfiguration : EntityTypeConfiguration<Blog>
        {
            public BlogConfiguration()
            {
                ToTable("Blog", "dbo");
                HasKey(k => k.BlogKey).Property(p=>p.BlogKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                //HasOptional(p => p.Post).WithOptionalPrincipal(p => p.Blog).WillCascadeOnDelete(false);
            }
        }
    
        public class PostConfiguration : EntityTypeConfiguration<Post>
        {
            public PostConfiguration()
            {
                ToTable("Post", "dbo");
                HasKey(k => k.PostKey).Property(p=>p.PostKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                HasOptional(p => p.Blog).WithOptionalDependent(p => p.Post).WillCascadeOnDelete(false);
            }
        }
    }
    
    This does not allows to have a foreign key property in the model, automatically creates a foreign key by convention in database. The name of this foreign key can be overriden by using HasOptional().WithOptional().Map()
  5. HasOptional().WithRequired() We will assume Blog to be required and Post to be optional in this example.
    In case Blog and Post models have there primary keys set by database, forexample : identity columns. This configuration will throw an error. Read This Stack Overflow question.
    We need to keep our optional table key as DatabaseGeneratedOption.None
    By default the dependent's primary key will also become foreign key column which will reference the primary key column of the principal. We can make the dependent's primary key to be databasegenerated as an identity column if we provide a mapped column using HasOptional().WithRequired().Map(). In this case this mapped column will become not null.
    After this example we will see the difference between HasOptional().WithRequired() & HasRequired().WithRequired() with an example. We will notice that the database structure made for these two are same. The difference is that in case of HasRequired().WithRequired() a Blog can not exist without a Post.
    ways to write
    • HasOptional(p => p.Post).WithRequired(p => p.Blog).WillCascadeOnDelete(false);
    • HasRequired(p => p.Blog).WithOptional(p => p.Post).WillCascadeOnDelete(false);
    Below example demonstrates HasOptional().WithRequired() with DatabaseGeneratedOption.None for the dependent and no mapped column.
    
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Blog
        {
            public int BlogKey { get; set; }
            public string Title { get; set; }
            public string BloggerName { get; set; }
            public virtual Post Post { get; set; }
        }
    }
    
    
    
    using System;
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Post
        {
            public int PostKey { get; set; }
            public string Title { get; set; }
            public DateTime? DateCreated { get; set; }
            public string Content { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }
    
    
    
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.ModelConfiguration;
    
    namespace DataLayer
    {
        public class BlogConfiguration : EntityTypeConfiguration<Blog>
        {
            public BlogConfiguration()
            {
                ToTable("Blog", "dbo");
                HasKey(k => k.BlogKey).Property(p=>p.BlogKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                HasOptional(p => p.Post).WithRequired(p => p.Blog).WillCascadeOnDelete(false);
            }
        }
    
        public class PostConfiguration : EntityTypeConfiguration<Post>
        {
            public PostConfiguration()
            {
                ToTable("Post", "dbo");
                HasKey(k => k.PostKey).Property(p=>p.PostKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
                //HasRequired(p => p.Blog).WithOptional(p => p.Post).WillCascadeOnDelete(false);
    
            }
        }
    }
    
    using DataLayer;
    using System;
    
    namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                MyDbContext c = new MyDbContext();
    
                //Blog required, Post not required
                Blog blog = new Blog { Title = "world", Post = null, BloggerName = "suyash" };
    
    
                //Blog required, Post required
                //Blog blog = new Blog { Title = "work", Post = new Post { Title = "new world post" }, BloggerName = "suyash" };
    
                c.Blogs.Add(blog);
    
                c.SaveChanges();
    
            }
        }
    }
    
    
    
    Below example demonstrates HasOptional().WithRequired() with DatabaseGeneratedOption.Identity for the dependent and a mapped column.
    
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Blog
        {
            public int BlogKey { get; set; }
            public string Title { get; set; }
            public string BloggerName { get; set; }
            public virtual Post Post { get; set; }
        }
    }
    
    
    
    using System;
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Post
        {
            public int PostKey { get; set; }
            public string Title { get; set; }
            public DateTime? DateCreated { get; set; }
            public string Content { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }
    
    
    
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.ModelConfiguration;
    
    namespace DataLayer
    {
        public class BlogConfiguration : EntityTypeConfiguration<Blog>
        {
            public BlogConfiguration()
            {
                ToTable("Blog", "dbo");
                HasKey(k => k.BlogKey).Property(p=>p.BlogKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                HasOptional(p => p.Post).WithRequired(p => p.Blog).Map(m=>m.MapKey("BlogKey")).WillCascadeOnDelete(false);
            }
        }
    
        public class PostConfiguration : EntityTypeConfiguration<Post>
        {
            public PostConfiguration()
            {
                ToTable("Post", "dbo");
                HasKey(k => k.PostKey).Property(p=>p.PostKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                //HasRequired(p => p.Blog).WithOptional(p => p.Post).WillCascadeOnDelete(false);
    
            }
        }
    }
    
    using DataLayer;
    using System;
    
    namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                MyDbContext c = new MyDbContext();
    
                //Blog required, Post not required
                Blog blog = new Blog { Title = "world", Post = null, BloggerName = "suyash" };
    
    
                //Blog required, Post required
                //Blog blog = new Blog { Title = "work", Post = new Post { Title = "new world post" }, BloggerName = "suyash" };
    
                c.Blogs.Add(blog);
    
                c.SaveChanges();
    
            }
        }
    }
    
    
    
  6. HasRequired().WithRequired()
    ways to write
    • HasRequired(p => p.Post).WithRequiredPrincipal(p => p.Blog).WillCascadeOnDelete(false);
    • HasRequired(p => p.Blog).WithRequiredDependent(p => p.Post).WillCascadeOnDelete(false)
    
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Blog
        {
            public int BlogKey { get; set; }
            public string Title { get; set; }
            public string BloggerName { get; set; }
            public virtual Post Post { get; set; }
        }
    }
    
    
    
    using System;
    using System.Collections.Generic;
    
    namespace DataLayer
    {
        public class Post
        {
            public int PostKey { get; set; }
            public string Title { get; set; }
            public DateTime? DateCreated { get; set; }
            public string Content { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }
    
    
    
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.ModelConfiguration;
    
    namespace DataLayer
    {
        public class BlogConfiguration : EntityTypeConfiguration<Blog>
        {
            public BlogConfiguration()
            {
                ToTable("Blog", "dbo");
                HasKey(k => k.BlogKey).Property(p=>p.BlogKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                HasRequired(p => p.Post).WithRequiredPrincipal(p => p.Blog).WillCascadeOnDelete(false);
            }
        }
    
        public class PostConfiguration : EntityTypeConfiguration<Post>
        {
            public PostConfiguration()
            {
                ToTable("Post", "dbo");
                HasKey(k => k.PostKey).Property(p=>p.PostKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
                //HasRequired(p => p.Blog).WithRequiredDependent(p => p.Post).WillCascadeOnDelete(false);
    
            }
        }
    }
    
    using DataLayer;
    using System;
    
    namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                MyDbContext c = new MyDbContext();
    
                //Blog required, Post not required
                //Blog blog = new Blog { Title = "world", Post = null, BloggerName = "suyash" };
    
    
                //Blog required, Post required
                Blog blog = new Blog { Title = "work", Post = new Post { Title = "new world post" }, BloggerName = "suyash" };
    
                c.Blogs.Add(blog);
    
                c.SaveChanges();
    
            }
        }
    }
    
    
    
    In case you run Blog blog = new Blog {Title=""world"", Post = null, BloggerName = ""suyash""}, there will NOT be any error.
    Asked this question on Stack Overflow here. But, in case we map the dependent's foreign key column to another column it will throw an error: {"Entities in 'MyDbContext.Blogs' participate in the 'Blog_Post' relationship. 0 related 'Blog_Post_Target' were found. 1 'Blog_Post_Target' is expected."} about requiring the post.