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
.
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.
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:
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.
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!
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.
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:
One little thing to mention is that you can exclude a property form being mapped by using the Ignore
method:
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.
[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");
});
And that would create a one-to-one relationship between Customer
and the newly generated, CustomerAddress
.
The final thing here is that you can create a one-to-many relationship by using 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 also need 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> Address { get; set; }
}
The result is as follows:
History
- 21st March, 2021: Initial version