Configuring Entity Framework 4 ‘CodeFirst’

In this post I’m going to cover configuring Entity Framework “CodeFirst”.

  1. Custom column name
  2. Custom storage type
  3. Custom table name
  4. Data constraints
  5. Ignore a property
  6. Turning cascade delete off
  7. One to one mapping
  8. Inheritance Mapping

    1. Table per class heirarchy
    2. Table per type
    3. Table per concrete type
  9. Conclusion

Out of the box, EF 4 will apply a number of conventions to an entity model to derive an appropriate physical data model. For example, it will be assumed that a property of type int with a name that is the same as the class name suffixed with Id represents the primary key of the underlying data table.

It doesn’t take too much variance from what is expected before EF 4 is not able to imply the appropriate datamodel, and that is where explicit model definition is required.

EF 4 has two methods for defining the model – either using declarative attributes, or using Configuration classes. Configuration classes derive from EntityTypeConfiguration<TEntity>, and are added to the modelBuilder of the DbContext during model creation.

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){}
}

public class ProductContext : DbContext{
  public DbSet<product> Products { get; set; }
  protected override void OnModelCreating(ModelBuilder modelBuilder){
    modelBuilder.Configurations.Add(new ProductConfiguration());
  }
}

Today I’m going to examine a few common scenarios that you’ll probably come up against some time or other when using EF 4 Code First, and how you might approach them using both attributes and Configuration classes.

Unconventional Key Name

Consider the class Product:

public class Product{
  public int ProductCode { get; set; }
  public string Name { get; set; }
}

EF 4 will not be able to build a DBSet<Product> – it will complain that a key has not been defined (because there is no property called “PropertyId”).

Using Attributes, we can decorate the ProductCode property with the System.ComponentModel.DataAnnotations.KeyAttribute.

public class Product{
[Key]
public int ProductCode { get; set; }
public string Name { get; set; }
}

Or alternatively we could use the Configuration class (ensuring that the configuration class is added to the modelBuilder Configurations in OnModelCreating of the DbContext).

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    this.HasKey(m =>m.ProductCode);
  }
}

Either of these approaches will work.

Custom Column Name

Code naming conventions may differ from database naming conventions, so it may be neccesary to specify a custom column name for a property.

Using the System.Component.DataAnnotations.ColumnAttribute, we can specify the column name as follows:

 

[Column(Name="ProductName")]
public string Name { get; set; }

Using configuration, we can specify the column name using the HasColumName fluent API:

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    this.Property(m => m.Name).HasColumnName("ProductName");
  }
}

It is possible to have a custom name for the Key column also. Simply add a ColumnAttribute in addition to the KeyAttribute, or in the Fluent API add another specification:

 
public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    this.HasKey(m => m.ProductCode);
    this.Property(m => m.ProductCode).HasColumnName("ProductID");
   
    ....
  }
}

The default names for foreign keys can be changed also.

With foreign keys, they can either be exposed in the model or not. When I say “exposed in the model”, it means there is an additional property on the dependent entity that is the value of the principal entity. In the code below, the property PrimaryCategoryCode is an exposed foreign key of Primary Category.

public string PrimaryCategoryCode { get; set; }
public virtual Category PrimaryCategory { get; set; }

To indicate this, use the ForeignKey attribute on the PrimaryCategory property as follows:

public string PrimaryCategoryCode { get; set; }

[ForeignKey("PrimaryCategoryCode")]
public virtual Category PrimaryCategory { get; set; }

You can also use the Fluent API as follows:

 
public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    ....
    this.HasRequired(m => m.PrimaryCategory).WithMany().HasForeignKey(p=>p.PrimaryCategoryCode);   
    ....
  }
}

Note the use of the empty WithMany() API to indicate the relationship is navigable in just one direction (Product->Category).

It’s possible to customise the name of the foreign key column using the Column attribute as I’ve already covered.

Where the foreign key is not exposed on the model (say the property “ProductCatgoryCode” was not on the Product class), then the situation is a bit more complex, and I think a custom column name can only be configured using the Fluent API as follows:

 
this.HasRequired(m => m.PrimaryCategory)
                .WithMany()
                .IsIndependent()
                .Map(m => m.MapKey(p => p.CategoryCode, "MainCategory"));

Note the use of the IsIndependent() API to indicate that the foreign key column is not exposed on the Product entity. In this case the foreign key coulmn in the database would be called "MainCategory".

Custom storage type

It is common to want to store a decimal as a money type. EF 4 will not do this automatically. We can again the ColumnAttribute to specify the desired store type as follows (where the store type is a string that matches a SqlServer datatype).


public class Product{
  ....
  [Column(TypeName="money", Name="PurchasePrice")]
  public decimal BaseCost { get; set; }
  ....
}

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    this.Property(m => m.BaseCost).HasColumnName("PurchasePrice").HasColumnType("money");
  }
}

Data Constraints

DataAnnotation attributes such as Required or StringLength will be applied to the applicable columns. They can also be applied using the Fluent API as follows:

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    this.Property(m => m.Name)
        .HasColumnName("ProductName")
        .IsRequired()
        .HasMaxLength(50);
.....
  }
}

Ignore a property

Is not uncommon to have properties that you do NOT want persisted. You can declaratively specify this using the System.ComponentModel.DataAnnotations.NotMapped attribute, or use the fluent API as follows:

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    this.Ignore(m=>m.CurrentUserName);
	.....
  }
}

Custom table name

Use the class-level System.ComponentModel.DataAnnotations.Table attribute to specify a custom storage location for an entity:

[Table("tblProduct")]
public class Product{
....
}

Or use the Fluent API as follows:

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    this.ToTable("tblProduct");
.....
  }
}

There is an overload in both cases to enable the specification of a schema name.

Turning Cascade Delete Off

This seems only possible using the Fluent API as follows:

public class CategoryConfiguration : EntityTypeConfiguration<category>{
  public CategoryConfiguration(){
    this.HasMany(c => c.Products)
        .WithRequired(p=>p.PrimaryCategory)
        .WillCascadeOnDelete(false);           
  }
}

One to One relationships

Generally a bad idea, but sometimes you have them. It seems they can only be represented using the Fluent API. The result in the database one table's primary key is a foreign key into the other table.

Where the relationship is optional at one end, then the mapping is pretty strightforward as follows:

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    ...
    this.HasOptional(p => p.Note).WithRequired(n => n.Product);
    ...       
  }
}

Will result in the Notes table having a non-identity primary key.

Where both ends of the relationship are either required or optional, additional hints need to be supplied to indicate which table in the database is the primary key holder, and which is the foreign key holder.

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    ....
    this.HasRequired(p => p.Note).WithRequiredPrincipal(n => n.Product);
  }
}

The WithRequiredPrincipal Fluent API indicates that the Product table is the primary key table. Alternatively, using the WithRequiredDependent would mean the Notes table was the primary key table.

Inheritance mapping

Table Per Heirarchy mapping

In TPH, all derived instances of a base class are stored in the same table, with a column acting as a discriminator.

All Mapable subclasses must be defined using the Fluent API as follows:

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    this.Map<product>(m=>m.Requires("Type").HasValue("Current"));
    this.Map<discontinuedproduct>(m => m.Requires("Type").HasValue("Old"));
  }
}

This will result in a column called “Type” being generated in the database, even though it does not exist in the object model. When an instance of "DiscontinuedProduct" is saved, the value "Old" will be saved into the column Type.

Table Per Type

In TPT a base set of properties are saved in a table, and properties specific to derived classes are kept in separate tables joined by foreign keys.

To implement TPC, simply specify a different table to store the sub class in as follows:

public class ProductConfiguration : EntityTypeConfiguration<product>{
  public ProductConfiguration(){
    this.ToTable("Products");
  }
}

public class DiscontinuedProductsConfiguration: EntityTypeConfiguration<discontinuedproduct>{
  public DiscontinuedProductsConfiguration<p align="center"></p>(){
    this.ToTable("DiscontinuedProducts");
  }
}

This will result into tables being created in the database.

Table per Concrete Type

In TPC, there is a base abstract class that all concrete classes inherit from. Each of classes are stored in their own table. It does not seem possible to do TPC where the base class is non-abstract and has storage.

I going to create a very contrived code mode:


 public abstract class Product{ public int ProductId { get; set; } public string Name { get; set; } } public class TaxableProduct : Product{ public string TaxCode{get;set;} } public class UnTaxedProduct : Product{ public string Reason{get;set;} } 

Becuase the tables are being persisted to different tables, separate configurations are required.


 public class UnTaxedProductConfiguration : EntityTypeConfiguration<untaxedproduct>{ public UnTaxedProductConfiguration(){ this.Map(m => m.MapInheritedProperties()).ToTable("Untaxed"); this.HasKey(m => m.ProductId); this.Property(m => m.ProductId).HasDatabaseGenerationOption(DatabaseGenerationOption.Identity); } } public class TaxableProductProductConfiguration : EntityTypeConfiguration<taxableproduct>{ public TaxableProductProductConfiguration(){ this.Map(m => m.MapInheritedProperties()).ToTable("taxed"); this.HasKey(m => m.ProductId); this.Property(m => m.ProductId).HasDatabaseGenerationOption(DatabaseGenerationOption.Identity); } } 

Conclusion

I've covered a lot of scenarios here. It can be seen that many peristence specifications can be made either declaratively as attributes or using special configuration classes.

I would normally avoid decorating entity classes with persistance specifications such as column name, table name, store type or ignore, preferring instead to use a buddy configuration class and the model builder. To use of attributes couples the entity to it's persistence, which seem to me to be defeating the whole point of having an ORM in the first place.

Entity Framework still feels like it has a ways to go compared to say NHibernate, particularly in the the specification of discriminator for sub clsses and the use of custom types, but it has come a long way.

I was interested to see today that Orchard, the new CMS recently published by Microsoft, is backed by NHibernate instead of Entity Framework. To my mind that shows amazing pragmatism on the part of Micrsoft, and in no dampens my enthusiasm for Entity Framework - I am sure it's going to get even better down the track.

kick it on DotNetKicks.com

About these ads
This entry was posted in Entity Framework, NHibernate and tagged , , . Bookmark the permalink.

7 Responses to Configuring Entity Framework 4 ‘CodeFirst’

  1. Bats Ihor says:

    Thanks, very useful information.

  2. ntziolis says:

    Actually there is a way to Ignore a property by attribute its called ‘NotMapped’

  3. Franz says:

    As you mentioned Orchard, it’s worth to have a look on Dotnetage. It uses EF – Code First.

  4. 评测 says:

    That was some instructive writing!

  5. sfmskywalker says:

    Nice post. Have you ever tried mapping a inherited property? Because I would be glad to hear that works, as I am getting the error:

    “The property ‘UserName’ is not a declared property on type ‘Advertiser’. Verify that the property has not been explicitly excluded from the model by using the Ignore method or NotMappedAttribute data annotation. Make sure that it is a valid primitive property. ”

    My model looks like this:

    abstract class Entity { public int Id {get; set; }}
    abstract class User : Entity { public string UserName {get; set;} }
    sealed class Advertiser : User

    My AdvertisementConbfiguration class looks like this:

    class AdvertiserConfiguration : EntityTypeConfiguration
    {
    public AdvertiserConfiguration()
    {
    // the following line indirectly causes an InvalidOperationException:
    Property( x => x.UserName ).HasMaxLength(50);
    }
    }

    If I would change the Advertiser class so that it does not inherit from User (and pull the UserName property down) then it works great.

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