Conventions for Code First

 

The latest preview of Code First allows you to describe a model using C# or VB.Net classes. The basic shape of the model is detected by convention and then a fluent API can be used to further refine your model.

We recently posted about our plans to support Data Annotations as another way to further describe your model. We are now looking at extending and improving the conventions that initially infer the shape of the model. This post describes the conventions we are planning to include.

Conventions are designed to provide a starting point for a model. Data Annotations or the fluent API can then be used to further describe the model or change what was detected by convention. Precedence is given to configuration via the fluent API followed by Data Annotations then convention.

Primary Key

Previously Code First would infer that a property is a primary key if the property is called ‘Id’ or ‘<class name>Id’. The only change to this convention is that once primary key properties are detected if their type is ‘int’, ‘long’ or ‘short’, they are registered as identity columns in the database by default. Primary key detection is not case sensitive.

Relationship Inverses

When defining a relationship between two types it is common to include a navigation property on both types, such as in the following example:

public class Product

{

public int ProductId { get; set; }

public string Name { get; set; }

public Category Category { get; set; }

}

public class Category

{

public int CategoryId { get; set; }

public string Name { get; set; }

public ICollection<Product> Products { get; set; }

}

Previously Code First would create two separate relationships between Product and Category but will now infer that Product.Category and Category.Products represent different ends of the same relationship. Inverse detection will occur when both types involved in the relationship define one and only one navigation property (reference to or collection) to each other. If one of the types in a relationship defines two or more navigation properties that reference the other type then inverse detection will not occur and the relationships will need to be manually configured using Data Annotations or the fluent API.

Foreign Keys

Building on the previous convention it is also common to include a foreign key property on the dependent end of a relationship, in this case BookReview.SubjectISBN:

 

 

public class BookReview

{

    public int Id { get; set; }

    public Book Subject { get; set; }

    public string SubjectISBN { get; set; }

}

public class Book

{

    [Key]

    public string ISBN { get; set; }

    public string Name { get; set; }

    public ICollection<BookReview> Reviews { get; set; }

}

Code First will now infer that any property named ‘<navigation property name><primary key property name>’ (i.e. SubjectISBN), ‘<principal class name><primary key property name>’ (i.e. BookISBN) or ‘<primary key property name>’ (i.e. ISBN), with the same data type as the primary key, represents a foreign key for the relationship. If multiple matches are found then precedence is given in the order listed above. Foreign key detection will not be case sensitive.

When a foreign key property is detected Code First will also infer the multiplicity of the relationship based on the nullability of the foreign key, if the property is nullable then the relationship is registered as optional, otherwise the relationship is registered as required, and cascade delete is turned on. The multiplicity and cascade delete behavior detected by convention can be overridden using the fluent API.

Type Discovery

Previously Code First would only include types that were declared in object sets on your derived context or manually registered through the fluent API.  Given the following example Product would have been included in your model, but Category would not:

 

 

public class ProductContext : ObjectContext

{

    public ProductContext(EntityConnection connection)

        : base(connection)

    { }

    public ObjectSet<Product> Products

    {

        get { return base.CreateObjectSet<Product>(); }

    }

}

public class Product

{

    public int Id { get; set; }

    public string Name { get; set; }

    public Category Category { get; set; }

}

public class Category

{

    public int Id { get; set; }

    public string Name { get; set; }

    public ICollection<Product> Products { get; set; }

}

Code First will now recognize that Product has a property that references Category and automatically include Category in your model. Reachability is recursive, so if Category then referenced another unregistered type, this would also be included in the model.  Reachability will also follow references to types defined in another assembly. The convention may include types that do not belong in the model; these can be removed using the StoreIgnore Data Annotation or via the fluent API as follows:

 

 

var builder = new ContextBuilder<ProductContext>();

builder.Ignore<Category>();

Complex Type Discovery

Building on the reachability convention, if Code First discovers a class definition where a primary key cannot be inferred, and no primary key is registered through Data Annotations or the fluent API, then the type will be automatically registered as a complex type. Complex type detection also requires that the type does not have properties that reference entity types and is not referenced from a collection property on another type. Given the following class definitions Code First would infer that Name is a complex type because it has no primary key:

 

 

public class Person

{

    public int PersonId { get; set; }

    public Name Name { get; set; }

}

public class Name

{

    public string Title { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

}

Summary

Code First will provide an extended set of default conventions to determine the shape of a model. These conventions can be overridden using Data Annotations which in turn can be overridden via the fluent API.

We’d like to hear any feedback you have on the set of conventions as well as the rules associated with each convention.

 

 

Rowan Miller
Program Manager
ADO.Net Entity Framework Team