How To Improve Performance With EF Core Query Splitting
Tags: efcore, dotnet, csharp
Published: 2023-09-10 07:47:01

---
title: How To Improve Performance With EF Core Query Splitting
published: true
date: 2022-09-17 00:00:00 UTC
tags: efcore,dotnet,csharp
canonical_url: https://www.milanjovanovic.tech/blog/how-to-improve-performance-with-ef-core-query-splitting
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9wzx8jij5o0yp4pevte.png
---

I recently ran into an issue with **Entity Framework Core**.

The query I was running was constantly timing out.

I tried to scale up the application server, and it didn't help.
 
I tried to scale up the database server, and it didn't help.

So how did I solve the problem?

## What Was The Problem With This Query?

I'm working on an application in the e-commerce domain. To be specific, it's an order management system for a kitchen cabinet manufacturer.

The table that I frequently query on is the `Orders` table. The `Order` can have one or more `LineItems`. A typical `Order` will contain 50 `LineItems`. Also, `LineItems` have a table that contains the valid dimensions - `LineItemDimensions`.

This is the query I was trying to run:

```csharp
dbContext
    .Orders
    .Include(order => order.LineItems)
    .ThenInclude(lineItem => lineItem.Dimensions)
    .First(order => order.Id == orderId);
```

When EF Core converts this into SQL, this is what it will send to the database:

```sql
SELECT o.*, li.*, d.*
FROM Orders o
LEFT JOIN LineItems li ON li.OrderId = o.Id
LEFT JOIN LineItemDimensions d ON d.LineItemId = li.Id
WHERE o.Id = @orderId
ORDER BY o.Id, li.Id, d.Id;
```

In most cases, this query will execute just fine.

However, in my situation I was running into the problem of _Cartesian Explosion_. This is mainly because of the join to the `LineItemDimensions` table. And this is what's causing my query to fail, and time out.

So how did I solve this problem?

## Query Splitting To The Rescue

With the release of **EF Core 5.0** we got a new feature called **Query Splitting**. This allows us to specify that a given LINQ query should be split into multiple `SQL` queries.

To use **Query Splitting** , all you need to do is call the `AsSplitQuery` method:

```csharp
dbContext
    .Orders
    .Include(order => order.LineItems)
    .ThenInclude(lineItem => lineItem.Dimensions)
    .AsSplitQuery()
    .First(order => order.Id == orderId);
```

In this case, EF Core will generate the following SQL queries:

```sql
SELECT o.*
FROM Orders o
WHERE o.Id = @orderId;

SELECT li.*
FROM LineItems li
JOIN Orders o ON li.OrderId = o.Id
WHERE o.Id = @orderId;

SELECT d.*
FROM LineItemDimensions d
JOIN LineItems li ON d.LineItemId = li.Id
JOIN Orders o ON li.OrderId = o.Id
WHERE o.Id = @orderId;
```

Notice that for each `Include` statement we have a separate `SQL` query. The benefit here is that we are not duplicating data when fetching from the database, as we were in the previous case.

## Turning On Query Splitting For All Queries

You can enable **Query Splitting** at the database context level. When configuring your database context you need to call the `UseQuerySplittingBehavior` method:

```csharp
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(
        "CONNECTION_STRING",
        o => o.UseQuerySplittingBehavior(
            QuerySplittingBehavior.SplitQuery)));
```

This will cause all queries that EF Core generates to be split queries. To revert back to a single query, you need to call the `AsSingleQuery` method:

```csharp
dbContext
    .Orders
    .Include(o => o.LineItems)
    .ThenInclude(li => li.Dimensions)
    .AsSingleQuery()
    .First(o => o.Id == orderId);
```

## What You Should Know About Query Splitting

Although query splitting is an excellent addition to EF Core, there are a few things you need to be aware of.

There is no consistency guarantee for multiple SQL queries. You may run into a problem if you have a concurrent update going through at the same time when you query your data. To mitigate this, you can wrap the queries inside of a transaction, but this will only introduce performance issues elsewhere.

Each query will require one network round trip. This can degrade performance if your latency to the database is high.

Now that you are armed with this knowledge, go and make your EF queries faster!

* * *

**P.S. Whenever you're ready, there are 2 ways I can help you:**

1. [**Pragmatic Clean Architecture:**](https://www.milanjovanovic.tech/pragmatic-clean-architecture?utm_source=dev.to&utm_medium=website&utm_campaign=cross-posting) This comprehensive course will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture. [**Join 950+ students here.**](https://www.milanjovanovic.tech/pragmatic-clean-architecture?utm_source=dev.to&utm_medium=website&utm_campaign=cross-posting)

2. [**Patreon Community:**](https://www.patreon.com/milanjovanovic) Think like a senior software engineer with **access to the source code** I use in my YouTube videos and **exclusive discounts** for my courses. [**Join 820+ engineers here.**](https://www.patreon.com/milanjovanovic)