Reducing Complexity Using Entity Framework Core Owned Types

I came across a very nice feature of Entity Framework Core that I would like to share with you. It is the owned type.

EF Core’s owned types allow you to group fields that you do not want to appear as a reference, in a separate type.

Let us start with an example. Suppose you have this model. You have Customer and Order and both have fields for an address.

class Customer
{
    [Key]
    public long CustomerID { get; set; }
    public string Name { get; set; }

    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

class Order
{
    [Key]
    public long OrderID { get; set; }
    public bool IsShipped { get; set; }

    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

You can easily refactor the previous model by extracting the address fields to a new table and referencing that table in your Customer and Order tables.

class Address
{
    [Key]
    public long AddressID { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

But what if you want to keep the three address fields inside Customer and Order, and you do want to refactor the code and reduce complexity and duplication, what you are going to do? The answer is in owned entities.

You can update your Address class remove its key field and include a reference in Customer and Order as follows.

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

class Customer
{
    [Key]
    public long CustomerID { get; set; }
    public string Name { get; set; }

    public Address Address { get; set; }
}

class Order
{
    [Key]
    public long OrderID { get; set; }
    public bool IsShipped { get; set; }

    public Address Address { get; set; }
}

Finished? Not yet. You cannot leave your code like this as no entity can be defined without a primary key. Actually, you will get this error if you try to apply your changes to the database.

Database

The next update that we need to do is in the data context itself. We can use the Fluent API to give a hint of how our model will work.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Customer>().OwnsOne(a => a.Address);
    modelBuilder.Entity<Order>().OwnsOne(a => a.Address);
}

Now we can apply our model to the database and watch the magic happen.

Model

Our update looks very promising; however, we have very little issue here, no one likes those “Address_” prefixes.

The answer is in Fluent API again.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Customer>().OwnsOne(a => a.Address, add =>
    {
        add.Property(p => p.Street).HasColumnName(nameof(Address.Street));
        add.Property(p => p.City).HasColumnName(nameof(Address.City));
        add.Property(p => p.State).HasColumnName(nameof(Address.State));
    });
    modelBuilder.Entity<Order>().OwnsOne(a => a.Address);
}

After applying the previous code, you can see the difference between the two generated tables.

Generated tables

One little thing to mention is that you can exclude a property from being mapped by using the Ignore method.

add.Ignore(p => p.State);

If you do not like the Fluent API approach, you can simply decorate the Address class with the OwnedAttribute attribute, however, this will not allow you to customize field names and other properties.

[Owned]
class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

Another thing to mention is that you can create a table for the owned entity using the ToTable method.

modelBuilder.Entity<Customer>().OwnsOne(a => a.Address, add =>
{
    add.Property(p => p.Street).HasColumnName(nameof(Address.Street));
    add.Property(p => p.City).HasColumnName(nameof(Address.City));
    add.Property(p => p.State).HasColumnName(nameof(Address.State));
    add.ToTable("CustomerAddress");
});

That would create a one-to-one relationship between the Customer and the newly generated, CustomerAddress.

 CustomerAddress

The final thing here is that you can create a one-to-many relationship by using the OwnsMany method.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Customer>().OwnsMany(a => a.Address);
    modelBuilder.Entity<Order>().OwnsOne(a => a.Address);
}

You need also to update the Customer entity to match the one-to-many relationship.

class Customer  
{  
    [Key]  
    public long CustomerID { get; set; }  
    public string Name { get; set; }  
    public List<Address> Addresses { get; set; }  
}  

The result is as follows.

Orders


Similar Articles