Query Splitting: Improving Query Performance with Entity Framework

When working with Entity Framework, we often need to load related data between tables. The ease of using methods like “Include” and “ThenInclude” to load these relationships is one of the advantages of Entity Framework, but it can also introduce performance challenges. In this article, we will explore how the Query Splitting feature can help optimize queries involving multiple related tables.

The problem of "Cartesian Explosion"

Before diving into the solution, it is essential to understand the problem that Query Splitting solves. Imagine a typical scenario where we have two related tables: Blog and Post. A blog can have many posts, representing a one-to-many relationship. To load a blog along with all its posts, we can use the following code:

 
				
					var blogs = context.Blogs
                   .Include(b => b.Posts)
                   .ToList();
				
			

By default, Entity Framework will generate a SQL query that uses a “JOIN” to combine the “Blogs” and “Posts” tables. When these tables contain many records, especially in a many-to-many relationship, the result can be a combination of all possible matches. See the generated SQL query:

				
					SELECT [b].[BlogId], [b].[Description], [b].[Title], [p].[PostId], [p].[BlogId], [p].[Content],
[p].[CreatedAt], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]
				
			

This phenomenon is known as cartesian explosion, where the number of rows in the query result grows exponentially, causing:

  • Degraded performance: the query can become very slow due to the large volume of data being processed.

  • Excessive memory usage: loading a large number of records overloads the server’s memory, impacting other operations.

  • Increased response time: the time to transport the data from the database to the application increases, impacting the user experience.

Introducing Query Splitting

Query Splitting is a technique that helps mitigate the problems caused by the cartesian explosion. Instead of executing a single complex query with multiple “JOINs”, Entity Framework divides the query into several smaller queries. By doing this, it first loads the data of the main entity and then loads the related data using additional queries.

Imagine that we want to load all “Blogs” with their respective “Posts”, but we want to avoid the impact of a large “JOIN”. We can use the “AsSplitQuery” method to split the query:

				
					var blogs = context.Blogs
                   .Include(b => b.Posts)
                   .AsSplitQuery()
                   .ToList();
				
			

When we use “AsSplitQuery()”, Entity Framework executes the queries separately, resulting in two distinct queries:

				
					SELECT [b].[BlogId], [b].[Description], [b].[Title]
FROM [Blogs] AS [b]
ORDER BY [b].[BlogId]
				
			
				
					SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[CreatedAt], [p].[Title], [b].[BlogId]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]
				
			

With “AsSplitQuery()”, Entity Framework executes one query to load all “Blogs” and then a separate query for each set of related “Posts”. This approach reduces the amount of redundant data transferred and avoids the excessive combination of records.

Advantages of Query Splitting

  • Reduction of redundant data: as each query focuses only on loading the necessary data for the current entity, there is less duplicate data.

  • Improved performance in specific scenarios: for queries where the combination of data can generate many rows (due to many-to-many relationships), splitting the query can result in a shorter total execution time.

  • Better memory usage: smaller queries mean less memory usage on the server, which is ideal for applications with heavy load or environments with limited resources.

When to use Query Splitting?

Query Splitting is not always the best solution. It is most effective in scenarios where the cartesian explosion problem is about to occur or is already causing performance issues. If the generated query normally returns a reasonable number of records and latency is not a problem, a single query with “JOINs” may be more efficient. However, in situations where you observe significant performance problems due to queries with many “JOINs”, especially with large tables and complex relationships, Query Splitting can be a valuable optimization technique.

Conclusion

In summary, Query Splitting is not a magic solution for all performance problems, but a very useful tool in the developer’s arsenal for specific scenarios where the intensive use of “JOINs” is causing significant bottlenecks.