Chapter 12: Saving data with Entity Framework Core
This chapter introduction frames EF Core as a .NET library for reducing database-access complexity and previews configuring it, evolving database schemas, and performing CRUD in an ASP.NET Core app.
After this chapter you will be able to
- Database access complexity. Explain that database-backed ASP.NET Core applications must store and load data, and that direct database work entails concerns such as connections and translating application data into database-understandable formats.
- EF Core purpose. Explain that Entity Framework Core (EF Core) is a modern .NET library for quickly building database-access code in ASP.NET Core applications, reducing the friction of querying and saving data.
- EF Core purpose. Explain that EF Core is a higher-level .NET abstraction for database access, contrasted with lower-level options such as ADO.NET, and state the database-access problem it is intended to address.
- Application-database mapping. Describe EF Core as helping bridge the gap between application code and a database through a mapping from app code to database concepts.
- EF Core as an ORM. Explain that EF Core is a library and object-relational mapper that lets an application access a database through object-oriented concepts. Describe how it maps .NET classes and objects to database concepts such as tables, rows, and columns, and maps database responses back to .NET objects.
- EF Core database scope. Describe EF Core's primary use with relational databases accessed through SQL, including examples such as SQL Server, SQLite, MySQL, and Postgres. State that its provider model allows support for database systems to be added, while this book focuses on relational database mapping.
- ORM database abstraction. Explain how an ORM such as EF Core replaces much of the low-level work of reading database data with object-oriented .NET code. Contrast the manual connection, SQL, data-reading, and POCO-mapping work with providing a LINQ query to EF Core.
- ORM tradeoffs and selection. Explain that ORMs trade some performance and database visibility for faster development and less plumbing code, and can cause inefficient query patterns such as the N+1 problem. Describe the pragmatic option of using EF Core for most of an application while using lower-level APIs such as ADO.NET only for proven bottlenecks, or avoiding an ORM for minimal access needs or maximum performance.
- EF Core and EF 6.x distinction. Distinguish EF Core from Entity Framework 6.x by explaining that EF Core was rewritten as a separate project with similar but nonidentical APIs, so a direct upgrade from EF 6.x is nontrivial.
- ORM choice for new applications. Justify choosing EF Core for a new .NET application when using an ORM: EF 6.x is still supported but is no longer actively developed or recommended for new applications, whereas EF Core is a strong contender with ASP.NET Core subsystem support.
- Entities and database contexts. Describe an EF Core entity as a typically POCO .NET class mapped to a database table, and explain the distinct roles of entity classes and a DbContext. State that the DbContext configures EF Core and provides runtime database access.
- EF Core model discovery. Explain how EF Core builds an internal model when the application first uses it: it starts with entity types exposed as DbSet<T> properties on the DbContext and explores their properties for additional entity types. Trace this in the recipe app from DbSet<Recipe> to the ICollection<Ingredient> property.
- EF Core setup workflow. Describe the ordered process for adding EF Core to an ASP.NET Core application, from choosing a database provider and installing packages through defining and registering the data model, then creating and applying a migration.
- Service-layer data access. Explain that the example keeps EF Core interaction inside a RecipeService service layer instead of allowing minimal API endpoint handlers to access EF Core directly, and state that this separates concerns and improves testability.
- EF Core provider model. Explain how EF Core's provider model lets application code use the same high-level API with different underlying databases while EF Core generates database-specific implementation code and SQL.
- Provider package selection. Describe choosing a database provider by adding the corresponding provider NuGet package to the project's .csproj file, taking account of the database you intend to use and provider maintenance or licensing requirements.
- POCO entity mapping. Explain how EF Core can map ordinary POCO entity classes to database tables without requiring a special base class or mapping attributes. Describe how EF Core uses naming conventions, such as an Id-suffixed property, to identify a table's primary key.
- Recipe ingredient relationship. Describe how Ingredient.RecipeId together with Recipe's ICollection<Ingredient> represents a many-to-one relationship. Explain that each recipe can have many ingredients, each ingredient belongs to one recipe, and the foreign key links to the other table's primary key.
- Application DbContext. Describe AppDbContext as the application's custom class derived from DbContext and as the central object used for database calls. Explain that exposing DbSet<Recipe> lets EF Core discover and map Recipe, and that DbSet properties represent top-level entities.
- Data context DI registration. Describe how to register AppDbContext with the WebApplicationBuilder in Program.cs by using EF Core's generic AddDbContext<T> method. State that this makes the context available from the dependency injection container as a scoped service.
- Provider and connection configuration. Explain that AddDbContext<T> receives a configuration function for a DbContextOptionsBuilder, where the database provider and connection string are configured. Describe using UseSqlite with a connection string for SQLite, or the appropriate Use* method for another provider such as SQL Server.
- Database schema and model synchronization. Explain why an application's database schema must stay synchronized with its EF Core data model, and define schema as the database's tables, columns, and relationships.
- EF Core migration records. Describe an EF Core migration as a C# code file that records how the data model changed, such as added columns or entities, and thereby records the database schema's evolution over time.
- Initial migration artifacts. Explain that creating a named migration generates three files in the Migrations folder: a provider-specific migration file, a designer file describing the model at that migration, and the current AppDbContext model snapshot. Distinguish creating a migration from changing the actual database.
- Migration application workflow. Trace what happens when dotnet ef database update applies migrations: it builds the app, loads Program.cs services including AppDbContext, creates the configured database if needed, and applies unapplied migrations. Explain that the __EFMigrationsHistory table records applied migration names so later updates apply only new migrations.
- Second migration workflow. Describe the workflow for changing an existing EF Core schema: change the entity classes, generate a named migration with dotnet ef migrations add, and apply it with dotnet ef database update.
- Migration files and tracking. Explain that creating a migration adds migration files and updates AppDbContextModelSnapshot, but does not modify the database; applying updates compares application migrations with the database's __EFMigrationsHistory table and runs outstanding migrations.
- RecipeService responsibilities. Describe the role of RecipeService in the recipe application: it contains business logic for querying recipes, creating recipes, and modifying existing recipes in the database.
- Business logic separation. Explain why the section recommends extracting business logic into services instead of generally placing it in endpoint handlers or Razor Pages: doing so decouples it from HTTP concerns and makes it easier to test and reuse.
- Recipe creation workflow. Describe the three EF Core steps for creating a recipe: create the Recipe and Ingredient entities, add them to the AppDbContext tracking list with Add(), and call SaveChangesAsync() to execute the database inserts.
- Command-to-entity mapping. Explain that the endpoint receives a CreateRecipeCommand binding model and maps its properties to separate Recipe and Ingredient entities before persistence, rather than binding the request directly to the Recipe entity.
- Recipe summary projection. Describe how GetRecipes returns RecipeSummaryViewModels containing RecipeId, Name, and TimeToCook instead of exposing Recipe entities directly from the endpoint’s public API. Explain that Select() can project database results into non-entity objects and fetch only the needed columns.
- LINQ query execution. Trace the three-part EF Core query pattern: build a fluent LINQ query from the DbSet<Recipe> IQueryable, optionally filter or project it, and call an execution method such as ToListAsync() to generate and run SQL. State that the LINQ chain is translated to SQL when an execute method is called, not merely when the chain is defined.
- Single-record EF Core query. Describe how RecipeService loads one recipe by ID using a LINQ Where() filter, Select projection, and SingleOrDefaultAsync(). Explain that SingleOrDefaultAsync() causes EF Core to generate and run the database query and construct the requested view model.
- Entity update workflow. Describe the three-step process used by RecipeService.UpdateRecipe: load the Recipe by ID, change its properties from an UpdateRecipeCommand, and call SaveChangesAsync() to persist the changes. State that this business logic belongs in RecipeService rather than directly in a minimal API endpoint handler.
- EF Core change tracking. Explain how EF Core tracks an entity loaded from the database by keeping a snapshot of its original property values. Describe how SaveChanges() compares the tracked entity with that snapshot and includes only changed properties in the generated UPDATE statement.
- Soft-delete pattern. Explain that, instead of physically removing a Recipe, the application can soft-delete it by setting its IsDeleted flag and saving the update. Describe why this avoids lost data and referential-integrity issues, and state that queries must exclude recipes whose IsDeleted value is true, optionally through global query filters.
- Production EF Core preparation. Explain why the chapter's introductory EF Core coverage is not sufficient for production and why further EF Core learning is necessary before deploying an application.
- Application-level production concerns. Describe the EF Core concerns that arise in application code: sensible column definitions, explicit validation, concurrent updates, transient-error handling, and the synchronous-versus-asynchronous choice.
- Database deployment and migrations. Describe the production database-management issues around applying migrations, including automated deployment, multiple web hosts, backward-compatible schema changes, separate migration assemblies, and explicit data seeding.
What it builds on
- Using dependency injection in ASP.NET Core (chapter 8) · The idea that ASP.NET Core uses dependency injection to provide application services.
- Creating loosely coupled code (chapter 8) · The principle of creating loosely coupled code by separating responsibilities.
- Using services from the DI container (chapter 8) · Using registered services from the ASP.NET Core DI container.
- The .csproj project file: Declaring your dependencies (chapter 3) · Declaring project dependencies with PackageReference elements in the .csproj file.
- Scoped: Let’s stick together (chapter 9) · The scoped lifetime used for services that should be shared within a request.
- Storing configuration secrets safely (chapter 10) · The need to store configuration secrets safely.
- Loading environment-specific configuration files (chapter 10) · Loading environment-specific configuration files.
- Defining route handlers with functions (chapter 5) · The idea of route handlers as code that handles HTTP endpoint requests.
Section by section
Introduction
This chapter introduction frames EF Core as a .NET library for reducing database-access complexity and previews configuring it, evolving database schemas, and performing CRUD in an ASP.NET Core app.
- EF Core chapter roadmap: Describe the chapter’s progression from introducing EF Core and its benefits through adding and configuring it, modeling and migrating a database, and performing CRUD operations.
Why it matters: This roadmap helps you connect each EF Core task to the larger workflow of persisting application data. - Database access complexity: Explain that database-backed ASP.NET Core applications must store and load data, and that direct database work entails concerns such as connections and translating application data into database-understandable formats.
Why it matters: Recognizing these recurring concerns clarifies the problem that EF Core is intended to reduce. - EF Core purpose: Explain that Entity Framework Core (EF Core) is a modern .NET library for quickly building database-access code in ASP.NET Core applications, reducing the friction of querying and saving data.
Why it matters: EF Core is the chapter’s main tool for making routine database work easier in an ASP.NET Core application. - Scope and database choices: State that the chapter is a quick practical overview, sufficient to connect an app to a database and manage schema changes but not an in-depth EF Core treatment; it uses SQLite while noting that the shown code is not SQLite-specific and a LocalDB version is also available.
Why it matters: Knowing the stated scope and provider independence helps set realistic expectations for applying the examples.
Key terms: Entity Framework Core, EF Core, data model, migrations, CRUD, database, connections, data store, database access code, Entity Framework 6.x
12.1 Introducing Entity Framework Core
Introduces Entity Framework Core as a higher-level way to bridge application code and databases, framing its purpose, tradeoffs, and mapping concepts.
- EF Core purpose: Explain that EF Core is a higher-level .NET abstraction for database access, contrasted with lower-level options such as ADO.NET, and state the database-access problem it is intended to address.
Why it matters: This establishes why an application might use EF Core instead of directly managing every database interaction. - Database access complexity: Describe the work involved in directly reading data from a database: managing network connections, writing SQL statements, and handling variable result data.
Why it matters: These recurring low-level concerns motivate using a database-access abstraction. - Application-database mapping: Describe EF Core as helping bridge the gap between application code and a database through a mapping from app code to database concepts.
Why it matters: The mapping is the central idea that lets application code work with database data through EF Core. - EF Core adoption tradeoffs: State that using EF Core involves tradeoffs and that those tradeoffs should inform whether it is the right choice for an application's needs.
Why it matters: EF Core is one of several database-access approaches, so its fit depends on the application's purposes.
Key terms: EF Core, ADO.NET, database access code, network connections, SQL statements, result data, database access, mapping, app code, database
12.1.1 What is EF Core?
This section defines EF Core as a cross-platform object-relational mapper that maps .NET objects to database data, chiefly for relational databases.
- EF Core as an ORM: Explain that EF Core is a library and object-relational mapper that lets an application access a database through object-oriented concepts. Describe how it maps .NET classes and objects to database concepts such as tables, rows, and columns, and maps database responses back to .NET objects.
Why it matters: EF Core lets you work with database data through the C# classes and objects used by your application. - EF Core database scope: Describe EF Core's primary use with relational databases accessed through SQL, including examples such as SQL Server, SQLite, MySQL, and Postgres. State that its provider model allows support for database systems to be added, while this book focuses on relational database mapping.
Why it matters: Knowing EF Core's relational focus and provider model helps you choose the appropriate database package and understand the examples that follow. - EF Core design background: State that EF Core is based on but distinct from the older Entity Framework libraries, including Entity Framework 6.x. Describe its .NET Core origins and its goals of cross-platform use, high performance, and support for a wide range of databases.
Why it matters: This distinction prevents you from treating EF Core as merely a new version of the older Entity Framework library.
Key terms: Entity Framework Core, EF Core, object-relational mapper, ORM, classes, objects, relational database, SQL, provider model, SQL Server
12.1.2 Why use an object-relational mapper?
This section explains how an ORM such as EF Core simplifies database access, the tradeoffs it introduces, and when lower-level data access may be preferable.
- ORM database abstraction: Explain how an ORM such as EF Core replaces much of the low-level work of reading database data with object-oriented .NET code. Contrast the manual connection, SQL, data-reading, and POCO-mapping work with providing a LINQ query to EF Core.
Why it matters: This abstraction lets you focus on application objects and requested data instead of repetitive database plumbing. - Change tracking and updates: Describe how EF Core tracks changes to properties on objects loaded from the database. Explain that after modifying such an object in .NET, the ORM can determine changed properties and issue updates for the corresponding database columns.
Why it matters: Change tracking removes the need to manually determine and construct database updates after changing an object. - ORM tradeoffs and selection: Explain that ORMs trade some performance and database visibility for faster development and less plumbing code, and can cause inefficient query patterns such as the N+1 problem. Describe the pragmatic option of using EF Core for most of an application while using lower-level APIs such as ADO.NET only for proven bottlenecks, or avoiding an ORM for minimal access needs or maximum performance.
Why it matters: Understanding the tradeoff helps you choose EF Core deliberately and optimize only where its abstraction is not a good fit.
Key terms: object-relational mapper, ORM, EF Core, LINQ, POCO, SQL, change tracking, properties, update statements, columns
12.1.3 When should you choose EF Core?
This section positions EF Core as the preferred ORM choice for new .NET applications while distinguishing it from the older EF 6.x framework.
- EF Core and EF 6.x distinction: Distinguish EF Core from Entity Framework 6.x by explaining that EF Core was rewritten as a separate project with similar but nonidentical APIs, so a direct upgrade from EF 6.x is nontrivial.
Why it matters: Treating EF Core as a distinct framework helps you make realistic migration and technology choices. - ORM choice for new applications: Justify choosing EF Core for a new .NET application when using an ORM: EF 6.x is still supported but is no longer actively developed or recommended for new applications, whereas EF Core is a strong contender with ASP.NET Core subsystem support.
Why it matters: Choosing the current ORM avoids starting a new application on the older, no-longer actively developed EF 6.x framework.
Key terms: EF Core, Entity Framework 6.x, EF 6.x, ORM, object-relational mapper, ASP.NET Core Identity
12.1.4 Mapping a database to your application code
This section introduces the recipe API’s EF Core data model and explains how entities, DbContext, DbSet properties, and relationships become a database model.
- Entities and database contexts: Describe an EF Core entity as a typically POCO .NET class mapped to a database table, and explain the distinct roles of entity classes and a DbContext. State that the DbContext configures EF Core and provides runtime database access.
Why it matters: Entities describe the data to persist, while the DbContext is the application’s gateway for configuring and working with that data. - EF Core model discovery: Explain how EF Core builds an internal model when the application first uses it: it starts with entity types exposed as DbSet<T> properties on the DbContext and explores their properties for additional entity types. Trace this in the recipe app from DbSet<Recipe> to the ICollection<Ingredient> property.
Why it matters: This discovery process determines which application types and relationships EF Core understands when it generates database operations. - Recipe ingredient relationship: Describe the Recipe-to-Ingredient association as many-to-one: a recipe can have many ingredients, while each Ingredient belongs to one recipe. Explain that EF Core maps both entities and this relationship to the corresponding database structure, and that similarly named ingredients in different recipes are separate instances.
Why it matters: Correctly modeling ownership and multiplicity lets EF Core create the appropriate database structure and keeps changes to one recipe’s ingredient from changing another recipe’s ingredient.
Key terms: entity, POCO, DbContext, database context, internal model, DbSet<T>, AppDbContext, DbSet<Recipe>, ICollection<Ingredient>, many-to-one relationship
12.2 Adding EF Core to an application
This section outlines how to set up EF Core in the recipe API, separating database access into a service layer and previewing the configuration and migration workflow.
- EF Core setup workflow: Describe the ordered process for adding EF Core to an ASP.NET Core application, from choosing a database provider and installing packages through defining and registering the data model, then creating and applying a migration.
Why it matters: This workflow connects your application’s C# data model to a database schema that EF Core can use. - Service-layer data access: Explain that the example keeps EF Core interaction inside a RecipeService service layer instead of allowing minimal API endpoint handlers to access EF Core directly, and state that this separates concerns and improves testability.
Why it matters: Keeping persistence code behind a service layer makes endpoint code cleaner and lets data-access behavior be tested separately. - EF Core default conventions: State that the chapter’s examples use EF Core default conventions and explain that sticking with defaults where possible is recommended because it makes development easier in the long run.
Why it matters: Default conventions provide a simpler starting point even though EF Core can be customized extensively.
Key terms: database provider, EF Core NuGet packages, DbContext, entities, data model, DI container, service layer, RecipeService, minimal API endpoint handlers, EF Core
12.2.1 Choosing a database provider and installing EF Core
This section explains how EF Core database providers let an application use a chosen database and which NuGet packages to install for runtime and design-time support.
- EF Core provider model: Explain how EF Core's provider model lets application code use the same high-level API with different underlying databases while EF Core generates database-specific implementation code and SQL.
Why it matters: The provider model separates the API your application uses from the database-specific SQL EF Core must produce. - Provider package selection: Describe choosing a database provider by adding the corresponding provider NuGet package to the project's .csproj file, taking account of the database you intend to use and provider maintenance or licensing requirements.
Why it matters: Installing the correct provider package is what gives EF Core support for the database your application will use. - SQLite package installation: State that a SQLite-based EF Core application installs Microsoft.EntityFrameworkCore.SQLite for runtime database support and Microsoft.EntityFrameworkCore.Design for shared build-time components used to build the data model, then restores the packages with dotnet restore or Visual Studio.
Why it matters: EF Core is modular, so the runtime provider alone is not the complete package setup described for the recipe application.
Key terms: EF Core, provider model, high-level API, SQL statements, database provider, NuGet package, .csproj, PackageReference, Npgsql.EntityFrameworkCore.PostgreSQL, Microsoft.EntityFrameworkCore.SqlServer
12.2.2 Building a data model
This section defines a convention-based EF Core data model using POCO entity classes, relationships, and a custom DbContext.
- POCO entity mapping: Explain how EF Core can map ordinary POCO entity classes to database tables without requiring a special base class or mapping attributes. Describe how EF Core uses naming conventions, such as an Id-suffixed property, to identify a table's primary key.
Why it matters: EF Core can create a database model from familiar C# classes, so you do not have to hand-map every table and column. - Recipe ingredient relationship: Describe how Ingredient.RecipeId together with Recipe's ICollection<Ingredient> represents a many-to-one relationship. Explain that each recipe can have many ingredients, each ingredient belongs to one recipe, and the foreign key links to the other table's primary key.
Why it matters: Relationships let EF Core represent connected application objects and translate those connections into linked database tables. - Application DbContext: Describe AppDbContext as the application's custom class derived from DbContext and as the central object used for database calls. Explain that exposing DbSet<Recipe> lets EF Core discover and map Recipe, and that DbSet properties represent top-level entities.
Why it matters: The DbContext is the application entry point for EF Core's model discovery and later database operations. - Related entity discovery: Explain why Ingredient does not need its own DbSet<Ingredient> on AppDbContext in this model. Describe that EF Core discovers Ingredient through Recipe's Ingredients property, and that ingredients are accessed by navigating through a Recipe entity's Ingredients collection.
Why it matters: Knowing which entities are roots versus related entities helps you design a small, understandable context model.
Key terms: POCO classes, convention over configuration, entity, primary key, RecipeId, IngredientId, foreign key, many-to-one relationship, ICollection<Ingredient>, Ingredients
12.2.3 Registering a data context
This section registers an EF Core AppDbContext in ASP.NET Core's DI container and configures it to use a database provider and environment-specific connection string.
- Data context DI registration: Describe how to register AppDbContext with the WebApplicationBuilder in Program.cs by using EF Core's generic AddDbContext<T> method. State that this makes the context available from the dependency injection container as a scoped service.
Why it matters: Registering the context lets application code obtain a configured EF Core connection to the database through dependency injection. - Provider and connection configuration: Explain that AddDbContext<T> receives a configuration function for a DbContextOptionsBuilder, where the database provider and connection string are configured. Describe using UseSqlite with a connection string for SQLite, or the appropriate Use* method for another provider such as SQL Server.
Why it matters: EF Core needs both a provider and connection information in order to communicate with the selected database. - Configuration-based connection strings: Justify loading the connection string from application configuration rather than hard-coding it, because it is typically a secret and configuration selects the value for the current environment. State that this allows local development and production to use different databases.
Why it matters: Keeping connection strings in configuration protects sensitive settings and lets each environment target its appropriate database.
Key terms: AppDbContext, dependency injection container, WebApplicationBuilder, Program.cs, AddDbContext<T>, scoped service, DbContextOptionsBuilder, UseSqlite, Microsoft.EntityFrameworkCore.SQLite, connection string
12.3 Managing changes with migrations
This section introduces EF Core migrations as versioned C# records that generate and apply schema changes to keep a database aligned with an evolving data model.
- Database schema and model synchronization: Explain why an application's database schema must stay synchronized with its EF Core data model, and define schema as the database's tables, columns, and relationships.
Why it matters: Your application code expects a particular database structure, so mismatched code and schemas can break the application. - EF Core migration records: Describe an EF Core migration as a C# code file that records how the data model changed, such as added columns or entities, and thereby records the database schema's evolution over time.
Why it matters: Migrations make database changes explicit, versioned alongside application code, and reproducible for each database environment. - Migration application and rollback safety: Describe how EF Core command-line tools can create a new database from migrations or update an existing database by applying new migrations, and explain why rollbacks can cause permanent data loss.
Why it matters: Unlike application binaries, databases contain valuable data, so schema changes must be applied and reversed with care.
Key terms: schema, data model, database schema, EF Core migrations, migration, command-line tools, applying migrations, rollback, data loss
12.3.1 Creating your first migration
This section shows how to create an initial EF Core migration and apply it to create and update a database schema.
- EF Core migration tooling: Describe the two available EF Core migration tooling options and how the book uses the cross-platform dotnet-ef .NET tool. State that commands must normally be run from the application project folder that registered AppDbContext, not the solution folder.
Why it matters: Using the correct EF Core tools and working directory lets the commands find your project and its data context. - Initial migration artifacts: Explain that creating a named migration generates three files in the Migrations folder: a provider-specific migration file, a designer file describing the model at that migration, and the current AppDbContext model snapshot. Distinguish creating a migration from changing the actual database.
Why it matters: Migration files record schema changes in the application so EF Core can later determine how to update a database. - Migration application workflow: Trace what happens when dotnet ef database update applies migrations: it builds the app, loads Program.cs services including AppDbContext, creates the configured database if needed, and applies unapplied migrations. Explain that the __EFMigrationsHistory table records applied migration names so later updates apply only new migrations.
Why it matters: Applying migrations is the step that actually brings the database schema into line with the application's EF Core model.
Key terms: Package Manager Console, Microsoft.EntityFrameworkCore.Tools, dotnet-ef, dotnet ef, AppDbContext, migration, Migrations folder, migration file, Migration designer.cs file, AppDbContextModelSnapshot.cs
12.3.2 Adding a second migration
This section shows how to evolve an EF Core data model by creating and applying a second migration, while explaining how migrations track database schema versions.
- Second migration workflow: Describe the workflow for changing an existing EF Core schema: change the entity classes, generate a named migration with dotnet ef migrations add, and apply it with dotnet ef database update.
Why it matters: This workflow lets the database schema evolve alongside changes to your application model. - Migration files and tracking: Explain that creating a migration adds migration files and updates AppDbContextModelSnapshot, but does not modify the database; applying updates compares application migrations with the database's __EFMigrationsHistory table and runs outstanding migrations.
Why it matters: Understanding this separation explains why EF Core knows which schema changes still need to be applied. - Schema versioning benefit: Explain that migrations version the database schema together with application code in source control, so checking out an earlier version of the application can be used to recreate the schema from that point in time.
Why it matters: Keeping schema changes with code makes historical versions of an application reproducible. - Production migration considerations: State that migrations are straightforward for a solo developer or a single web server, but shared databases across multiple servers and containerized applications require additional database-management considerations.
Why it matters: Applying schema changes in production needs more care when several deployed application instances share one database.
Key terms: EF Core migrations, dotnet ef migrations add, dotnet ef database update, Recipe, IsVegetarian, IsVegan, migration file, .designer.cs, AppDbContextModelSnapshot.cs, __EFMigrationsHistory
12.4 Querying data from and saving data to the database
This section introduces RecipeService as the place to implement recipe database operations while keeping business logic separate from HTTP handlers or Razor Pages.
- RecipeService responsibilities: Describe the role of RecipeService in the recipe application: it contains business logic for querying recipes, creating recipes, and modifying existing recipes in the database.
Why it matters: Putting these operations in one service gives the application a clear place for its recipe-related business logic. - Business logic separation: Explain why the section recommends extracting business logic into services instead of generally placing it in endpoint handlers or Razor Pages: doing so decouples it from HTTP concerns and makes it easier to test and reuse.
Why it matters: Separating application rules from web-request code keeps the code more reusable and easier to verify. - Current database state: State that the recipe-and-ingredient model has migrations applied so the database schema matches EF Core's model, but the database currently contains no data and therefore needs an initial recipe.
Why it matters: This explains why the next task is creating a record rather than only querying existing recipes.
Key terms: RecipeService, business logic, recipes, database, endpoint handlers, Razor Pages, HTTP-centric, decouples, test, migrations
12.4.1 Creating a record
This section shows how a validated POST request is mapped to Recipe and Ingredient entities and saved through EF Core.
- Recipe creation workflow: Describe the three EF Core steps for creating a recipe: create the Recipe and Ingredient entities, add them to the AppDbContext tracking list with Add(), and call SaveChangesAsync() to execute the database inserts.
Why it matters: This is the basic pattern for turning request data into persisted database rows with EF Core. - Command-to-entity mapping: Explain that the endpoint receives a CreateRecipeCommand binding model and maps its properties to separate Recipe and Ingredient entities before persistence, rather than binding the request directly to the Recipe entity.
Why it matters: Using a separate command model avoids exposing entity properties that clients should not be able to set. - Related entity persistence: Explain that saving a new Recipe also saves its linked Ingredient entities, and EF Core creates the correct RecipeId relationships and updates autogenerated entity IDs after a successful save.
Why it matters: This lets application code save a related object graph without manually assigning the database relationship IDs. - Asynchronous saving and failures: State that SaveChangesAsync() is generally favored over its synchronous counterpart for handling more concurrent connections, and that database exceptions, such as an unapplied migration, should be handled rather than shown as ugly user-facing errors.
Why it matters: Choosing asynchronous database calls and handling persistence failures helps an application remain responsive and present errors appropriately.
Key terms: AppDbContext, Recipe, Ingredient, Add(), SaveChangesAsync(), tracked entities, CreateRecipeCommand, overposting, binding model, RecipeId
12.4.2 Loading a list of records
This section shows how to query EF Core for a filtered, projected list of recipe summaries and explains when LINQ work runs in SQL or on the client.
- Recipe summary projection: Describe how GetRecipes returns RecipeSummaryViewModels containing RecipeId, Name, and TimeToCook instead of exposing Recipe entities directly from the endpoint’s public API. Explain that Select() can project database results into non-entity objects and fetch only the needed columns.
Why it matters: Projecting to a summary model keeps the API focused and avoids returning database entities as its public contract. - LINQ query execution: Trace the three-part EF Core query pattern: build a fluent LINQ query from the DbSet<Recipe> IQueryable, optionally filter or project it, and call an execution method such as ToListAsync() to generate and run SQL. State that the LINQ chain is translated to SQL when an execute method is called, not merely when the chain is defined.
Why it matters: Knowing where a query executes helps you control when the application accesses the database and what SQL EF Core sends. - Deleted recipe filtering: Explain that the list query uses a Where() LINQ expression to exclude recipes marked as deleted before returning recipe summaries.
Why it matters: Filtering deleted recipes in the query ensures that the list endpoint returns only records intended for users to see. - Client-side expression evaluation: Explain that EF Core translates many LINQ expressions to SQL, but for expressions it cannot translate, it selects the fields needed and evaluates that expression in C# on the client. Describe the TimeToCook TimeSpan-to-string interpolation as the example, and note that dangerous client-side evaluation is rejected by recent EF Core versions.
Why it matters: This distinction lets you use C# expressions safely while avoiding queries that would inefficiently load records before filtering.
Key terms: RecipeSummaryViewModel, Recipe entity, Select(), projection, DbSet<Recipe>, IQueryable, LINQ, ToListAsync(), ToArrayAsync(), SingleAsync()
12.4.3 Loading a single record
This section shows how to query EF Core for one recipe by ID, project it and its related ingredients into a detail view model, and execute the query asynchronously.
- Single-record EF Core query: Describe how RecipeService loads one recipe by ID using a LINQ Where() filter, Select projection, and SingleOrDefaultAsync(). Explain that SingleOrDefaultAsync() causes EF Core to generate and run the database query and construct the requested view model.
Why it matters: Fetching one identified entity is the basis for detail pages and for later editing existing data. - Single-result query constraint: Explain that SingleOrDefaultAsync() throws an exception when the preceding Where clause produces more than one record, so the query is intended to identify at most one entity.
Why it matters: Knowing the single-result constraint helps you understand failures when a supposedly unique query has duplicate matches. - Related ingredient projection: Describe how the recipe query maps a Recipe and its related Ingredients into RecipeDetailViewModel as if working with in-memory objects, while EF Core decides how to build the underlying database queries.
Why it matters: This illustrates an ORM benefit: application code can work with related object data without manually composing the underlying SQL.
Key terms: Where(), Select, SingleOrDefaultAsync(), RecipeId, RecipeDetailViewModel, Where clause, Ingredients, ORM, EF Core
12.4.4 Updating a model with changes
This section shows how to update an EF Core entity through read-modify-save, explains EF Core change tracking, and recommends soft deletion for recipes when appropriate.
- Entity update workflow: Describe the three-step process used by RecipeService.UpdateRecipe: load the Recipe by ID, change its properties from an UpdateRecipeCommand, and call SaveChangesAsync() to persist the changes. State that this business logic belongs in RecipeService rather than directly in a minimal API endpoint handler.
Why it matters: This pattern keeps API concerns separate from data-update business logic while saving an existing record safely. - EF Core change tracking: Explain how EF Core tracks an entity loaded from the database by keeping a snapshot of its original property values. Describe how SaveChanges() compares the tracked entity with that snapshot and includes only changed properties in the generated UPDATE statement.
Why it matters: Change tracking lets EF Core determine which database columns actually need updating. - Soft-delete pattern: Explain that, instead of physically removing a Recipe, the application can soft-delete it by setting its IsDeleted flag and saving the update. Describe why this avoids lost data and referential-integrity issues, and state that queries must exclude recipes whose IsDeleted value is true, optionally through global query filters.
Why it matters: Soft deletion hides records from the API while preserving data and avoiding the cascading consequences of deleting related rows.
Key terms: RecipeService, UpdateRecipe, UpdateRecipeCommand, Recipe, FindAsync(), DbSet, change tracking, DbContext, tracked entities, snapshot
12.5 Using EF Core in production applications
This section highlights EF Core and database concerns that must be addressed before deploying an application to production.
- Production EF Core preparation: Explain why the chapter's introductory EF Core coverage is not sufficient for production and why further EF Core learning is necessary before deploying an application.
Why it matters: EF Core can make data access productive, but production database behavior introduces risks that a basic application does not expose. - Application-level production concerns: Describe the EF Core concerns that arise in application code: sensible column definitions, explicit validation, concurrent updates, transient-error handling, and the synchronous-versus-asynchronous choice.
Why it matters: These concerns determine whether database operations behave correctly and reliably under real users, invalid input, failures, and load. - Database deployment and migrations: Describe the production database-management issues around applying migrations, including automated deployment, multiple web hosts, backward-compatible schema changes, separate migration assemblies, and explicit data seeding.
Why it matters: A safe deployment must evolve a shared database without conflicting hosts, breaking older app versions, or omitting required initial data.
Key terms: EF Core, production-ready app, data-access code, scaffolding of columns, DataAnnotations validation attributes, Timestamp columns, concurrency, transient errors, connection resiliency, automatic migrations
12.6 Summary
This summary recaps how EF Core maps application entities to databases, is configured through a DbContext, and handles schema changes and data operations.
Where this is used later
- Updating the EF Core data model to support Identity (chapter 23) · Uses an EF Core data model when adding ASP.NET Core Identity to an existing project.
- Summary (chapter 34) · The role of a relational database in application storage
- Summary (chapter 36) · The in-memory EF Core provider for isolated database testing builds on the provider-based approach.
- What is ASP.NET Core Identity? (chapter 23) · EF Core as the data-access technology used to map application models to a database.
- Isolating the database with an in-memory EF Core provider (chapter 36) · EF Core as an object-relational mapper
- Avoiding SQL injection attacks with EF Core and parameterization (chapter 29) · The role of an object-relational mapper
- Configuring the ASP.NET Core Identity services (chapter 23) · Configuring Identity services in an existing project also depends on database configuration.
- Handling request logic with page models and handlers (chapter 13) · Razor Pages handlers can call separate services rather than owning all application logic.
Concepts explained in this chapter
- Entity Framework CoreEntity Framework Core is a cross-platform .NET object-relational mapper for working with relational and some nonrelational databases through .NET types and queries.
- migrationA database migration is a versioned change that updates a database schema to match changes in an application's data model.
- dbcontextDbContext is the Entity Framework Core base class that coordinates database connections, entity tracking, querying, and persistence for a data model.
- DbSet<T>DbSet<T> is the Entity Framework Core API representing a set of entities of type T and exposing query and persistence operations.
- entityAn entity is an application object whose structure and identity are mapped by an object-relational mapper to a database record.
- SaveChangesAsyncSaveChangesAsync is an Entity Framework Core database-context method that asynchronously persists tracked changes to the database.
- soft deletionSoft delete is a deletion technique that marks a record as deleted so it is excluded from application exposure while remaining stored.
- CRUDA CRUD operation is an operation that creates, reads, updates, or deletes persistent data.
- data modelA database model represents the application data stored in a database and the structure used to work with that data.
- database providerA database provider model is an abstraction that allows an object-relational mapper to target different database systems through provider-specific implementations.
- object-relational mapperAn object-relational mapper is a software abstraction that maps object-oriented program types and operations to database concepts such as tables, columns, and queries.
- schemaA database schema is the structural definition of a database's tables, columns, keys, relationships, and related objects.
- dotnet ef database updatedotnet ef database update is an Entity Framework Core command that creates the database when necessary and applies pending migrations.
- IQueryableIQueryable is a .NET interface for representing queries whose expression trees can be translated and executed by a data source.
- one-to-many relationshipA many-to-one relationship is an association in which multiple instances of one entity type are associated with a single instance of another entity type.
- dotnet ef migrations adddotnet ef migrations add is an Entity Framework Core command that compares the current model with a prior snapshot and generates a new migration.
- FindAsyncFindAsync is an Entity Framework Core DbSet method that asynchronously loads an entity by its primary key, first checking tracked entities.
- IsDeleted flagAn IsDeleted flag is an entity property whose value indicates whether the entity should be treated as deleted without removing its database row.
- SaveChangesSaveChanges is an Entity Framework Core database-context method that persists tracked data changes to the configured database.
- change trackingChange tracking is the process of recording entity instances and detecting changes to their properties for persistence.
- global query filterA global query filter is an Entity Framework Core model-level predicate that is automatically applied to queries for an entity type.
- snapshotA tracking snapshot is a stored set of an entity's property values used to compare its original and current state.
- databaseA database is an organized collection of data managed for storage, retrieval, and processing.
- AddDbContext<T>AddDbContext<T> is an ASP.NET Core dependency-injection extension method that registers an Entity Framework Core database context type and configures its provider and options.
- SelectA select clause is a LINQ query operation that projects source elements into a specified result shape.
- .NET toolA .NET global tool is a .NET CLI tool installed for use across projects and invoked from the command line.
- convention over configurationConvention over configuration is a design approach in which framework-defined naming and structure conventions determine behavior unless explicitly overridden.
- plain old CLR objectA plain old CLR object is an ordinary .NET object that does not depend on a specialized framework base type or programming model.
- NuGetA NuGet package is a distributable package containing a .NET library or other reusable assets together with metadata and dependency information.
- overpostingOverposting is a security vulnerability in which client-supplied fields are bound directly to properties that the application did not intend to expose for modification.
- dependency injection containerThe built-in dependency injection container is the default service container provided by ASP.NET Core for registering and resolving application services.
- Microsoft.EntityFrameworkCore.SQLiteMicrosoft.EntityFrameworkCore.SQLite is the NuGet package that supplies SQLite database-provider integration for Entity Framework Core.
- PackageReferencePackageReference is an MSBuild project-file item that declares a package dependency for a .NET project.
- dotnet restoredotnet restore is a .NET CLI command that downloads a project's package dependencies and establishes their references for building.
- projectA project is a .NET unit of deployment containing the source files and configuration needed to build an application or library.
- UseSqlite()UseSqlite() is an Entity Framework Core configuration method that selects SQLite as the database provider for a context.
- business logicBusiness logic is the application behavior that implements domain rules, decisions, and processes independently of presentation or infrastructure concerns.
- dataannotations attributeDataAnnotations is the .NET attribute-based framework for declaring metadata and validation rules on types and their members.
- transient failureA transient error is a temporary failure, such as a network or service interruption, that may succeed when the operation is retried.
- deletionRemove is an Entity Framework Core method that marks an entity for deletion when changes are saved.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
