Partager via


EF Feature CTP5: Pluggable Conventions

 


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.

The pluggable conventions feature was removed from the EF4.1 release. You can track this feature at https://entityframework.codeplex.com/workitem/61.


 

We have released Entity Framework Feature Community Technology Preview 5 (CTP5) . Feature CTP5 contains a preview of new features that we are planning to release as a stand-alone package in Q1 of 2011 and would like to get your feedback on. Feature CTP5 builds on top of the existing Entity Framework 4 (EF4) functionality that shipped with .NET Framework 4.0 and Visual Studio 2010 and is an evolution of our previous CTPs.

If you aren’t familiar with Code First then you should read the Code First Walkthrough before tackling this post.

Code First includes a set of simple, model-wide behaviors that provide sensible configuration defaults for the parts of your model that have not been explicitly configured using Data Annotations or the Fluent API. These default behaviors are referred to as Conventions. One of the most commonly requested features is the ability to add custom conventions or switch off some of the default conventions. Based on your feedback we’ve bumped up the priority of this feature and CTP5 includes an early preview of “Pluggable Conventions”.

 

Limitations (Please Read)

The functionality included in CTP5 is an early preview that was included to get your feedback. There are a number of rough edges and the API surface is likely to change for the RTM release.

The Pluggable Conventions feature we have implemented so far allows you to perform configuration based on information obtained by reflection from the types and members in your model. It does not allow you to read information about the shape of your model. This does impose some restrictions; for example you can-not read the model to find out if a given property is a foreign key. These restrictions are something we will remove in the future but not before the initial RTM in 2011.

 

The Model

Here is a simple console application that uses Code First to perform data access. If we run the application we’ll get an exception because Code First can’t find a property to use as the primary key for the two entities. This is because the primary key properties don’t conform to the default conventions.

 using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Database;
 namespace ConventionSample
{
    class Program
    {
        static void Main(string[] args)
        {
            DbDatabase.SetInitializer(
                new DropCreateDatabaseIfModelChanges<BlogContext>());

            using (var ctx = new BlogContext())
            {
                ctx.Blogs.Add(new Blog {  Name = "Test Blog" });
                ctx.SaveChanges();

                foreach (var blog in ctx.Blogs)
                {
                    System.Console.WriteLine("{0}: {1}", blog.BlogKey, blog.Name);
                }
            }
        }
    }

    public class BlogContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
    }

    public class Blog
    {
        public int BlogKey { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Post> Posts { get; set; }
    }

    public class Post
    {
        public int PostKey { get; set; }
        public string Title { get; set; }
        public string Abstract { get; set; }
        public string Content { get; set; }

        public int BlogKey { get; set; }
        public virtual Blog Blog { get; set; }
    }
}
  

Our First Custom Convention

We could just use DataAnnotations or the Fluent API to configure the primary key for each of these entities but if we have a larger model this is going to get very repetitive. It would be better if we could just tell Code First that any property named ‘<TypeName>Key’ is the primary key. We can now do this by creating a custom convention:

 using System;
using System.Data.Entity.ModelConfiguration.Configuration.Types;
using System.Data.Entity.ModelConfiguration.Conventions.Configuration;
 public class MyKeyConvention :
IConfigurationConvention<Type, EntityTypeConfiguration>
{
    public void Apply(Type typeInfo, Func<EntityTypeConfiguration> configuration)
    {
        var pk = typeInfo.GetProperty(typeInfo.Name + "Key");
        if (pk != null)
        {
            configuration().Key(pk);
        }
    }
}
  
 We then register our convention by overriding OnModelCreating in our derived context:
 public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Add<MyKeyConvention>();
    }
}
  

A Closer Look at IConfigurationConvention

IConfigurationConvention allows us to implement a convention that is passed some reflection information and a configuration object.

Why Not Just IConvention?

You may be wondering why it’s not just IConvention? As mentioned earlier this first part of pluggable conventions allows you to write simple conventions using reflection information but does not allow you to read any information about the model. Later on (after our first RTM) we intend to expose the other half of our pluggable convention story that allows you to read from the model and perform much more granular changes.

Why Func<EntityTypeConfiguration>?

We want to avoid needlessly creating configurations for every type and property in your model, the configuration is lazily created when you decide to use it. We know the use of Func isn’t ideal and we’ll have something cleaner ready for RTM.

Am I restricted to Type and EntityTypeConfiguration?

No, the generic parameters are pretty flexible and Code First will call your convention with any combination of MemberInfo/Configuration that it finds in the model. For example you could specify PropertyInfo and StringPropertyConfiguration and Code First will call your convention for all string properties in your model:

 using System;
using System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive;
using System.Data.Entity.ModelConfiguration.Conventions.Configuration;
using System.Reflection;

public class MakeAllStringsShort :
    IConfigurationConvention<PropertyInfo, StringPropertyConfiguration>
{
    public void Apply(PropertyInfo propertyInfo, 
        Func<StringPropertyConfiguration> configuration)
    {
        configuration().MaxLength = 200;
    }
}

The first generic can be either of the following:

  • Type (System)
  • PropertyInfo (System.Reflection)

The second generic can be any of the following:

  • ModelConfiguration (System.Data.Entity.ModelConfiguration.Configuration)

  • EntityTypeConfiguration (System.Data.Entity.ModelConfiguration.Configuration.Types)

  • PropertyConfiguration (System.Data.Entity.ModelConfiguration.Configuration.Properties)

    • PrimitivePropertyConfiguration (System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive)

      • DateTimePropertyConfiguration (System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive)

      • DecimalPropertyConfiguration (System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive)

      • LengthPropertyConfiguration (System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive)

        • StringPropertyConfiguration (System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive)
        • BinaryPropertyConfiguration (System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive)

Important: Take note of the namespace included after each type. The Code First API currently includes types with the same names in other namespaces. Using these types will result in a convention that compiles but is never called. If the namespace ends in .Api then you have the wrong namespace. This is a rough edge that we will address before we RTM.

Most combinations are valid, however specifying Type and PropertyConfiguration (or any type derived from PropertyConfiguration) will result in the convention never being called since there is never a PropertyConfiguration for a Type. If you were to specify PropertyInfo and EntityTypeConfiguration then you will be passed the configuration for the type that the property is defined on. For example we could re-write our primary key convention as follows:

 using System;
using System.Data.Entity.ModelConfiguration.Configuration.Types;
using System.Data.Entity.ModelConfiguration.Conventions.Configuration;
using System.Reflection;

public class MyKeyConvention :
    IConfigurationConvention<PropertyInfo, EntityTypeConfiguration>
{
    public void Apply(PropertyInfo propertyInfo, Func<EntityTypeConfiguration> configuration)
    {
        if (propertyInfo.Name == propertyInfo.DeclaringType.Name + "Key")
        {
            configuration().Key(propertyInfo);
        }
    }
}
 

More Examples

Make all string properties in the model non-unicode:

 using System;
using System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive;
using System.Data.Entity.ModelConfiguration.Conventions.Configuration;
using System.Reflection;

public class MakeAllStringsNonUnicode :
    IConfigurationConvention<PropertyInfo, StringPropertyConfiguration>
{
    public void Apply(PropertyInfo propertyInfo, 
        Func<StringPropertyConfiguration> configuration)
    {
        configuration().IsUnicode = false;
    }
}
  
  
 
 

Ignore properties that conform to a common pattern:

 
 using System;
using System.Data.Entity.ModelConfiguration.Configuration.Types;
using System.Data.Entity.ModelConfiguration.Conventions.Configuration;
using System.Reflection;

public class IgnoreCommonTransientProperties :
    IConfigurationConvention<PropertyInfo, EntityTypeConfiguration>
{
    public void Apply(PropertyInfo propertyInfo, 
        Func<EntityTypeConfiguration> configuration)
    {
        if (propertyInfo.Name == "LastRefreshedFromDatabase"
            && propertyInfo.PropertyType == typeof(DateTime))
        {
            configuration().Ignore(propertyInfo);
        }
    }
}
  
  
 Ignore all types in a given namespace. 
 using System;
using System.Data.Entity.ModelConfiguration.Configuration;
using System.Data.Entity.ModelConfiguration.Conventions.Configuration;

public class IgnoreAllTypesInNamespace :
    IConfigurationConvention<Type, ModelConfiguration>
{
    private string _namespace;

    public IgnoreAllTypesInNamespace(string ns)
    {
        _namespace = ns;
    }

    public void Apply(Type typeInfo, 
        Func<ModelConfiguration> configuration)
    {
        if (typeInfo.Namespace == _namespace)
        {
            configuration().Ignore(typeInfo);
        }
    }
}

Because this convention does not have a default constructor we need to use a different overload of ModelBuilder.Conventions.Add.

 public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Add(
            new IgnoreAllTypesInNamespace("ConventionSample.UnMappedTypes"));
    }
}
  
  
 Use TPT mapping for inheritance hierarchies by default:
 using System;
using System.Data.Entity.ModelConfiguration.Configuration.Types;
using System.Data.Entity.ModelConfiguration.Conventions.Configuration;

public class TptByDefault :
    IConfigurationConvention<Type, EntityTypeConfiguration>
{
    public void Apply(Type typeInfo, 
        Func<EntityTypeConfiguration> configuration)
    {
        configuration().ToTable(typeInfo.Name);
    }
}
  
  
 

Use a custom [NonUnicode] attribute to identify properties that should be non-unicode, note the use of the AttributeConfigurationConvention helper class that is included in CTP5.

 using System;
using System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive;
using System.Data.Entity.ModelConfiguration.Conventions.Configuration;
using System.Reflection;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class NonUnicodeAttribute : Attribute
{ }

public class ApplyNonUnicodeAttribute
    : AttributeConfigurationConvention<PropertyInfo, StringPropertyConfiguration, NonUnicodeAttribute>
{

    public override void Apply(PropertyInfo propertyInfo, StringPropertyConfiguration configuration, NonUnicodeAttribute attribute)
    {
        configuration.IsUnicode = false;
    }
}

Removing Existing Conventions

As well as adding in your own conventions you can also disable any of the default conventions. For example you may wish to disable the convention that configures integer primary keys to be identity by default:

 public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>();
    }
}

The conventions that can be removed are:

Namespace: System.Data.Entity.ModelConfiguration.Conventions.Edm

  • AssociationInverseDiscoveryConvention

    Looks for navigation properties on classes that reference each other and configures them as inverse properties of the same relationship.

  • ComplexTypeDiscoveryConvention

    Looks for types that have no primary key and configures them as complex types.

  • DeclaredPropertyOrderingConvention

    Ensures the primary key properties of each entity precede other properties.

  • ForeignKeyAssociationMultiplicityConvention

    Configures relationships to be required or optional based on the nullability of the foreign key property, if included in the class definition.

  • IdKeyDiscoveryConvention

    Looks for properties named Id or <TypeName>Id and configures them as the primary key.

  • NavigationPropertyNameForeignKeyDiscoveryConvention

    Looks for a property to use as the foreign key for a relationship, using the <NavigationProperty><PrimaryKeyProperty> pattern.

  • OneToManyCascadeDeleteConvention

    Switches cascade delete on for required relationships.

  • OneToOneConstraintIntroductionConvention

    Configures the primary key as the foreign key for one:one relationships.

  • PluralizingEntitySetNameConvention

    Configures the entity set name in the Entity Data Model to be the pluralized type name.

  • PrimaryKeyNameForeignKeyDiscoveryConvention

    Looks for a property to use as the foreign key for a relationship, using the <PrimaryKeyProperty> pattern.

  • PropertyMaxLengthConvention

    Configures all String and byte[] properties to have max length by default.

  • StoreGeneratedIdentityKeyConvention

    Configure all integer primary keys to be Identity by default.

  • TypeNameForeignKeyDiscoveryConvention

    Looks for a property to use as the foreign key for a relationship, using the <PrincipalTypeName><PrimaryKeyProperty> pattern.

Namespace: System.Data.Entity.ModelConfiguration.Conventions.Edm.Db

  • ColumnOrderingConvention

    Applies any ordering specified in [Column] annotations.

  • ColumnTypeCasingConvention

    Converts any store types that were explicitly configured to be lowercase. Some providers, including MS SQL Server and SQL Compact, require store types to be specified in lower case.

  • PluralizingTableNameConvention

    Configures table names to be the pluralized type name.

Namespace: System.Data.Entity.ModelConfiguration.Conventions.Configuration

These conventions process the data annotations that can be specified in classes. For example if you wanted to stop Code First from taking notice of the StringLength data annotation you could remove the StringLengthAttributeConvention.

  • ColumnAttributeConvention
  • ComplexTypeAttributeConvention
  • ConcurrencyCheckAttributeConvention
  • DatabaseGeneratedAttributeConvention
  • ForeignKeyAttributeConvention
  • InversePropertyAttributeConvention
  • KeyAttributeConvention
  • MaxLengthAttributeConvention
  • NotMappedPropertyAttributeConvention
  • NotMappedTypeAttributeConvention
  • RequiredNavigationPropertyAttributeConvention
  • StringLengthAttributeConvention
  • TableAttributeConvention
  • TimestampAttributeConvention

Namespace: System.Data.Entity.Database

  • IncludeMetadataInModel

    Adds the EdmMetadata table, used by DbContext to check if the database schema matches the current model.

 

Summary

In this post we covered the Pluggable Conventions feature that is included in EF Feature CTP5. This is an early preview and still has a number of rough edges but we included it because we really want your feedback.

As always we would love to hear any feedback you have by commenting on this blog post.

For support please use the Entity Framework Pre-Release Forum.

Rowan Miller

Program Manager

ADO.NET Entity Framework

Comments

  • Anonymous
    January 10, 2011
    I can wait for a CTP6 before RTM? I have a trouble with Security Exception =/ social.msdn.microsoft.com/.../316a043a-98b0-4b91-b68f-723d588dad53 Another important question. I never get a answer: Can we expect that Entity Framework(the core) is unbound .Net Framework in future? Something likes Enums need Core updates, and wait for a .Net Framework is too long time. Something like ASP.NET MVC would be great. MVC 3 will be released 7 months after MVC 2. I'm not asking to be Open Source (would be perfect). Sorry for bad english

  • Anonymous
    January 10, 2011
    Hi Rowan, I hope that for RTM the RIA Services team will be able to read information about the shape of your model so that it can automatically decorate properties with the Key and Association attributes required by RIA Services by implementing a DomainServiceDescriptionProvider. It would otherwise seem silly having to manually add these attributes for the sake of RIA Services when EF Code First can derive these configurations by convention. Mathew Charles mentioned in this thread here social.msdn.microsoft.com/.../57793bec-abc6-4520-ac1d-a63e40239aed that we can expect this in the future. However, after reading about the limitations in your post above about the RTM not allowing you to read information about the shape of your model, I wonder if the RIA Services team will be able to provide this with the RTM of EF Code First. Or can we read information about the model from ((IObjectContextAdapter)DbContext).DataContext.MetadataWorkspace? Can you tell me what we can expect for RTM regarding first class RIA Services support? Thanks Remco

  • Anonymous
    January 10, 2011
    Hi Rowan, I've been using the EF4 CTP5 and I could see that he has no way to update the database schema. I do not want it to delete the database, but update in accordance with the upgrades in my Model. It´s possible in EF4 CTP5 version ??? Thank´s

  • Anonymous
    January 10, 2011
    The Entity method on ModelConfiguration is marked internal, making it impossible to map any simple entity types, while the complex method is public. Is this intentional? I need to call this method to be able to implement a schema tracking table similar to the IncludeMetadataConvention.

  • Anonymous
    January 14, 2011
    Will this (or any other feature in this CTP) allow us to use managed types in the database, such as the spatial types included in SQL Server (or managed types we develop ourselves)?

  • Anonymous
    January 16, 2011
    I am using IgnoreAllTypesInNamespace as following and yet I see below error while reading employee from context. Any Suggestions ? modelBuilder.Conventions.Add(new IgnoreAllTypesInNamespace(("e10.Objects"))); The mapping of CLR type to EDM type is ambiguous because multiple CLR types match the EDM type 'Employee'. Previously found CLR type 'e10.Data.Employee', newly found CLR type 'e10.Objects.Employee'.

  • Anonymous
    January 18, 2011
    It would be nice to see an example mapping a naming convention like ProductID = product_id, BrandName = brand_name, etc...

  • Anonymous
    January 27, 2011
    The comment has been removed

  • Anonymous
    February 20, 2011
    This is great stuff - I love the ability to have simple POCO classes easily persistable. However, my preference is to have Entity Set names in the DB to be Singular... No problem you think, just remove the PluralizingTableNameConvention! But it doesn't work (when you set it to create the database) - it still creates pluralized Entity Set names. I have managed to get around it by adding a convention: public class SingularEntitySetNameConvention : IConfigurationConvention<Type, EntityTypeConfiguration> { public void Apply(Type memberInfo, Func<EntityTypeConfiguration> configuration) { configuration().ToTable(memberInfo.Name); } } ... and adding it in in the OnModelCreating override; but the PluralizingTableNameConvention will hopefully fixed for RTM. Cheers, and keep up the good work  :-D

  • Anonymous
    April 18, 2011
    I'm having trouble creating a custom convention using v4.1 RTM.  It appears that IConfigurationConvention is internal...and DbModelBuilder.Conventions does not have a public Add method. If this functionality was removed from the RTM...would you please put a Big Darn Notice at the top of the article so peeps don't waste their time on this. BTW, I am very bummed conventions were removed.  They appeared to be the perfect answer to an issue I was struggling with.  Hopefully, there will be a v4.2 that puts them back in.  :)

  • Anonymous
    February 06, 2012
    Agree with Doug Clutter. This feature is very compelling, and since it's not released with the RTW, this post should include a big red announcement on top of it that the info here wasn't release. Very disapointing indeed, worked for half an hour making zillions of attributes now nothing works. I hate the fluent API. I like attributes. Clean code. Here is an awsome workaround meanwhile: dotnetspeak.com/.../custom-conventions-in-entity-framework-code-first-v-4-1 Please vote here: data.uservoice.com/.../1155529-ability-to-plug-in-custom-conventions-for-schema-g