entity-framework/core/modeling/relationships/conventions.md
EF Core uses a set of conventions when discovering and building a model based on entity type classes. This document summarizes the conventions used for discovering and configuring relationships between entity types.
[!IMPORTANT] The conventions described here can be overridden by explicit configuration of the relationship using either mapping attributes or the model building API.
[!TIP] The code below can be found in RelationshipConventions.cs.
Relationship discovery begins by discovering navigations between entity types.
A property of an entity type is discovered as a reference navigation when:
For example, consider the following entity types:
<!-- public class Blog { // Not discovered as reference navigations: public int Id { get; set; } public string Title { get; set; } = null!; public Uri? Uri { get; set; } public ConsoleKeyInfo ConsoleKeyInfo { get; set; } public Author DefaultAuthor => new() { Name = $"Author of the blog {Title}" }; // Discovered as a reference navigation: public Author? Author { get; private set; } } public class Author { // Not discovered as reference navigations: public Guid Id { get; set; } public string Name { get; set; } = null!; public int BlogId { get; set; } // Discovered as a reference navigation: public Blog Blog { get; init; } = null!; } -->[!code-csharpReferenceNavigations]
For these types, Blog.Author and Author.Blog are discovered as reference navigations. On the other hand, the following properties are not discovered as reference navigations:
Blog.Id, because int is a mapped primitive typeBlog.Title, because 'string` is a mapped primitive typeBlog.Uri, because Uri is automatically converted to a mapped primitive typeBlog.ConsoleKeyInfo, because ConsoleKeyInfo is a C# value typeBlog.DefaultAuthor, because the property does not have a setterAuthor.Id, because Guid is a mapped primitive typeAuthor.Name, because 'string` is a mapped primitive typeAuthor.BlogId, because int is a mapped primitive typeA property of an entity type is discovered as a collection navigation when:
IEnumerable<TEntity>, where TEntity is, or could be, an entity type. This means that the type of TEntity:
For example, in the following code, both Blog.Tags and Tag.Blogs are discovered as collection navigations:
[!code-csharpCollectionNavigations]
Once a navigation going from, for example, entity type A to entity type B is discovered, it must next be determined if this navigation has an inverse going in the opposite direction--that is, from entity type B to entity type A. If such an inverse is found, then the two navigations are paired together to form a single, bidirectional relationship.
The type of relationship is determined by whether the navigation and its inverse are reference or collection navigations. Specifically:
Discovery of each of these types of relationship is shown in the examples below:
A single, one-to-many relationship between Blog and Post is discovered by pairing the Blog.Posts and Post.Blog navigations:
[!code-csharpOneToManySingleRelationship]
A single, one-to-one relationship is discovered between Blog and Author is discovered by pairing the Blog.Author and Author.Blog navigations:
[!code-csharpOneToOneSingleRelationship]
A single, many-to-many relationship is discovered between Post and Tag is discovered by pairing the Post.Tags and Tag.Posts navigations:
[!code-csharpManyToManySingleRelationship]
[!NOTE] This pairing of navigations may be incorrect if the two navigations represent two, different, unidirectional relationships. In this case, the two relationships must be configured explicitly.
Pairing of relationships only works when there is a single relationship between two types. Multiple relationships between two types must be configured explicitly.
[!NOTE] The descriptions here are in terms of relationships between two different types. However, it is possible for the same type to be on both ends of a relationship, and therefore for a single type to have two navigations both paired with each other. This is called a self-referencing relationship.
Once the navigations for a relationship have either been discovered or configured explicitly, then these navigations are used to discover appropriate foreign key properties for the relationship. A property is discovered as a foreign key when:
<navigation property name><principal key property name><navigation property name>Id<principal entity type name><principal key property name><principal entity type name>Id[!TIP] The "Id" suffix can have any casing.
The following entity types show examples for each of these naming conventions.
Post.TheBlogKey is discovered as the foreign key because it matches the pattern <navigation property name><principal key property name>:
[!code-csharpNavigationPrincipalKeyFKName]
Post.TheBlogID is discovered as the foreign key because it matches the pattern <navigation property name>Id:
[!code-csharpNavigationIdFKName]
Post.BlogKey is discovered as the foreign key because it matches the pattern <principal entity type name><principal key property name>:
[!code-csharpPrincipalTypePrincipalKeyFKName]
Post.Blogid is discovered as the foreign key because it matches the pattern <principal entity type name>Id:
[!code-csharpPrincipalTypeIdFKName]
[!NOTE] In the case of one-to-many navigations, the foreign key properties must be on the type with the reference navigation, since this will be the dependent entity. In the case of one-to-one relationships, discovery of a foreign key property is used to determine which type represents the dependent end of the relationship. If no foreign key property is discovered, then the dependent end must be configured using
HasForeignKey. See One-to-one relationships for examples of this.
The rules above also apply to composite foreign keys, where each property of the composite must have a compatible type with the corresponding property of the primary or alternate key, and each property name must match one of the naming conventions described above.
EF uses the discovered navigations and foreign key properties to determine the cardinality of the relationship together with its principal and dependent ends:
If EF has determined the dependent end of the relationship but no foreign key property was discovered, then EF will create a shadow property to represent the foreign key. The shadow property:
By convention, required relationships are configured to cascade delete. Optional relationships are configured to not cascade delete.
Many-to-many relationships do not have principal and dependent ends, and neither end contains a foreign key property. Instead, many-to-many relationships use a join entity type which contains pairs of foreign keys pointing to either end of the many-to-many. Consider the following entity types, for which a many-to-many relationship is discovered by convention:
<!-- public class Post { public int Id { get; set; } public ICollection<Tag> Tags { get; } = new List<Tag>(); } public class Tag { public int Id { get; set; } public ICollection<Post> Posts { get; } = new List<Post>(); } -->[!code-csharpManyToManySingleRelationship]
The conventions used in this discovery are:
<left entity type name><right entity type name>. So, PostTag in this example.
<navigation name><principal key name>. So, in this example, the foreign key properties are PostsId and TagsId.
<principal entity type name><principal key name>.PostsId and TagsId.This results in the following EF model:
Model:
EntityType: Post
Properties:
Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
Skip navigations:
Tags (ICollection<Tag>) CollectionTag Inverse: Posts
Keys:
Id PK
EntityType: Tag
Properties:
Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
Skip navigations:
Posts (ICollection<Post>) CollectionPost Inverse: Tags
Keys:
Id PK
EntityType: PostTag (Dictionary<string, object>) CLR Type: Dictionary<string, object>
Properties:
PostsId (no field, int) Indexer Required PK FK AfterSave:Throw
TagsId (no field, int) Indexer Required PK FK Index AfterSave:Throw
Keys:
PostsId, TagsId PK
Foreign keys:
PostTag (Dictionary<string, object>) {'PostsId'} -> Post {'Id'} Cascade
PostTag (Dictionary<string, object>) {'TagsId'} -> Tag {'Id'} Cascade
Indexes:
TagsId
And translates to the following database schema when using SQLite:
CREATE TABLE "Posts" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT);
CREATE TABLE "Tag" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Tag" PRIMARY KEY AUTOINCREMENT);
CREATE TABLE "PostTag" (
"PostsId" INTEGER NOT NULL,
"TagsId" INTEGER NOT NULL,
CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostsId", "TagsId"),
CONSTRAINT "FK_PostTag_Posts_PostsId" FOREIGN KEY ("PostsId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_PostTag_Tag_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tag" ("Id") ON DELETE CASCADE);
CREATE INDEX "IX_PostTag_TagsId" ON "PostTag" ("TagsId");
By convention, EF creates a database index for the property or properties of a foreign key. The type of index created is determined by:
For a one-to-many relationship, a straightforward index is created by convention. The same index is created for optional and required relationships. For example, on SQLite:
CREATE INDEX "IX_Post_BlogId" ON "Post" ("BlogId");
Or on SQL Server:
CREATE INDEX [IX_Post_BlogId] ON [Post] ([BlogId]);
For an required one-to-one relationship, a unique index is created. For example, on SQLite:
CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");
Or on SQL Sever:
CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]);
For optional one-to-one relationships, the index created on SQLite is the same:
CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");
However, on SQL Server, an IS NOT NULL filter is added to better handle null foreign key values. For example:
CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]) WHERE [BlogId] IS NOT NULL;
For composite foreign keys, an index is created covering all the foreign key columns. For example:
CREATE INDEX "IX_Post_ContainingBlogId1_ContainingBlogId2" ON "Post" ("ContainingBlogId1", "ContainingBlogId2");
[!NOTE] EF does not create indexes for properties that are already covered by an existing index or primary key constraint.
Indexes have overhead, and, as asked here, it may not always be appropriate to create them for all FK columns. To achieve this, the ForeignKeyIndexConvention can be removed when building the model:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
}
When desired, indexes can still be explicitly created for those foreign key columns that do need them.
By convention foreign key constraints are named FK_<dependent type name>_<principal type name>_<foreign key property name>. For composite foreign keys, <foreign key property name> becomes an underscore separated list of foreign key property names.