Entity Framework 4.1: Override conventions (2)

This is part of a series of blog post about Entity Framework 4.1.  The past blog entries are:

In this article, I’ll cover how to over conventions.

We’ve seen that EF 4.1 Code-First infer the mapping between the model (classes) and the tables according to conventions.  When those conventions aren’t to our liking, we have two different ways to override the conventions:

In future release, we’ll also be able to add / remove conventions, but this feature has been removed on 4.1 (it was present in the CTPs).

As a basis for example, I’ll use the following Order class:

public class Order
{
    public int OrderID { get; set; }
    public string OrderTitle { get; set; }
    public string CustomerName { get; set; }
    public DateTime TransactionDate { get; set; }
}

Let’s start with the model builder.  In order to use the model builder, we must override a method on our db-context class:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    //    Map schemas
    modelBuilder.Entity<Order>().ToTable("efdemo.Order");
}

By default EF maps an entity to a table having the same name in the database schema dbo.  Here I do map the entity Order to the table efdemo.Order.

The model builder exposes a fluent API.  Basically that means that you can chain most operations on the API because the methods are always returning the same object.  Therefore, we could chain operations like this:

modelBuilder.Entity<Order>().Property(x => x.OrderID)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
                .IsRequired()
                .HasColumnName("TheOrderID");

Here we did three things with the property OrderID:

Here we can mention one of the powerful feature of EF 4.1:  the way we single out properties.  Instead of using a string to identify the property, we use a lambda expression.  This has several advantages:

So what would convention would we typically want to override?  Well, if we look at our Order class, by default we would have the following conventions applied that do not suit me:

We can override those convention using the model builder:

//    Map schemas
modelBuilder.Entity<Order>().ToTable("efdemo.Order");
//    Identity Column
modelBuilder.Entity<Order>().Property(x => x.OrderID)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
//    String columns
modelBuilder.Entity<Order>().Property(x => x.OrderTitle)
                .IsRequired()
                .HasMaxLength(64);
modelBuilder.Entity<Order>().Property(x => x.CustomerName)
                .IsRequired()
                .HasMaxLength(32);
//    Date Columns
modelBuilder.Entity<Order>().Property(x => x.TransactionDate)
                .IsRequired();

This is quite verbose but it has the advantage of leaving the model untouched, purely POCO.

Now, the other way to override conventions is to add attributes to the classes.

This approach is much less verbose and feels more natural since the information about a property are on a property.  The only issue is that your class isn’t POCO anymore, although it isn’t EF either:  the attributes you use to decorate your classes are from System.ComponentModel.DataAnnotations, the .NET validation module, and are therefore not coupled to EF.

Let’s give an example:

public class Order
{
    public int OrderID { get; set; } 

    [Required]
    [StringLength(32, MinimumLength = 2)]

    public string OrderTitle { get; set; }

      [Required]
    [StringLength(64, MinimumLength = 5)]

    public string CustomerName { get; set; }

      [Required]

    public DateTime TransactionDate { get; set; }
}

Here we told the model builder to map OrderTitle to a non-nullable nvarchar of size 32.  The minimum size of 2 isn’t used to create the schema mapping but it will be used for validation.

The example I just gave puts business annotation.  But you could put technical annotations too:

public class Order
{

  [Key]
  [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  public int OrderNumber { get; set; }

    …
}

Here we force the OrderNumber property to be the primary key of the table and we also specify that it’s an auto-incremental column.

You can therefore use either OnModelCreated or annotations to override conventions.  Some minor restrictions apply, for instance (the ones I’ve noticed):

Those are quite understandable:  OnModelCreated supports only what pertains to table-mapping (reg-ex nor minimum length don’t influence db-schemas) while annotations support mostly validations.

For me the guideline I’m using is:

This way your model gets richer and remains POCO.  You can reuse your model elsewhere and benefit from the validation rules.


Leave a comment