This content relates to a pre-release version of Entity Framework (EF6)
For more information, see Future Versions
When using Code First, your classes are mapped to the database using a set of conventions. The default Code First Conventions determine things like which property becomes the primary key of an entity, the name of the table an entity maps to, and what precision and scale a decimal column has by default.
Sometimes these default conventions are not ideal for your model, and you have to work around them by configuring many individual entities using Data Annotations or the Fluent API. Custom Code First Conventions let you define your own conventions that provide configuration defaults for your model. In this walkthrough, we will explore the different types of custom conventions and how to create each of them.
Let's start by defining a simple model that we can use with our conventions. Add the following classes to your project.
class ProductContext : DbContext
{
static ProductContext()
{
Database.SetInitializer(
new DropCreateDatabaseIfModelChanges<ProductContext>());
}
public DbSet<Product> Products { get; set; }
}
public class Product
{
public int Key { get; set; }
public string Name { get; set; }
public decimal? Price { get; set; }
public DateTime? ReleaseDate { get; set; }
}
The simplest type of custom convention is called a lightweight convention. To define one of these, you filter to the thing you want to configure, then configure them. Let’s write a convention that configures any property named Key to be the primary key for its entity type.
Conventions are enabled on the model builder which can be accessed by overriding OnModelCreating in the context. Add the following to the ProductContext class.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Properties()
.Where(p => p.Name.EndsWith("Key"))
.Configure(p => p.IsKey());
}
Now, any property in our model named Key will be configured as the entity’s primary key.
Lightweight conventions are the main way to define a convention. Our hope is that, for most projects, lightweight conventions will be all that you need. Of course, if a convention is too complex to be expressed as a lightweight convention, you can always drop down to the lower-level building blocks of the Custom Code First Conventions feature. The rest of this article describes these lower-level building blocks.
You can see more Lightweight Conventions in the Further Examples section of this page.
Lightweight conventions are built on top of something called configuration conventions. This type of convention lets you configure entities and properties using objects that are similar to the ones used in the Fluent API.
By default, the SQL Server Entity Framework provider maps DateTime properties to columns of type datetime. Let’s create a convention that maps these properties to datetime2 columns instead.
To create a configuration convention, define a class that inherits from IConfigurationConvention. Add the following class to your project.
class DateTimeColumnTypeConvention :
IConfigurationConvention<PropertyInfo, DateTimePropertyConfiguration>
{
public void Apply(
PropertyInfo propertyInfo,
Func<DateTimePropertyConfiguration> configuration)
{
// If ColumnType hasn't been configured...
if (configuration().ColumnType == null)
{
configuration().ColumnType = "datetime2";
}
}
}
You may have noticed that, with configuration conventions, we have the power to override things that have already been configured. Because of this, we need to check that the column type has not already been configured before we set it.
To enable a configuration convention, add it to the model builder’s convention list. Add the following statement inside of ProductContext’s OnModelCreating method.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// (Existing code here)
modelBuilder.Conventions.Add<DateTimeColumnTypeConvention>();
}
The IConfigurationConvention interface has two type parameters: TMemberInfo and TConfiguration. These are used to filter the model elements that your convention applies to.
The first type parameter, TMemberInfo, can be either of the following.
The second one, TConfiguration, can be any of the following.
Any combination is valid except for Type and PropertyConfiguration (or one of its derived types) which will result in Apply never being called. If you specify PropertyInfo and EntityTypeConfiguration then Apply will be called with the configuration for the type that the property is defined on.
One great use of configuration conventions is to enable new attributes to be used when configuring a model. To illustrate this, let’s create an attribute that we can use to mark String properties as non-Unicode.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
class NonUnicodeAttribute : Attribute
{
}
Now, let’s create a configuration convention to apply this attribute to our model. Instead of implementing IConfigurationConvention directly, we are going to inherit from AttributeConfigurationConvention, an abstract class that includes all of the logic required to look for attributes applied to types and properties.
class NonUnicodeAttributeConvention :
AttributeConfigurationConvention<PropertyInfo, StringPropertyConfiguration, NonUnicodeAttribute>
{
public override void Apply(
PropertyInfo propertyInfo,
StringPropertyConfiguration configuration,
NonUnicodeAttribute attribute)
{
if (configuration.IsUnicode == null)
{
configuration.IsUnicode = false;
}
}
}
With these classes added to our project and the convention enable on our model, we could add the NonUnicode attribute to the Name property on Product. Instead of being mapped to an nvarchar column, Name would then be mapped to a column of type varchar.
The final type of convention, model-based, is the most powerful. This type of convention allows you to directly manipulate the model metadata that gets used by the Entity Framework. The interfaces used to create these conventions are IEdmConvention, IDbConvention, and IDbMappingConvention.
Let’s create a convention to set the default Scale of decimal properties. Add the following class to your project.
class DecimalScaleConvention : IEdmConvention<EdmProperty>
{
public void Apply(EdmProperty property, EdmModel model)
{
if (property.PrimitiveType.PrimitiveTypeKind == PrimitiveTypeKind.Decimal
&& property.Scale == null)
{
property.Scale = 4;
}
}
}
If you were to enable this convention on a model builder using the regular Add method, none of the decimal properties would end up having a Scale of four. This is because DecimalPropertyConvention, one of Code First’s default conventions, gets applied before ours and sets the Scale before we have a chance to configure it.
To adjust the order in which our convention gets applied, we can use the AddBefore and AddAfter methods. Add the following to ProductContext’s OnModelCreating method.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// (Existing code here)
modelBuilder.Conventions.AddBefore<DecimalPropertyConvention>(
new DecimalScaleConvention());
}
Now our convention will be applied before the Scale gets set by DecimalPropertyConvention.
Both IEdmConvention and IDbConvention take a single type parameter. This determines the kind of model element that gets passed to Apply.
Some common types that you can use with IEdmConvention are:
A couple of common ones to use with IDbConvention are:
As we saw with DecimalScaleConvention, the order in which a convention gets applied is significant. Conventions are applied in order according to their type. That order is as follows.
Because custom conventions could be affected by the default Code First conventions, it may be worth knowing what the default conventions are included in the Entity Framework. All the default conventions, and a brief description of what they do can be found on the following MSDN page. System.Data.Entity.ModelConfiguration.Conventions Namespace
Also, you can disable any of the default conventions that you do not want applied to your model. To disable a convention, use the Remove method. Here is an example of removing the PluralizingTableNameConvention.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// (Existing code here)
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
Here we will look at examples of lightweight conventions that achieve the same results as the other conventions we just looked at. All the code samples in this section will go inside your OnModelCreating event, but we will not show the event signature.
The following code will configure all DateTime properties to map to the datetime2 type in the database.
modelBuilder.Properties<DateTime>()
.Configure(c => c.HasColumnType("datetime2"));
As you can see, there is much less boilerplate code in the lightweight conventions.
The next example is to create a custom attribute that makes all marked properties non-unicode. If we already have the attribute created earlier in the walkthrough, then we can write the following code.
modelBuilder.Properties()
.Where(x => x.GetCustomAttributes().OfType<NonUnicodeAttribute>().Any())
.Configure(c => c.IsUnicode(false));
This code will give us the same behaviour as the convention we saw earlier, but with lightweight conventions there is another feature that can make custom attributes easier to work with.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
internal class IsUnicode : Attribute
{
public bool Unicode { get; set; }
public IsUnicode(bool isUnicode)
{
Unicode = isUnicode;
}
}
If our attribute has parameters that we want to be able to use later on, such as this attribute that specifies a bool to set unicode to true or false, then you can use the Having method in lightweight conventions.
modelBuilder.Properties()
.Having(x => x.GetCustomAttributes<IsUnicode>().FirstOrDefault())
.Configure((c, a) => c.IsUnicode(a.Unicode));
Any properties that return null in the Having will not be configured. For those properties that do not return null the object returned will be passed as second parameter to the configure method. This gives a nice strongly typed experience in the configure method, as well as not needing to check for null anywhere.
Lastly we will look at configuring entitites instead of properties. The experience is similar to the conventions we have seen so far, but the options inside configure will be at the entity instead of property level. The following both tell EF that the database will generate values for our Timestamp property, but one uses the generic version of the entities method. This code assumes an interface exists with a property called Timestamp that we want to be generated by the database, an example might be a trigger that sets the timestamp column to the current date and time when a change is made.
modelBuilder.Entities<ITrackedEntity>()
.Configure(c => c.Property(p => p.Timestamp)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed));
modelBuilder.Entities()
.Where(e => e.IsAssignableFrom(typeof(ITrackedEntity)))
.Configure(c => c.Property("Timestamp")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed));
As you can see, when you use the generic versions of entities you can get strongly typed properties in your configure method instead of relying on strings.
Hopefully by now, you have a good idea of what Code First Conventions are and how to create your own custom ones. There are different types of conventions, each one with its own tradeoffs between simplicity and power. Conventions get applied in a specific order, and that order can have a significant impact on the final model.
As with everything else in the prerelease, we eagerly await any feedback you may have about the Custom Code First Conventions feature. Please let us know what kinds of custom conventions you are creating, what problems you’re running into, and what aspects of the feature you really like.