When To Use Single or Split Queries in Entity Framework

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.

Single Query

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.

Entity Framework Query

var singleQuery = dbContext.Users
    .Include(u => u.Locations)
    .Where(u => u.UserID == 1)
    .ToList();

Translated SQL Query

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]

Result Set

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.

Split Query

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.

Entity Framework Query

var singleQuery = dbContext.Users
    .Include(u => u.Locations)
    .Include(u => u.Purchases)
    .ThenInclude(p => p.PurchaseItems)
    .ThenInclude(pi => pi.Items)
    .ToList();

Translated SQL Query

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]

Result Set

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.

Entity Framework Query

var splitQuery = dbContext.Users
    .Include(u => u.Locations)
    .Include(u => u.Purchases)
    .ThenInclude(p => p.PurchaseItems)
    .AsSplitQuery()
    .ToList();

Translated SQL Query

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]

Result Set 1 (User)

UserID EmailAddress FirstName LastName
1 johndoe@structdevelopment.com John Doe
2 janedoe@structdevelopment.com Jane Doe

Result Set 2 (Locations)

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

Result Set 3 (Purchases)

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

Result Set 4 (Purchase Items)

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

Result Set 5 (Item Definitions)

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.

Both Options 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:

Final Notes

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.