Entity Framework compiled queries with EF 8.0.3 and .NET 8




Date Added (UTC):

07 Apr 2024 @ 20:18

Date Updated (UTC):

12 Apr 2024 @ 02:03


.NET Version(s):

.NET 8

Tag(s):

#EntityFramework


Added By:
Profile Image

Blog   
Ireland    
.NET Developer and tech lead from Ireland!

Benchmark Results:





Benchmark Code:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.EntityFrameworkCore;

[MemoryDiagnoser] 
public class CompiledQueries
{
    private static readonly Func<BloggingContext, IAsyncEnumerable<Blog>> _compiledQuery
        = EF.CompileAsyncQuery((BloggingContext context) => context.Blogs.Where(b => b.Url.StartsWith("http://")));

    private BloggingContext _context;

    [Params(1, 10)]
    public int NumBlogs { get; set; }

    [GlobalSetup]
    public async Task Setup()
    {
        using var context = new BloggingContext();
        await context.Database.EnsureDeletedAsync();
        await context.Database.EnsureCreatedAsync();
        await context.SeedDataAsync(NumBlogs);

        _context = new BloggingContext();
    }

    [Benchmark]
    public async ValueTask<int> WithCompiledQuery()
    {
        var idSum = 0;

        await foreach (var blog in _compiledQuery(_context))
        {
            idSum += blog.Id;
        }

        return idSum;
    }

    [Benchmark]
    public async ValueTask<int> WithoutCompiledQuery()
    {
        var idSum = 0;

        await foreach (var blog in _context.Blogs.Where(b => b.Url.StartsWith("http://")).AsAsyncEnumerable())
        {
            idSum += blog.Id;
        }

        return idSum;
    }

    [GlobalCleanup]
    public ValueTask Cleanup() => _context.DisposeAsync();

    public class BloggingContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder
                .UseSqlServer(@"Server=localhost;Database=Blogging;Trusted_Connection=True;TrustServerCertificate=true")
                .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);

        public async Task SeedDataAsync(int numBlogs)
        {
            Blogs.AddRange(Enumerable.Range(0, numBlogs).Select(i => new Blog { Url = $"http://www.someblog{i}.com" }));
            await SaveChangesAsync();
        }
    }

    public class Blog
    {
        public int Id { get; set; }
        public string Url { get; set; }
    }
}

// .NET 8 Lowered C# Code unavailable due to errors:
error CS0234: The type or namespace name 'EntityFrameworkCore' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)
error CS0246: The type or namespace name 'DbContext' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbSet<>' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbContextOptionsBuilder' could not be found (are you missing a using directive or an assembly reference?)
error CS0103: The name 'EF' does not exist in the current context
error CS1674: 'CompiledQueries.BloggingContext': type used in a using statement must be implicitly convertible to 'System.IDisposable'.
error CS1061: 'CompiledQueries.BloggingContext' does not contain a definition for 'Database' and no accessible extension method 'Database' accepting a first argument of type 'CompiledQueries.BloggingContext' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'CompiledQueries.BloggingContext' does not contain a definition for 'DisposeAsync' and no accessible extension method 'DisposeAsync' accepting a first argument of type 'CompiledQueries.BloggingContext' could be found (are you missing a using directive or an assembly reference?)
error CS0103: The name 'QueryTrackingBehavior' does not exist in the current context
error CS0103: The name 'SaveChangesAsync' does not exist in the current context

// .NET 8 IL Code unavailable due to errors:
error CS0234: The type or namespace name 'EntityFrameworkCore' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)
error CS0246: The type or namespace name 'DbContext' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbSet<>' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbContextOptionsBuilder' could not be found (are you missing a using directive or an assembly reference?)
error CS0103: The name 'EF' does not exist in the current context
error CS1674: 'CompiledQueries.BloggingContext': type used in a using statement must be implicitly convertible to 'System.IDisposable'.
error CS1061: 'CompiledQueries.BloggingContext' does not contain a definition for 'Database' and no accessible extension method 'Database' accepting a first argument of type 'CompiledQueries.BloggingContext' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'CompiledQueries.BloggingContext' does not contain a definition for 'DisposeAsync' and no accessible extension method 'DisposeAsync' accepting a first argument of type 'CompiledQueries.BloggingContext' could be found (are you missing a using directive or an assembly reference?)
error CS0103: The name 'QueryTrackingBehavior' does not exist in the current context
error CS0103: The name 'SaveChangesAsync' does not exist in the current context

// .NET 8 Jit Asm Code unavailable due to errors:
error CS0234: The type or namespace name 'EntityFrameworkCore' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)
error CS0246: The type or namespace name 'DbContext' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbSet<>' could not be found (are you missing a using directive or an assembly reference?)
error CS0246: The type or namespace name 'DbContextOptionsBuilder' could not be found (are you missing a using directive or an assembly reference?)
error CS0103: The name 'EF' does not exist in the current context
error CS1674: 'CompiledQueries.BloggingContext': type used in a using statement must be implicitly convertible to 'System.IDisposable'.
error CS1061: 'CompiledQueries.BloggingContext' does not contain a definition for 'Database' and no accessible extension method 'Database' accepting a first argument of type 'CompiledQueries.BloggingContext' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'CompiledQueries.BloggingContext' does not contain a definition for 'DisposeAsync' and no accessible extension method 'DisposeAsync' accepting a first argument of type 'CompiledQueries.BloggingContext' could be found (are you missing a using directive or an assembly reference?)
error CS0103: The name 'QueryTrackingBehavior' does not exist in the current context
error CS0103: The name 'SaveChangesAsync' does not exist in the current context


Benchmark Description:


In really high volume apps it may be desirable to use compiled queries. [Compiled queries](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics?tabs=with-di%2Cexpression-api-with-constant#compiled-queries)

This benchmark setup is designed to measure the performance difference between executing compiled queries versus non-compiled queries in Entity Framework Core (EF Core). The benchmarks are run using the BenchmarkDotNet library, which is a powerful tool for benchmarking .NET code. The specific .NET version isn't mentioned, but it's important to ensure that the project targets a version compatible with the EF Core version being used. EF Core's performance can vary significantly between versions, so using the latest stable version is generally recommended for the best performance and features. ### General Setup - **Database Context (`BloggingContext`)**: This is a custom `DbContext` representing a blogging application's data model. It includes a `DbSet<Blog>` to represent a collection of blogs in the database. - **Database Provider**: The setup uses SQL Server as the database provider, as indicated by the `UseSqlServer` method in the `OnConfiguring` method. It's configured for no-tracking queries to improve performance, especially in read scenarios. - **Data Seeding (`SeedDataAsync`)**: Before benchmarks run, the database is created and seeded with a specified number of blog entries. The `NumBlogs` parameter controls the number of blogs seeded, with benchmarks running for both 1 and 10 blogs to test performance under different data volumes. - **Memory Diagnoser**: Enabled by the `[MemoryDiagnoser]` attribute, this tool collects and reports memory allocation information, which is crucial for understanding the memory efficiency of the queries. ### Benchmark Methods #### 1. `WithCompiledQuery` - **Purpose**: This method measures the performance of executing a compiled query. Compiled queries in EF Core are a way to pre-compile the LINQ query logic into a delegate, which can potentially improve performance by reducing the cost of query compilation on subsequent executions. - **Performance Aspect**: It specifically tests the execution time and memory allocations involved in iterating over the results of a compiled query that filters blogs based on their URL starting with "http://". - **Expected Insights**: Running this benchmark can show how much overhead is saved by avoiding repeated query compilation. In scenarios where the same query is executed multiple times with different parameters, compiled queries can offer significant performance benefits. #### 2. `WithoutCompiledQuery` - **Purpose**: This method benchmarks the performance of executing a similar query without pre-compilation. It directly uses LINQ to query the `DbContext`, which means the query will be compiled at runtime each time it's executed. - **Performance Aspect**: Like the first method, it measures execution time and memory allocations but for non-compiled queries. This provides a direct comparison to see how much overhead query compilation adds. - **Expected Insights**: This benchmark is expected to highlight the potential performance cost of query compilation in EF Core. If the difference in performance between this method and `WithCompiledQuery` is significant, it underscores the value of using compiled queries for frequently executed operations. ### Conclusion By comparing the results of these two benchmarks, developers can gain valuable insights into the performance implications of query compilation in EF Core. This can guide optimization efforts, especially in high-load scenarios where reducing CPU and memory usage is critical. It's important to note that the benefits of compiled queries might vary depending on the complexity of the query, the size of the dataset, and the specific database provider used.


Benchmark Comments: