Entity Framework 6 Conventions

Published on: Author: Michael

I was incredibly excited when conventions were finally made public in EF6.  A convention allows you to set up a policy that a model will follow.  For example EF comes with a convention that tables are plural by entities are singular.  EF has supported conventions for a while but the necessary public interface was not exposed until EF6.  In this post I’m going to walk through creating a simple convention.

Data Annotations

A data annotation is an attribute that you can apply to a model property to control how it is treated.  EF ships with several data annotations including ColumnAttribute, KeyAttribute and StringLengthAttribute.  Data annotations are used in many places in the framework including validation and binding.  In fact the above annotations are actually part of the core framework and not EF.  Annotations make it simple to add information to a model. 

Unfortunately EF will ignore annotations it does not recognize.  In some cases you can derive from an existing annotation and EF will work.  For example it is common to use type aliases in SQL for commonly used column types.  If a type alias is defined to limit a string to a specific length then StringLengthAttribute can be used to create a new data annotation to represent that. 

The following code defines a maximum string length of 50 to match a type alias in SQL.  Since it derives from an annotation that EF recognizes it will be used.

public class NameDefinition : StringLengthAttribute
{
    public NameDefinition () : base(50)
    {  }
}

Here is how it might be used in a model.

[Column("RoleName")]
[NameDefinition]
public string Name { get; set; }

Data Configurations

When data annotations will not work you have to revert to a data configuration type that is registered with the model.  The configuration type can modify the model even more than the data annotations but it does require an extra type and that you register it.

For example EF assumes strings are Unicode by default.  In most databases this is recommended but there are cases where it is not true.  There is no data annotation that identifies the character set of a string so a data configuration has to be used.  Here is how you might define an ANSI string.

internal class UserConfiguration : EntityTypeConfiguration<User>
{
    public UserConfiguration ()
    {
        Property(x => x.Name).IsUnicode(false);
    }
}

Just defining the type is not sufficient because EF doesn’t know about it.  Prior to EF 6 you would have had to override the model creation of the context and add the configuration like so.

protected override void OnModelCreating ( DbModelBuilder modelBuilder )
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Configurations.Add(new UserConfiguration());           
}

This was a pain for maintenance so many developers created a helper method to find all such types using reflection.  Fortunately EF6 added such a method.

protected override void OnModelCreating ( DbModelBuilder modelBuilder )
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Configurations.AddFromAssembly(this.GetType().Assembly);
}

The above code will load all the data configurations in the given assembly.

Conventions

As the number of custom annotations and configurations grow the above solutions start to become problematic.  Ideally we want to be able to define a custom annotation and then have that annotation applied to a model at model creation.  Ideally we could derive from some base “model annotation” attribute and EF would recognize the attribute but this isn’t possible with the existing annotations.  But EF does support the concept of user-defined conventions. 

A user-defined convention is a rule that you can define for a model or portions thereof.  EF already has a couple but starting with EF6 we can define our own.  Going back to the ANSI string of earlier it might be necessary to make all strings of a model ANSI.  Rather than adding a configuration for each model that has string properties we can define a convention that sets all strings to ANSI.

public class AnsiStringConvention : Convention
{
    public AnsiStringConvention ()
    {
        Properties<string>().Configure(c => c.IsUnicode(false));
    }
}

Registering the convention is as simple as this.

protected override void OnModelCreating ( DbModelBuilder modelBuilder )
{
    base.OnModelCreating(modelBuilder);

    //Set up conventions
    modelBuilder.Conventions.Add(new Conventions.AnsiStringConvention());            

    //Register configurations
    //modelBuilder.Configurations.AddFromAssembly(this.GetType().Assembly);
}

For one-off conventions you can actually just apply the convention at model creation rather than creating a custom type but I find the custom type to be cleaner.

Custom Annotations

Even with conventions there will be occasions when a convention needs to be violated.  For example maybe all the strings in a database are Unicode (or ANSI) but a couple are not.  It would be nice to be able to make the convention smarter to handle these exceptions.  Data annotations are a great way to handle this. 

Here is a simple data annotations that identifies a string as either Unicode (the default) or ANSI.

[AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
public class UnicodeAttribute : Attribute
{
    public UnicodeAttribute ( ) : this(true)
    { }

    public UnicodeAttribute ( bool isUnicode )
    {
        IsUnicode = isUnicode;
    }

    public bool IsUnicode { get; private set; }
}

The attribute can now be applied to the properties of a model. 

[Unicode]
public string Name { get; set; }

By itself it does nothing so we need to create a new convention that detects this attribute and applies it accordingly. 

public class CharSetConvention : Convention
{
    public CharSetConvention ()
    {
        //For each string property with a Unicode attribute, set the property to use the attribute's value
        Properties<string>()
            .Having(p => p.GetCustomAttributes(false).OfType<UnicodeAttribute>())
            .Configure((c,a) =>
            {
                if (a.Any())
                    c.IsUnicode(a.First().IsUnicode);
            });
    }
}

This convention gets all string properties with the custom annotation.  It then applies the appropriate configuration to the property.

Ordering

One issue to be aware of with conventions is ordering.  Conventions may conflict and therefore the order in which they are applied is important.  EF follows a last convention wins approach.  The Conventions property allows you to order conventions relative to each other if needed.  With our new annotation and convention we can run into this issue.  The charset convention needs to be applied after the ANSI string convention.

Summary

Conventions are going to eliminate the need for data configurations in many cases.  Custom conventions will allow developers to create annotations for common database properties like type aliases and column defaults.  Conventions can be used to configure more than just model properties though.  Refer to MSDN for examples on how to use conventions to control other aspects of model creation as well.