When retrieving data from your database with Entity Framework, there are two major options: ToArray
and ToList
. Besides the different return type, is there any significant difference in performance between the two? Let's find out!
The setup
For this to answer, I will use Sqlite
with an in memory database to keep the variation as small as possible. The database will hold a BlogPost
table with 10'000 records in total. Here the model:
public class BlogPost
{
public int Id { get; set; }
public required string Title { get; set; }
public required string Subtitle { get; set; }
public DateTime PublishDate { get; set; }
public int Likes { get; set; }
}
The configuration of the rest is also pretty minimal:
public class BlogPostConfiguration : IEntityTypeConfiguration<BlogPost>
{
public void Configure(EntityTypeBuilder<BlogPost> builder)
{
builder.Property(b => b.Title)
.HasMaxLength(4000)
.IsRequired();
builder.Property(b => b.Subtitle)
.HasMaxLength(4000)
.IsRequired();
}
}
I also created a little data seeder to fill the database with some random data:
public static class DataSeeder
{
public static void Seed(BlogContext context, int numberOfPosts)
{
var blogPosts = new List<BlogPost>();
for (var i = 1; i <= numberOfPosts; i++)
{
blogPosts.Add(new BlogPost
{
Title = $"Title {i}",
Subtitle = $"Subtitle {i}",
PublishDate = DateTime.Now.AddDays(-i),
Likes = i
});
}
context.BlogPosts.AddRange(blogPosts);
context.SaveChanges();
}
}
The Benchmark
The main methods I want to test are:
[Benchmark]
public async Task<List<BlogPost>> ToListAsyncBenchmark()
{
return await _context!.BlogPosts.Take(NumberOfElements).ToListAsync();
}
[Benchmark]
public async Task<BlogPost[]> ToArrayAsyncBenchmark()
{
return await _context!.BlogPosts.Take(NumberOfElements).ToArrayAsync();
}
And some ceremonial code around:
[MemoryDiagnoser]
public class ToArrayVsToListBenchmark
{
private BlogContext? _context;
private DbConnection? _connection;
[GlobalSetup(Targets = [nameof(ToArrayAsyncBenchmark), nameof(ToListAsyncBenchmark)])]
public void Setup()
{
_connection = CreateInMemoryConnection();
var options = new DbContextOptionsBuilder()
.UseSqlite(_connection)
.Options;
_context = new BlogContext(options);
_context.Database.EnsureDeleted();
_context.Database.EnsureCreated();
DataSeeder.Seed(_context, 10000);
}
[Params(100, 1000, 10000)]
public int NumberOfElements { get; set; }
Let's run that bad boy!
The Results
BenchmarkDotNet v0.14.0, macOS Sequoia 15.0 (24A335) [Darwin 24.0.0]
Apple M2 Pro, 1 CPU, 12 logical and 12 physical cores
.NET SDK 9.0.100-rc.1.24452.12
[Host] : .NET 8.0.8 (8.0.824.36612), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.8 (8.0.824.36612), Arm64 RyuJIT AdvSIMD
| Method | NumberOfElements | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
|---------------------- |----------------- |------------:|----------:|----------:|---------:|--------:|-------:|-----------:|
| ToListAsyncBenchmark | 100 | 40.67 us | 0.494 us | 0.438 us | 3.7842 | - | - | 30.95 KB |
| ToArrayAsyncBenchmark | 100 | 41.54 us | 0.637 us | 0.565 us | 3.7842 | - | - | 31.83 KB |
| ToListAsyncBenchmark | 1000 | 332.61 us | 2.188 us | 1.940 us | 31.7383 | 0.9766 | - | 263 KB |
| ToArrayAsyncBenchmark | 1000 | 330.59 us | 5.548 us | 5.190 us | 32.7148 | 0.9766 | - | 270.91 KB |
| ToListAsyncBenchmark | 10000 | 3,355.47 us | 36.660 us | 34.292 us | 320.3125 | 39.0625 | 7.8125 | 2682.84 KB |
| ToArrayAsyncBenchmark | 10000 | 3,408.63 us | 40.163 us | 35.603 us | 328.1250 | 39.0625 | 7.8125 | 2761.1 KB |
All in all, the difference is negligible. The ToList
method is slightly faster with a bit less memory. The reason might be that the ToArray
method has to trim the array to the correct size, and therefore has to make a copy of the array. But just with that one simple object and random values, it isn't statistically significant.
So overall, you can use both methods without worrying about performance too much. Just use the one that fits your needs better. If you need a List
, use ToList
, if you need an array, use ToArray
. Simple as that!
Resources
- Code for this blog post: https://github.com/linkdotnet/BlogExamples/tree/main/BenchmarkToArrayToListEF
- All of my examples: https://github.com/linkdotnet/BlogExamples
Edit: see the comment from @aunikitin:
Probably, it worth watching what is going underhood and life become match more simple:
public static async Task<TSource[]> ToArrayAsync(
this IQueryable source,
CancellationToken cancellationToken = default)
=> (await source.ToListAsync(cancellationToken).ConfigureAwait(false)).ToArray();