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() |
HasMany().WithMany()
a blog can have many posts, a post can come from many blogs
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); } } }
HasMany().WithMany().Map()
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); } } }
HasMany().WithOptional()
BlogId will be a null foreign key column
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); } } }
BlogId
property of Post
classHasOptional(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")); } } }
HasMany().WithRequired()
BlogId will be non-null foreign key
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 modelHasOptional().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
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); } } }
HasOptional().WithOptional().Map()
HasOptional().WithRequired()
We will assume Blog
to be required and Post
to be optional in this example.
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.DatabaseGeneratedOption.None
HasOptional().WithRequired().Map()
. In this case this mapped column will become not null.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
.
HasOptional(p => p.Post).WithRequired(p => p.Blog).WillCascadeOnDelete(false);
HasRequired(p => p.Blog).WithOptional(p => p.Post).WillCascadeOnDelete(false);
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(); } } }
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(); } } }
HasRequired().WithRequired()
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(); } } }
Blog blog = new Blog {Title=""world"", Post = null, BloggerName = ""suyash""}
, there will NOT be any 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.