Interacting with Related
Data
Julie Lerman
EF Core Expert and Software Coach
@julielerman | thedatafarm.com
Focus on the one-to-many authors & books
Module
Inserting related data
Overview
Eager loading queries and shaping results
with projections
Loading related data for objects in memory
Filtering queries with related data
Update and delete related data
Insights into persisting untracked graphs
Bette Davis in “All About Eve”
"Fasten your seat belts, it's
gonna be a bumpy night."
Bette Davis in “All About Eve”
"Fasten your seat belts, it's
gonna be a bumpy night."
ing modu le
in te r e st
an
Adding Related Data
+
Add New Parent and New Child
Together
Let’s add new authors and their new books
One-to-Many in the Database
The relationship Constraints of this relationship
Views from SQL Server Management Studio
Object Graph
A connected set of related objects
Any Object Can Be the Head of a Graph
Author Graph Book Graph
An author with some A book with an author and
books in memory its book jacket in memory
+
Add a New Child to an Existing Parent
Our authors keep writing new books.
Let’s get those books into the database
Change Tracker Response
to New Child of Existing Parent
As child’s key value is Child’s FK value to parent
not set, state will (e.g. Book.AuthorId)
automatically be “Added” is set to parent’s key
Reminder: DbContext/DbSet Tracking Methods
Add Update Remove Attach
Change Tracker Response
to New Child of Existing Parent
Set foreign key
Add child to child Add existing
property in child
collection of tracked parent to
class to parent’s
existing tracked ref property of
key value.
parent. child.
Add &
SaveChanges SaveChanges
SaveChanges
Beware accidental
inserts!
Passing a pre-existing entity into its
DbSet Add will cause EF Core to try to
insert it into the database!
EF Core’s Default Entity State of Graph Data
Has Key Value No Key Value
Add(graph) Added Added
Update(graph)
Attach(graph)
EF Core’s Default Entity State of Graph Data
Has Key Value No Key Value
Add(graph) Added* Added
Update(graph) *Database will throw an exception
unless IDENTITY INSERT is explicitly
enabled and the key doesn’t already
exist
Attach(graph)
Understand
how your tools work!
“Foreign keys? NEVER!
They will make my classes dirty!”
“Foreign keys in my classes
make my life so much simpler!”
Some of this change
tracking behavior is
different in disconnected
scenarios.
Eager Loading Related Data in Queries
Means to Get Related Data from the Database
Query Projections
Eager Loading
Define the shape of query
Include related objects in query
results
Lazy Loading Explicit Loading
Explicitly request related data
On-the-fly retrieval of data for
related to objects in memory objects in memory
*Arrived with EF Core 2.1
Query Workflow
Receives Materializes
tabular results results as objects
Authors Books for
those Adds tracking
Authors details to
DbContext
instance
DbContext
connects the
relationships
Filtering & Sorting the Included Data
Long requested
By default, the You can filter and
feature that finally
entire collection is sort the related
arrived in
retrieved data
EF Core 5!
Composing Include with Other LINQ Methods
_context.Authors.Where(a=>a.LastName.StartsWith("L"))
.Include(a=>a.Books).ToList()
_context.Authors.Where(a => a.LastName == "Lerman")
.Include(a => a.Books).FirstOrDefault()
_context.Authors.Where(a => a.LastName == "Lerman")
.FirstOrDefault().Include(a => a.Books)
_context.Authors.Find(1).Include(a=>a.Books)
_context.Authors.Include(a=>a.Books).Find(1)
Remember, Find is not a LINQ method
Using Include for Multiple Layers of Relationships
_context.Authors
.Include(a => a.Books) t Get books for each author
.ThenInclude(b=>b.BookJackets) t Then get the jackets for each book
.ToList();
_context.Authors
t Get books for each author
.Include(a => a.Books)
.Include(a=>a.ContactInfo) t Also get the contact info each author
.ToList();
t Get the jackets for each author’s books
_context.Authors
.Include(a=>a.Books.BookJackets) (But don’t get the books)
.ToList();
Performance Considerations with Include
Composing many Includes in one Include defaults to a single SQL
query could create performance command. Use AsSplitQuery() to
issues. Monitor your queries! send multiple SQL commands
instead.
SQL Generated from Includes
Query is broken up
Single query
into multiple queries sent
with LEFT JOIN(s)
in a single command
Default With AsSplitQuery()
Projecting Related Data in Queries
Means to Get Related Data from the Database
Query Projections
Eager Loading
Define the shape of query
Include related objects in query
results
Lazy Loading Explicit Loading
Explicitly request related data
On-the-fly retrieval of data for
related to objects in memory objects in memory
var someType=_context.Authors
.Select(properties into a new type)
.ToList()
var someType=_context.Authors
.Select(a=>new {a.FirstName, a.LastName, a.Books.Count() }
.ToList()
someType structure:
FirstName
LastName
Books
Projecting into Undefined (“Anonymous”) Types
Use LINQ’s Select method
Use a lambda expression to specify properties to retrieve
Instantiate a type to capture the resulting structure
Anonymous types are not available outside of the method
EF Core Can Only Track Entities Known by DbContext
Anonymous types Entities that are properties
of an anonymous type
are not tracked are tracked
Loading Related Data for Objects
Already in Memory
Means to Get Related Data from the Database
Query Projections
Eager Loading
Define the shape of query
Include related objects in query
results
Loading related data for objects already in memory
Means to Get Related Data from the Database
Query Projections
Eager Loading
Define the shape of query
Include related objects in query
results
Explicit Loading
Lazy Loading
Explicitly request related data
On-the-fly retrieval of data
for related to objects in memory
objects in memory
With author object already in memory, load a collection
_context.Entry(author).Collection(a => a.Books).Load();
With book object already in memory, load a reference (e.g., parent or 1:1)
_context.Entry(book).Reference(b => b.Author).Load();
Explicit Loading
Explicitly retrieve related data for objects already in memory
DbContext.Entry(object).Collection().Load()
DbContext.Entry(object).Reference().Load()
More on Explicit Loading
You can only load from a single object Profile to determine if LINQ query
would be better performance
Filter on loading using Query() method var newfBooks =
context.Entry(author)
.Collection(a => a.Books)
.Query().Where(b =>
b.Title.Contains(“Newf”)
.ToList();
Using Lazy Loading to Retrieve Related
Data
Means to Get Related Data from the Database
Query Projections
Eager Loading
Define the shape of query
Include related objects in query
results
Explicit Loading Lazy Loading
Explicitly request related data for On-the-fly retrieval of data
objects in memory related to objects in memory
Lazy loading is easy to misuse!
I recommend some advanced learning before
using it.
Lazy Loading is OFF by default
Enabling Lazy Loading
Every navigation property in every entity must be virtual
e.g., public virtual List<Book> Books { get; set; }
Reference the Microsoft.EntityFramework.Proxies package
Use the proxy logic provided by that package
optionsBuilder.UseLazyLoadingProxies()
Some Good and Not So Good Ways to Use Lazy
Loading
Good Behavior
foreach(var b in author.Books) t One command to the db to get the books for
this one author
{
Console.WriteLine(b.Title);
}
Behavior to Avoid
var bookCount= author.Books.Count(); t Retrieves all the Book objects from the
database and materialize them and then give you
the count.
t Sends N+1 commands to the database as each
Data bind a grid to lazy-loaded data author’s book is loaded into a grid row
Lazy loading when no context in scope t No data is retrieved
Using Related Data to Filter Objects
Modifying Related Data
EF Core’s Default Entity State of Graph Data
Has Key Value No Key Value
Add(graph) Added Added
Update(graph) Modified Added
Attach(graph)
Connected Disconnected
DbContext is aware DbContext has no clue
of all changes made to objects about history of objects
that it is tracking before they are attached
(when DetectChanges is called)
Reminder: DbContext/DbSet Tracking Methods
Add Update Remove Attach
Attach starts tracking with state set to Unchanged
EF Core’s Default Entity State of Graph Data
Has Key Value No Key Value
Add(graph) Added Added
Update(graph) Modified Added
Attach(graph) Unchanged Added
The Challenge
Edited
Book
Author
Book Book
The Challenge
Edited
Book
Author
Book Book
The Challenge
_context.Entry
( Edited
Book
)
.State
Author
Book Book
The Challenge
_context.Entry
( Edited
Book
)
.State
Author
Book Book
DbContext.Entry gives you
a lot of fine-grained control
over the change tracker.
Understanding Deleting Within Graphs
Multiple Meanings of Remove/Delete
Remove from an Set State to Deleted Delete from database
in-memory collection in Change Tracker
Cascade Delete When Dependents
Can’t Be “Orphaned”
When parent is optional, the
children will not be deleted
When parent is optional, the
children will not be deleted
This was a breaking change in EF Core 7
I will start with a focus on
required parents and
cascade delete enabled.
Foreign Key Relationship Constraint in SQL Server
void DeleteAnAuthor()
{
var author = _context.Authors.Find(2);
_context.Authors.Remove(author);
_context.SaveChanges();
}
Database Enforces Cascade Delete
Only author is in memory, tracked by EF Core & marked “Deleted”
Database’s cascade delete will take care of books when author is deleted
EF Core Enforces
Cascade Deletes
Any related data that is also tracked
will be marked as Deleted along with
the principal object
It’s running the
DELETE FROM Books
while the books are still in
the database.
var author=_context.Authors t Retrieve an author with only *some* of their
.Include(a=>a.Books books
.Where(b=>b.Title=“XYZ”)
.FirstOrDefault();
t Mark the author as deleted
_context.Authors.Remove(author);
//Entry(author).State=Deleted t Change tracker will also mark that book as
//Entry(thatbook).State=Deleted deleted
DELETE thatbook FROM BOOKS t SaveChanges sends DELETE for that book and
DELETE for the author to database
DELETE thatauthor FROM AUTHORS
//database cascade delete any other t The database will delete any remaining books
books in that database for the author
A Few Last Questions about Cascade Delete
Question Answer
Will Remove() remove everything in a No. Remove will only remove the
graph, just like Update? specific object.
What about deleting a dependent that’s You’ve actually done this. If it’s in
not in a graph? memory, DbSet.Remove works. If it’s
not, then ExecuteDelete.
Some More Points About Removing Dependents
What about .... Examples
How to move a child from one parent to • book.AuthorId=3
another (when tracked)? • newAuthor.Books.Add(book)
• book.Author=newAuthor
What about optional relationships? • book.AuthorId=null
• author.Books.Remove(book)
What about deleting relationships in This is more advanced, but you will see
disconnected scenarios? some of it in the web app module.
Impact of Optional Relationship
No Cascade Delete in the Database No Cascade Delete in
ChangeTracker
You can eager load related data with
Include() or projections.
Lazy and explicit loading let you load after
Review the fact.
Pay attention to lazy loading behavior.
Filter the related data or use it to filter the
base data.
Adding, modifying or deleting data in
graphs has varying impacts on the related
objects.
DbContext.Entry() isolates and affects only
the object you pass in.
Up Next:
Defining and Using Many-to-Many
Relationships
Resources
Entity Framework Core on GitHub github.com/dotnet/efcore
EF Core Documentation learn.microsoft.com/ef/core
Lazy Loading With and Without Proxies
(Module from EF Core 2.1 What’s New) bit.ly/EFCoreLazy