Handling child collections in Entity Framework

Configuring Entity Framework through fluent API sometimes is quite hard. In this article I am showing how to configure it to handle entity's child collections that those properly gets added and removed from collection.

Sample model

Here is sample model which to configure. It has root entity - Order and collection of OrderItems.

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

    public virtual ICollection<OrderItem> Items { get; set; }
}

public class OrderItem
{
    public int Id { get; set; }
}

And here is DB Context for this model.

public class ModelDbContext : DbContext
{
    public virtual DbSet<Order> Orders { get; set; }
}

With this model in place I want an Order to be responsible for child collection - when I save Order also OrderItems should be saved, when I remove item from collection and save Order, removed item should be deleted in database.

With default configuration it only adds items - creates new OrderItem in DB and links it to Order. But when you remove item from collection it just removes relationship between Order and OrderItem. OrderItem table in DB will still contain removed OrderItem, but with NULL in OrderId column.

Solution

Solution is to define composite key for OrderItem which would consist of OrderItem's Id and foreign key to Order - OrderId. Now OrderId column is automatically generated in database and we do not have it in our model. So first of all we have to add OrderId to OrderItem.

public class OrderItem
{
    public int Id { get; set; }
    public int OrderId { get; set; }
}

Now we have to configure composite key. It is easy with Data Annotations, but I do not want my model to depend on Entity Framework directly. So I will do it using fluent API.

Fluent API configuration has to be done in DbConext's overriden method OnModelCreating.

public class ModelDbContext : DbContext
{
    public virtual DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    }
}

First of all configure foreign key relationship between Order and OrderItem.

modelBuilder.Entity<Order>()
    .HasMany(o => o.OrderItems)
    .WithOptional()
    .HasForeignKey(oi => oi.OrderId)

Next define composite key for OrderItem using anonymous object. Also configure Id column values to be auto-generated by database, because it does not generate those for composite keys.

modelBuilder.Entity<OrderItem>()
    .HasKey(oi => new {oi.Id, oi.OrderId})
    .Property(oi => oi.Id)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

And here is complete DB context.

public class ModelDbContext : DbContext
{
    public virtual DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>()
            .HasMany(o => o.OrderItems)
            .WithOptional()
            .HasForeignKey(oi => oi.OrderId);
        modelBuilder.Entity<OrderItem>()
            .HasKey(oi => new { oi.Id, oi.OrderId })
            .Property(oi => oi.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    }
}

Now you can add/remove items from Order and items will get persisted/deleted in database.

// Adding new item
using(var context = new ModelDbContext())
{
    var order = context.Orders.Find(10);
    order.Items.Add(new OrderItem());
    context.SaveChanges();
}

// Removing item
using (var context = new ModelDbContext())
{
    var order = context.Orders.Find(10);
    var orderItem = order.Items.First();
    order.Items.Remove(orderItem);
    context.SaveChanges();
}

// Removing all items
using (var context = new ModelDbContext())
{
    var order = context.Orders.Find(10);
    order.Items.Clear();
    context.SaveChanges();
}

NOTE When you want to remove all items from collection use Clear method on collection. If instead you assign new collection, it will not remove items and leaves them.