Mastering owned entities in EF Core: Cleaner complex types
Not all data in your application should live as a standalone table with its own ID and lifecycle. Sometimes you need a tightly coupled dependent object that exists alongside its parent, like a movie's budget, a survey's questions, or a customer's address. If you had the magic to handle this data differently, you could save tons of lines and reduce Entity Framework Core (EF Core) overhead. The good news is that EF Core offers owned entities that do this. It reduces the complexities of your domain model. In this post, I will show you how to use owned entity types to maintain dependent data.

What is an owned entity?
In EF Core, an owned entity is a special type of entity that exists only within another entity and cannot stand alone in the database. Unlike other EF models, owned entities do not have their own identity, such as a primary key. Owned entities inherit the identity of their parent table. The lifecycle of owned entities also depends on the parent entity. When the owning entity is created, owned properties are made, when it is deleted, all of its owned properties are also deleted.
Key characteristics of EF Core's owned properties
EF Core owned entities possess the following characteristics:
- Owned entity types do not have a separate identity. Instead, they depend on their parents' identity.
- An owned entity's lifecycle is tied to its owner entity and cannot exist independently of it. It is created, updated, and deleted with its parent entity.
- They are stored in the same table as their owner by default. Although we create a separate class for them, they do not go to a separate table and are saved with their class prefix, like
Address_Street. - An owned entity does not have a separate
DbSetsand cannot be queried independently. - An owned entity always has a one-to-one relation with its owner entity
- You cannot reference an owned entity with another entity via a foreign key relation.
- EF change tracker tracks owned entities along with their owners, not as separate entities.
- Can encapsulate complex data types such as coordinates, addresses, and contact details.
How to use an owned entity in EF Core
I am creating a console application to see how we can implement an owned entity in an EF Core-based project.
Step 1: Create a console application
Run the following in the CLI
dotnet new console -o MovieProjectionDemo
Step 2: Install all required packages
For the project, we need to run the command to install the required NuGet packages.
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.JsonI will be using a Postgres database, so installing Npgsql.EntityFrameworkCore.PostgreSQL You can install the NuGet package accordingly, as per your preferred database.
Step 3: Add models
namespace OwnedEntityDemo.Models;
public class Movie
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public int ReleaseYear { get; set; }
public string Genre { get; set; } = string.Empty;
public Budget Budget { get; set; } = null!;
}
public class Budget
{
public decimal ProductionCost { get; set; }
public decimal MarketingCost { get; set; }
public decimal DistributionCost { get; set; }
public string Currency { get; set; } = string.Empty;
}Here, the Budget object will be part of the Movie entity. Note, I did not create any ID in the budget as it relies on its owner, the Movie entity.
Step 4: Set up the DbContext
using Microsoft.Extensions.Configuration;
using OwnedEntityDemo.Models;
using Microsoft.EntityFrameworkCore;
namespace OwnedEntityDemo;
public class AppDbContext: DbContext
{
public DbSet<Movie> Movies => Set<Movie>();
private readonly string _connectionString;
public AppDbContext()
{
// Simple reading from appsettings.json
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
_connectionString = config.GetConnectionString("PostgresConnection");
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(_connectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Movie>()
.OwnsOne(c => c.Budget, bugdet =>
{
bugdet.Property(a => a.ProductionCost);
bugdet.Property(a => a.DistributionCost);
bugdet.Property(a => a.MarketingCost);
bugdet.Property(a => a.Currency);
});
}
}One DbSet is registered. Also, you need to configure the Budget entity as an owned entity with OwnsOne in the OnModelCreating method.
Step 5: Create appsettings.json
The console app does not contain an appsettings file by default. So I have created one with the connection string:
{
"ConnectionStrings": {
"PostgresConnection": "Host=localhost;Port=5432;Database=tvDb;Username=postgres;Password=4567"
}
}
Step 6: Configure appsettings in csproj
By default, a console app will expect the file in the bin directory. To read newly added appsettings from the root directory, add the following inside the <Project> tag of the application's project file:
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>Step 7: Prepare the database
To create the table in the database, create and run the migration:
dotnet ef migrations add InitialCreate
dotnet ef database updateOur database is now ready


Notice how columns of owned entities are named as a prefix of the owned class name.
Step 8: Prepare Program.cs to write the data
We will seed the data by initializing it in the Program.cs file:
using Microsoft.EntityFrameworkCore;
using OwnedEntityDemo;
using OwnedEntityDemo.Models;
using var context = new AppDbContext();
// Make sure database exists
await context.Database.EnsureCreatedAsync();
// Add a movie with budget
var movie1 = new Movie
{
Title = "Inception",
Genre = "Sci-Fi",
ReleaseYear = 2010,
Budget = new Budget
{
ProductionCost = 160_000_000,
MarketingCost = 100_000_000,
DistributionCost = 50_000_000,
Currency = "USD"
}
};
var movie2 = new Movie
{
Title = "Memento",
Genre = "Thriller",
ReleaseYear = 2000,
Budget = new Budget
{
ProductionCost = 90_000_000,
MarketingCost = 120_000_000,
DistributionCost = 2_000_000,
Currency = "USD"
}
};
context.Movies.Add(movie1);
context.Movies.Add(movie2);
await context.SaveChangesAsync();
Console.WriteLine("Movie saved!");
// Query movie
var storedMovies = await context.Movies.ToListAsync();
foreach (var movie in storedMovies)
{
Console.WriteLine($"Movie: {movie.Title}, Total Budget: {movie.Budget.ProductionCost + movie.Budget.MarketingCost}");
}In the first part, we are populating the Movie table with some data. While the latter part gets and prints them. I focused on implementing the owned entity. You can query the records better with a projection.
Step 9: Run the project
Let's run it:
dotnet run
The above approach can be more customized. You can rename the column in the configuration as you want.
modelBuilder.Entity<Movie>(entity =>
{
entity.OwnsOne(m => m.Budget, budget =>
{
// Optional: Configure column names
budget.Property(b => b.ProductionCost).HasColumnName("ProductionCost");
budget.Property(b => b.MarketingCost).HasColumnName("MarketingCost");
budget.Property(b => b.DistributionCost).HasColumnName("DistributionCost");
budget.Property(b => b.Currency).HasColumnName("Currency");
});
});That way, the columns will be like PodcutionCost and MarketingCost instead of Budget_PodcutionCost and Budget_MarketingCost.
How to add nested owned entities in EF Core
An entity can have more than one owned entity. Also, you can define a nested owned entity, an owned entity inside another owned entity. Let's have a look at how we can do that with the same example.
Step 1: Add another owned entity
Create the class CostBreakdown:
public class Budget
{
public decimal ProductionCost { get; set; }
public decimal MarketingCost { get; set; }
public decimal DistributionCost { get; set; }
public string Currency { get; set; } = string.Empty;
public CostBreakdown Breakdown { get; set; } = null!;
}
public class CostBreakdown
{
public decimal CastSalaries { get; set; }
public decimal CrewSalaries { get; set; }
public decimal Equipment { get; set; }
public decimal CGI { get; set; }
}Step 2: Configure a new owned type
modelBuilder.Entity<Movie>(movie =>
{
movie.OwnsOne(m => m.Budget, budget =>
{
budget.Property(b => b.ProductionCost);
budget.Property(b => b.MarketingCost);
budget.Property(b => b.DistributionCost);
budget.Property(b => b.Currency);
// Nested Owned Entity
budget.OwnsOne(b => b.Breakdown, breakdown =>
{
breakdown.Property(p => p.CastSalaries);
breakdown.Property(p => p.CrewSalaries);
breakdown.Property(p => p.Equipment);
breakdown.Property(p => p.CGI);
});
});
});As in the example, you can use the fluent API to add nested owned types to relations in your Movies table.
Step 3: Add migration
Add a new migration by running the command:
dotnet ef migrations add CostBreakdownAddedand apply it:
dotnet ef database update
How to add a Collection Owned Entity in EF Core
Owned types can be a collection as well, where multiple objects of the same type depend on a single owner entity. In that case, EF Core maintains a separate table to save the dependent data. Let's see how we can do in our example.
Step 1: Create models
Let's create a new Questionnaire model. All the questions will be collected from the entity in question. Question.
public class Questionnaire
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public List<Question> Questions { get; set; } = new();
}
public class Question
{
public string Text { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty; // e.g. Text, MCQ, Rating
public bool IsRequired { get; set; }
}Step 2: Perform configurations in the DbContext
Add a new DbSet:
public DbSet<Questionnaire> Questionnaires => Set<Questionnaire>();
and use the fluent API to set up the entity:
modelBuilder.Entity<Questionnaire>(q =>
{
q.OwnsMany(x => x.Questions, questions =>
{
questions.WithOwner().HasForeignKey("QuestionnaireId");
questions.Property<int>("Id"); // shadow key
questions.HasKey("Id");
questions.Property(x => x.Text).HasColumnName("Text");
questions.Property(x => x.Type).HasColumnName("Type");
questions.Property(x => x.IsRequired).HasColumnName("IsRequired");
questions.ToTable("QuestionnaireQuestions"); // optional
});
});OwnsMany configures that each entity of Questionnaire will hold a collection of Questions as an owned type. questions.WithOwner().HasForeignKey("QuestionnaireId"); defines that each Question belongs to one Questionnaire with a foreign key QuestionnaireId. EF Core will translate it to:
QuestionnaireId INTEGER NOT NULL
Although Questions have a primary and foreign key, they are not queryable and cannot exist independently.
Step 3: Run migration
Make the updates with a new migration:
dotnet ef migrations add QuestionaireAddedAnd run the migration:
dotnet ef database update
Step 4: Set up Program.cs to insert data
var survey = new Questionnaire
{
Title = "Customer Satisfaction Survey",
Questions = new List<Question>
{
new Question { Text = "How satisfied are you?", Type = "Rating", IsRequired = true },
new Question { Text = "What can we improve?", Type = "Text", IsRequired = false },
new Question { Text = "Would you recommend us?", Type = "YesNo", IsRequired = true }
}
};
context.Questionnaires.Add(survey);
await context.SaveChangesAsync();
Console.WriteLine("Data saved!");
var data = await context.Questionnaires.FirstAsync();
Console.WriteLine($"Questionaire: {data.Title}");
foreach (var item in data.Questions)
{
Console.WriteLine($"Text: {item.Text}, Type: {item.Type}");
}Step 5: Test the project

Advantages of an owned entity
- An owned entity is a value object defined solely by its values, without an identity. The nature of owned types allows your domain to model real-world concepts while remaining clean and expressive.
- As owned entities are dependent, they cascade automatically with the owner. Hence, you do not require involvement in additional management for dependent fields, such as the questions in a questionnaire.
- In the model, you can encapsulate related fields in separate classes, while the database does not need to map them to the tables. That way, you can keep code flexible without cluttering up extra tables or DbSets.
- As EF Core stores the properties of owned entities in the same table, no additional joins are performed when querying the data. Hence, owning property helps improve the overall performance of the application.e
- Owned handles parent-child relations cleanly with shadow keys. You don't need standalone entities even for one-to-many parent-child relationships.
- Owned entities eliminate unnecessary IDs and primary keys by directly attaching them to their parent entities.
- It keeps data consistent because the lifecycle of owned entities is tied to the owner, deleting or updating the owner changes the child entities.
- The dependent entities reduce EF Core's overhead by allowing it to track only the parent entity.
Conclusion
Owned entity types provide a clean way to save complex values in the database. They are dependent entities that do not hold any identity or table of their own but exist alongside their parent. We delved into this remarkable feature and saw how you can encapsulate complex data such as addresses, costs, and order items. Owned entities keep the domain models clean by clustering related fields into dependent types.
elmah.io: Error logging and Uptime Monitoring for your web apps
This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.
See how we can help you monitor your website for crashes Monitor your website