EF Feature CTP5: Fluent API Samples

 


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.

For Mapping with the Fluent API see https://msdn.com/data/jj591617

For Configuring Relationships with the Fluent API see https://msdn.com/data/jj591620


 

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.

Code First provides a Fluent API that can be used to further configure a model, this post will provide a series of short samples of using the Fluent API.

NOTE: The Fluent API is a more advanced concept and this post assumes you have an understanding of the concepts detailed in the Code First Into.

The Model

The samples shown in this post all make use of the following model:

 public class ProductContext : DbContext
{
    public DbSet<Category> Categories { get; set; }
    public DbSet<Product> Products { get; set; }
    public DbSet<Tag> Tags { get; set; }
    public DbSet<Person> People { get; set; }
    public DbSet<Order> Orders { get; set; }
    public DbSet<OrderLine> OrderLines { get; set; }
    public DbSet<OrderLineNote> OrderLineNotes { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // TODO: Use Fluent API Here 
    }
}

public class Category
{
    public string CategoryCode { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Product> Products { get; set; }
}

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

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

    public string SecondaryCategoryCode { get; set; }
    public virtual Category SecondaryCategory { get; set; }


    
    public virtual ICollection<Tag> Tags { get; set; }
}

public class DiscontinuedProduct : Product
{
    public DateTime DiscontinuedDate { get; set; }
}
  
public class Tag
{
    public string TagId { get; set; }

    
    public virtual ICollection<Product> Products{ get; set; }
}


public class Person
{
    public int PersonId { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }

    public ICollection<Order> Orders { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

public class Order
{
    public int OrderId { get; set; }
    public DateTime OrderDate { get; set; }

    public ICollection<OrderLine> Lines { get; set; }
    public Person Person { get; set; }
}

public class OrderLine
{
    public int OrderId { get; set; }
    public int ProductId { get; set; }
    public int Quantity { get; set; }

    public Product Product { get; set; }
    public ICollection<OrderLineNote> Notes { get; set; }
}

public class OrderLineNote
{
    public int OrderId { get; set; }
    public int ProductId { get; set; }
    public string Note { get; set; }

    public OrderLine OrderLine { get; set; }
}

 

Primary Keys

Simple primary key:

modelBuilder.Entity<Category>()

.HasKey(c => c.CategoryCode);

Composite primary key:

modelBuilder.Entity<OrderLine>()

.HasKey(l => new { l.OrderId, l.ProductId });

 

Properties

Make a clr-nullable property required:

modelBuilder.Entity<Product>()

.Property(p => p.Name)

.IsRequired();

Change string length:

modelBuilder.Entity<Product>()

.Property(p => p.Name)

.HasMaxLength(50);

Switch off Identity:

modelBuilder.Entity<Product>()

.Property(p => p.ProductId)

.HasDatabaseGenerationOption(DatabaseGenerationOption.None);

Ignore a property:

modelBuilder.Entity<Person>()

.Ignore(p => p.Name);

 

Types

Specify a type is a complex type:

modelBuilder.ComplexType<Address>();

Ignore a type:

modelBuilder.Ignore<Person>();

 

Relationships

Standard one to many:

modelBuilder.Entity<Product>()

.HasRequired(p => p.PrimaryCategory)

.WithMany(c => c.Products)

.HasForeignKey(p => p.PrimaryCategoryCode);

The same relationship can also be configured from the other end (this has the same effect as the above code):

modelBuilder.Entity<Category>()

.HasMany(c => c.Products)

.WithRequired(p => p.PrimaryCategory)

.HasForeignKey(p => p.PrimaryCategoryCode);

Relationship with only one navigation property:

modelBuilder.Entity<OrderLine>()

.HasRequired(l => l.Product)

.WithMany()

.HasForeignKey(l => l.ProductId);

Switch cascade delete off:

modelBuilder.Entity<Category>()

.HasMany(c => c.Products)

.WithRequired(p => p.PrimaryCategory)

.HasForeignKey(p => p.PrimaryCategoryCode)

.WillCascadeOnDelete(false);

Relationship with composite foreign key:

modelBuilder.Entity<OrderLineNote>()

.HasRequired(n => n.OrderLine)

.WithMany(l => l.Notes)

.HasForeignKey(n => new { n.OrderId, n.ProductId });

 

Rename foreign key not exposed in object model:

modelBuilder.Entity<Order>()

.HasRequired(o => o.Person)

.WithMany(p => p.Orders)

.IsIndependent()

.Map(m => m.MapKey(p => p.PersonId, "CustomFkToPersonId")); 

 

Rename columns in many:many table:

modelBuilder.Entity<Product>()
.HasMany(p => p.Tags)

.WithMany(t => t.Products)

.Map(m =>

{

m.MapLeftKey(p => p.ProductId, "CustomFkToProductId");

m.MapRightKey(t => t.TagId, "CustomFkToTagId");

}); 

Table & Column Mapping

Change column name:

modelBuilder.Entity<Category>()

.Property(c => c.Name)

.HasColumnName("cat_name");

Change table name:

modelBuilder.Entity<Category>()

.ToTable("MyCategories");

Change table name with schema:

modelBuilder.Entity<Category>()

.ToTable("MyCategories", "sales");

 

Inheritance Table Mapping

Table Per Hierarchy (TPH)

TPH = “Store all my data in one table and use the values from one or more columns to identify which type each row is”

Simple TPH is the default mapping for an inheritance hierarchy.

TPH with custom discriminator column name and values:

modelBuilder.Entity<Product>()

.Map<Product>(m => m.Requires("Type").HasValue("Current"))

.Map<DiscontinuedProduct>(m => m.Requires("Type").HasValue("Old"));

Table Per Type (TPT)

TPT = “store all the data for properties on the base type in a single table, store any additional data for derived types in an extra table that has a foreign key to the base table”

modelBuilder.Entity<Product>().ToTable("Products");

modelBuilder.Entity<DiscontinuedProduct>().ToTable("OldProducts");

Table Per Concrete Class (TPC)

TPC = “Create a completely separate table for each non-abstract type in my hierarchy”

modelBuilder.Entity<Product>().ToTable("Products");

modelBuilder.Entity<DiscontinuedProduct>()

.Map(m =>

{

m.MapInheritedProperties();

m.ToTable("OldProducts");

});

 

Summary

In this post we looked at a number of samples for the Code First Fluent API, any scenarios that are commonly asked about in our forum will be added to this post over time.

Feedback & Support

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

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

 

Rowan Miller

Program Manager

ADO.NET Entity Framework