Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Reducing Complexity using Entity Framework Core Owned Types

4.56/5 (4 votes)
21 Mar 2021CPOL2 min read 6.4K  
Group fields that you do not want to appear as a reference, in a separate type using EF Core owned typed
In this post, we will take a look at EF Core’s owned types that allow you to group fields which you do not want to appear as a reference, in a separate type.

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

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 by an example. Suppose you have this model. You have Customer and Order and both have fields for an address.

C#
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 reference that table in your Customer and Order tables.

C#
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 and remove its key field and include a reference in Customer and Order as follows:

C#
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.

Image 1

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.

C#
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!

Image 2

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

The answer is in Fluent API again.

C#
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:

Image 3

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

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

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

C#
[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:

C#
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");
});

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

Image 4

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

C#
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  base.OnModelCreating(modelBuilder);

  modelBuilder.Entity<Customer>().OwnsMany(a => a.Address);
  modelBuilder.Entity<Order>().OwnsOne(a => a.Address);
}

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

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

  public List<Address> Address { get; set; }
}

The result is as follows:

Image 5

History

  • 21st March, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)