The purpose of this article is to examine the difference between single and split queries, and to identify the advantages to using one over the other.
Entity Framework by default runs its queries as single queries. This means the entity you request along with all related or included entities are resolved in a single query. Consider the first example below in which a single user and its locations are queried.
var singleQuery = dbContext.Users
.Include(u => u.Locations)
.Where(u => u.UserID == 1)
.ToList();
SELECT
[u].[UserID],
[u].[EmailAddress],
[u].[FirstName],
[u].[LastName],
[l].[LocationID],
[l].[City],
[l].[State],
[l].[StreetAddress1],
[l].[StreetAddress2],
[l].[UserID],
[l].[ZipCode]
FROM
[User].[Users] AS [u]
LEFT JOIN [User].[Locations] AS [l] ON [u].[UserID] = [l].[UserID]
WHERE
[u].[UserID] = 1
ORDER BY
[u].[UserID]
UserID | EmailAddress | FirstName | LastName | LocationID | City | State | StreetAddress1 | StreetAddress2 | UserID | ZipCode |
---|---|---|---|---|---|---|---|---|---|---|
1 | johndoe@structdevelopment.com | John | Doe | 1 | New York | NY | 20 W 34th St. | 1 | 10001 | |
1 | johndoe@structdevelopment.com | John | Doe | 2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 |
Entity Framework uses a join to gather data about the user and its locations in a single query. If a one-to-many relationship is present, this single query will result in duplicate data being returned. If more joins are present with one-to-many or many-to-many relationships, then this data duplication may increase exponentially as all the data from all the tables are present in each row.
This problem may be solved by using Entity Framework's split query functionality.
Entity Framework can be instructed to run its queries as split queries. This means the entity you request and each of its related or included entities are resovled in their own separate queries. Consider the second example below in which all users, all their locations, and all their purchases are queried.
var singleQuery = dbContext.Users
.Include(u => u.Locations)
.Include(u => u.Purchases)
.ThenInclude(p => p.PurchaseItems)
.ThenInclude(pi => pi.Items)
.ToList();
SELECT
[u].[UserID],
[u].[EmailAddress],
[u].[FirstName],
[u].[LastName],
[l].[LocationID],
[l].[City],
[l].[State],
[l].[StreetAddress1],
[l].[StreetAddress2],
[l].[UserID],
[l].[ZipCode],
[t0].[PurchaseID],
[t0].[PurchaseDate],
[t0].[PurchaserUserID],
[t0].[PurchaseItemID],
[t0].[ItemID],
[t0].[PurchaseID0],
[t0].[ItemID0],
[t0].[ItemName]
FROM
[User].[Users] AS [u]
LEFT JOIN [User].[Locations] AS [l] ON [u].[UserID] = [l].[UserID]
LEFT JOIN (
SELECT
[p].[PurchaseID],
[p].[PurchaseDate],
[p].[PurchaserUserID],
[t].[PurchaseItemID],
[t].[ItemID],
[t].[PurchaseID] AS [PurchaseID0],
[t].[ItemID0],
[t].[ItemName]
FROM
[Purchase].[Purchases] AS [p]
LEFT JOIN (
SELECT
[p0].[PurchaseItemID],
[p0].[ItemID],
[p0].[PurchaseID],
[i].[ItemID] AS [ItemID0],
[i].[ItemName]
FROM
[Purchase].[PurchaseItems] AS [p0]
LEFT JOIN [Purchase].[Items] AS [i] ON [p0].[PurchaseItemID] = [i].[ItemID]
) AS [t] ON [p].[PurchaseID] = [t].[PurchaseID]
) AS [t0] ON [u].[UserID] = [t0].[PurchaserUserID]
ORDER BY
[u].[UserID],
[l].[LocationID],
[t0].[PurchaseID],
[t0].[PurchaseItemID]
UserID | EmailAddress | FirstName | LastName | LocationID | City | State | StreetAddress1 | StreetAddress2 | UserID | ZipCode | PurchaseID | PurchaseDate | PurchaserUserID | PurchaseItemID | ItemID | PurchaseID0 | ItemID0 | ItemName |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | johndoe@structdevelopment.com | John | Doe | 1 | New York | NY | 20 W 34th St. | 1 | 10001 | 1 | 2024-01-15 02:17:42 | 1 | 2 | 4 | 1 | 2 | Strawberry | |
1 | johndoe@structdevelopment.com | John | Doe | 1 | New York | NY | 20 W 34th St. | 1 | 10001 | 2 | 2024-01-14 02:17:42 | 1 | 3 | 3 | 2 | 3 | Chocolate | |
1 | johndoe@structdevelopment.com | John | Doe | 1 | New York | NY | 20 W 34th St. | 1 | 10001 | 2 | 2024-01-14 02:17:42 | 1 | 4 | 4 | 2 | 4 | Mint Chocolate Chip | |
1 | johndoe@structdevelopment.com | John | Doe | 1 | New York | NY | 20 W 34th St. | 1 | 10001 | 2 | 2024-01-14 02:17:42 | 1 | 5 | 1 | 2 | 5 | Butter Pecan | |
1 | johndoe@structdevelopment.com | John | Doe | 1 | New York | NY | 20 W 34th St. | 1 | 10001 | 3 | 2024-01-17 02:17:42 | 1 | 9 | 6 | 3 | NULL | NULL | |
1 | johndoe@structdevelopment.com | John | Doe | 1 | New York | NY | 20 W 34th St. | 1 | 10001 | 3 | 2024-01-17 02:17:42 | 1 | 10 | 5 | 3 | NULL | NULL | |
1 | johndoe@structdevelopment.com | John | Doe | 1 | New York | NY | 20 W 34th St. | 1 | 10001 | 3 | 2024-01-17 02:17:42 | 1 | 11 | 4 | 3 | NULL | NULL | |
1 | johndoe@structdevelopment.com | John | Doe | 1 | New York | NY | 20 W 34th St. | 1 | 10001 | 3 | 2024-01-17 02:17:42 | 1 | 12 | 2 | 3 | NULL | NULL | |
1 | johndoe@structdevelopment.com | John | Doe | 1 | New York | NY | 20 W 34th St. | 1 | 10001 | 3 | 2024-01-17 02:17:42 | 1 | 13 | 1 | 3 | NULL | NULL | |
1 | johndoe@structdevelopment.com | John | Doe | 2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 | 1 | 2024-01-15 02:17:42 | 1 | 2 | 4 | 1 | 2 | Strawberry | |
1 | johndoe@structdevelopment.com | John | Doe | 2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 | 2 | 2024-01-14 02:17:42 | 1 | 3 | 3 | 2 | 3 | Chocolate | |
1 | johndoe@structdevelopment.com | John | Doe | 2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 | 2 | 2024-01-14 02:17:42 | 1 | 4 | 4 | 2 | 4 | Mint Chocolate Chip | |
1 | johndoe@structdevelopment.com | John | Doe | 2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 | 2 | 2024-01-14 02:17:42 | 1 | 5 | 1 | 2 | 5 | Butter Pecan | |
1 | johndoe@structdevelopment.com | John | Doe | 2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 | 3 | 2024-01-17 02:17:42 | 1 | 9 | 6 | 3 | NULL | NULL | |
1 | johndoe@structdevelopment.com | John | Doe | 2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 | 3 | 2024-01-17 02:17:42 | 1 | 10 | 5 | 3 | NULL | NULL | |
1 | johndoe@structdevelopment.com | John | Doe | 2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 | 3 | 2024-01-17 02:17:42 | 1 | 11 | 4 | 3 | NULL | NULL | |
1 | johndoe@structdevelopment.com | John | Doe | 2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 | 3 | 2024-01-17 02:17:42 | 1 | 12 | 2 | 3 | NULL | NULL | |
1 | johndoe@structdevelopment.com | John | Doe | 2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 | 3 | 2024-01-17 02:17:42 | 1 | 13 | 1 | 3 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 3 | Dallas | TX | 300 Reunion Blvd E | 2 | 75207 | 4 | 2024-01-13 02:17:57 | 2 | 14 | 4 | 4 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 3 | Dallas | TX | 300 Reunion Blvd E | 2 | 75207 | 4 | 2024-01-13 02:17:57 | 2 | 15 | 4 | 4 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 3 | Dallas | TX | 300 Reunion Blvd E | 2 | 75207 | 4 | 2024-01-13 02:17:57 | 2 | 16 | 2 | 4 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 3 | Dallas | TX | 300 Reunion Blvd E | 2 | 75207 | 4 | 2024-01-13 02:17:57 | 2 | 17 | 1 | 4 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 3 | Dallas | TX | 300 Reunion Blvd E | 2 | 75207 | 5 | 2024-01-16 02:17:57 | 2 | 6 | 4 | 5 | 6 | Rocky Road | |
2 | janedoe@structdevelopment.com | Jane | Doe | 3 | Dallas | TX | 300 Reunion Blvd E | 2 | 75207 | 5 | 2024-01-16 02:17:57 | 2 | 7 | 3 | 5 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 3 | Dallas | TX | 300 Reunion Blvd E | 2 | 75207 | 5 | 2024-01-16 02:17:57 | 2 | 8 | 2 | 5 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 3 | Dallas | TX | 300 Reunion Blvd E | 2 | 75207 | 6 | 2024-01-17 02:17:57 | 2 | 1 | 1 | 6 | 1 | Vanilla | |
2 | janedoe@structdevelopment.com | Jane | Doe | 4 | New York | NY | 89 E 42nd St | 2 | 10017 | 4 | 2024-01-13 02:17:57 | 2 | 14 | 4 | 4 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 4 | New York | NY | 89 E 42nd St | 2 | 10017 | 4 | 2024-01-13 02:17:57 | 2 | 15 | 4 | 4 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 4 | New York | NY | 89 E 42nd St | 2 | 10017 | 4 | 2024-01-13 02:17:57 | 2 | 16 | 2 | 4 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 4 | New York | NY | 89 E 42nd St | 2 | 10017 | 4 | 2024-01-13 02:17:57 | 2 | 17 | 1 | 4 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 4 | New York | NY | 89 E 42nd St | 2 | 10017 | 5 | 2024-01-16 02:17:57 | 2 | 6 | 4 | 5 | 6 | Rocky Road | |
2 | janedoe@structdevelopment.com | Jane | Doe | 4 | New York | NY | 89 E 42nd St | 2 | 10017 | 5 | 2024-01-16 02:17:57 | 2 | 7 | 3 | 5 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 4 | New York | NY | 89 E 42nd St | 2 | 10017 | 5 | 2024-01-16 02:17:57 | 2 | 8 | 2 | 5 | NULL | NULL | |
2 | janedoe@structdevelopment.com | Jane | Doe | 4 | New York | NY | 89 E 42nd St | 2 | 10017 | 6 | 2024-01-17 02:17:57 | 2 | 1 | 1 | 6 | 1 | Vanilla |
Since there are more relationships to resolve, more data duplication has occurred. Consider the third example, in which the same query is re-run but with split query enabled.
var splitQuery = dbContext.Users
.Include(u => u.Locations)
.Include(u => u.Purchases)
.ThenInclude(p => p.PurchaseItems)
.AsSplitQuery()
.ToList();
SELECT
[u].[UserID],
[u].[EmailAddress],
[u].[FirstName],
[u].[LastName]
FROM
[User].[Users] AS [u]
ORDER BY
[u].[UserID]
SELECT
[l].[LocationID],
[l].[City],
[l].[State],
[l].[StreetAddress1],
[l].[StreetAddress2],
[l].[UserID],
[l].[ZipCode],
[u].[UserID]
FROM
[User].[Users] AS [u]
INNER JOIN [User].[Locations] AS [l] ON [u].[UserID] = [l].[UserID]
ORDER BY
[u].[UserID]
SELECT
[p].[PurchaseID],
[p].[PurchaseDate],
[p].[PurchaserUserID],
[u].[UserID]
FROM
[User].[Users] AS [u]
INNER JOIN [Purchase].[Purchases] AS [p] ON [u].[UserID] = [p].[PurchaserUserID]
ORDER BY
[u].[UserID],
[p].[PurchaseID]
SELECT
[p0].[PurchaseItemID],
[p0].[ItemID],
[p0].[PurchaseID],
[u].[UserID],
[p].[PurchaseID]
FROM
[User].[Users] AS [u]
INNER JOIN [Purchase].[Purchases] AS [p] ON [u].[UserID] = [p].[PurchaserUserID]
INNER JOIN [Purchase].[PurchaseItems] AS [p0] ON [p].[PurchaseID] = [p0].[PurchaseID]
ORDER BY
[u].[UserID],
[p].[PurchaseID],
[p0].[PurchaseItemID]
SELECT
[i].[ItemID],
[i].[ItemName],
[u].[UserID],
[p].[PurchaseID],
[p0].[PurchaseItemID]
FROM
[User].[Users] AS [u]
INNER JOIN [Purchase].[Purchases] AS [p] ON [u].[UserID] = [p].[PurchaserUserID]
INNER JOIN [Purchase].[PurchaseItems] AS [p0] ON [p].[PurchaseID] = [p0].[PurchaseID]
INNER JOIN [Purchase].[Items] AS [i] ON [p0].[PurchaseItemID] = [i].[ItemID]
ORDER BY
[u].[UserID],
[p].[PurchaseID],
[p0].[PurchaseItemID]
UserID | EmailAddress | FirstName | LastName |
---|---|---|---|
1 | johndoe@structdevelopment.com | John | Doe |
2 | janedoe@structdevelopment.com | Jane | Doe |
LocationID | City | State | StreetAddress1 | StreetAddress2 | UserID | ZipCode | UserID |
---|---|---|---|---|---|---|---|
1 | New York | NY | 20 W 34th St. | 1 | 10001 | 1 | |
2 | Chicago | IL | 233 S Wacker Dr | 1 | 60606 | 1 | |
3 | Dallas | TX | 300 Reunion Blvd E | 2 | 75207 | 2 | |
4 | New York | NY | 89 E 42nd St | 2 | 10017 | 2 |
PurchaseID | PurchaseDate | PurchaserUserID | UserID |
---|---|---|---|
1 | 2024-01-15 02:17:42 | 1 | 1 |
2 | 2024-01-14 02:17:42 | 1 | 1 |
3 | 2024-01-17 02:17:42 | 1 | 1 |
4 | 2024-01-13 02:17:57 | 2 | 2 |
5 | 2024-01-16 02:17:57 | 2 | 2 |
6 | 2024-01-17 02:17:57 | 2 | 2 |
PurchaseItemID | ItemID | PurchaseID | UserID | PurchaseID |
---|---|---|---|---|
2 | 4 | 1 | 1 | 1 |
3 | 3 | 2 | 1 | 2 |
4 | 4 | 2 | 1 | 2 |
5 | 1 | 2 | 1 | 2 |
9 | 6 | 3 | 1 | 3 |
10 | 5 | 3 | 1 | 3 |
11 | 4 | 3 | 1 | 3 |
12 | 2 | 3 | 1 | 3 |
13 | 1 | 3 | 1 | 3 |
14 | 4 | 4 | 2 | 4 |
15 | 4 | 4 | 2 | 4 |
16 | 2 | 4 | 2 | 4 |
17 | 1 | 4 | 2 | 4 |
6 | 4 | 5 | 2 | 5 |
7 | 3 | 5 | 2 | 5 |
8 | 2 | 5 | 2 | 5 |
1 | 1 | 6 | 2 | 6 |
ItemID | ItemName | UserID | PurchaseID | PurchaseItemID |
---|---|---|---|---|
2 | Strawberry | 1 | 1 | 2 |
3 | Chocolate | 1 | 2 | 3 |
4 | Mint Chocolate Chip | 1 | 2 | 4 |
5 | Butter Pecan | 1 | 2 | 5 |
6 | Rocky Road | 2 | 5 | 6 |
1 | Vanilla | 2 | 6 | 1 |
Notice that multiple queries have now been run. One query resolves the user. Another query resolves the related locations. A third query resolves the related purchases. A fourth query resolves the items of each purchase. The last query resolves the definitions of each of those items. The use of five separate SQL queries is abstracted from the caller, as Entity Framework manages the relationships in the background. This has obvious advantages in removing data duplication. However, a major disadvantage of using split queries is each query is performed in a separate round trip to the database. This means when determining whether to use split queries or a single query, the trade-off of data duplication with network latency must be considered.
Single query and split queries both have their uses. Like most performance considerations in a database, deciding which to use comes down to knowing the data and anticipating how it will be used. The factors to consider when determining which to use are as follows:
Microsoft has published an article on the use of split queries vs. single queries. More information about the considerations of this decision can be found there.