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:

  • Intercept the model builder and modify the model with the fluent API
  • Add attributes to our class model

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:

  • Made it an identity column (i.e. auto-incremental column)
  • Made it required (ie non-nullable)
  • Mapped it to the column TheOrderID

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:

  • Intellisense will help you
  • You’ll have compile-time error-checking
  • The property type is known and influence what you can do with the property afterwards (for instance, only string property can be min-max length)

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:

  • The table will be in the dbo schema
  • OrderID is the primary key but isn’t auto-incremental
  • All other columns are nullable
  • The string columns are of length 128 (will be MAX at RTM)

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):

  • Table name override isn’t supported with annotations
  • Minimum length isn’t supported in OnModelCreated
  • Regular Expression isn’t supported in OnModelCreated

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:

  • Use annotations to enrich the model with validation rules
  • Use OnModelCreated to put db-only constraints (key, auto-incremented columns, table-name override, column-type override, etc.)

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

About these ads

9 thoughts on “Entity Framework 4.1: Override conventions (2)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s