Closed ajcvickers closed 1 year ago
Benchmarks after implementation in #29686:
Method | Mean | Error | StdDev | Median |
---|---|---|---|---|
Search_entries_by_primary_key | 233,097.7 ns | 3,968.21 ns | 3,517.72 ns | 231,888.2 ns |
DbSet_Find_by_primary_key | 190.0 ns | 3.77 ns | 7.18 ns | 186.5 ns |
DbSet_Local_FindEntryByKey | 164.7 ns | 3.27 ns | 4.89 ns | 162.5 ns |
Search_entries_by_alternate_key | 257,479.3 ns | 2,309.13 ns | 1,928.23 ns | 258,148.0 ns |
DbSet_Local_FindEntryByProperty_alternate_key | 276.0 ns | 5.54 ns | 9.99 ns | 272.1 ns |
Search_entries_by_foreign_key | 878,557.5 ns | 4,196.98 ns | 3,720.51 ns | 879,371.1 ns |
DbSet_Local_GetEntriesByProperty_foreign_key | 1,493.8 ns | 25.26 ns | 36.23 ns | 1,478.4 ns |
Search_entries_by_non_key | 877,076.0 ns | 7,297.84 ns | 6,469.35 ns | 875,781.9 ns |
DbSet_Local_GetEntriesByProperty_non_key | 656,630.7 ns | 11,402.99 ns | 10,108.45 ns | 655,563.9 ns |
using System.ComponentModel.DataAnnotations.Schema;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
BenchmarkRunner.Run<Benchmarks>();
public class Benchmarks
{
private static readonly TestContext Context;
private static readonly IProperty AlternateKeyProperty;
private static readonly IProperty ForeignKeyProperty;
private static readonly IProperty NonKeyProperty;
static Benchmarks()
{
using (var context = new TestContext())
{
context.Seed();
}
Context = new TestContext();
Context.Principals.Include(e => e.Dependent1s).Include(e => e.Dependent2s).Load();
Context.ChangeTracker.AutoDetectChangesEnabled = false;
AlternateKeyProperty = Context.Principals.EntityType.FindProperty(nameof(Principal.AltId))!;
ForeignKeyProperty = Context.Dependent2s.EntityType.FindProperty(nameof(Dependent2.PrincipalId))!;
NonKeyProperty = Context.Dependent2s.EntityType.FindProperty(nameof(Dependent2.NonKey))!;
}
[Benchmark]
public void DbSet_Find_by_primary_key()
{
var entity = Context.Principals.Find(501);
}
[Benchmark]
public void Search_entries_by_primary_key()
{
foreach (var entry in Context.ChangeTracker.Entries<Principal>())
{
if (entry.Entity.Id == 501)
{
return;
}
}
}
[Benchmark]
public void DbSet_Local_FindEntryByKey()
{
var entry = Context.Principals.Local.FindEntryByKey(501);
}
[Benchmark]
public void Search_entries_by_alternate_key()
{
foreach (var entry in Context.ChangeTracker.Entries<Principal>())
{
if (entry.Entity.AltId == 501)
{
return;
}
}
}
[Benchmark]
public void DbSet_Local_FindEntryByProperty_alternate_key()
{
var entry = Context.Principals.Local.FindEntryByProperty(AlternateKeyProperty, 501);
}
[Benchmark]
public void Search_entries_by_foreign_key()
{
var results = new List<Dependent2>();
foreach (var entry in Context.ChangeTracker.Entries<Dependent2>())
{
if (entry.Entity.PrincipalId == 501)
{
results.Add(entry.Entity);
}
}
}
[Benchmark]
public void DbSet_Local_GetEntriesByProperty_foreign_key()
{
var results = new List<Dependent2>();
foreach (var entry in Context.Dependent2s.Local.GetEntriesByProperty(ForeignKeyProperty, 501))
{
if (entry.Entity.PrincipalId == 501)
{
results.Add(entry.Entity);
}
}
}
[Benchmark]
public void Search_entries_by_non_key()
{
var results = new List<Dependent2>();
foreach (var entry in Context.ChangeTracker.Entries<Dependent2>())
{
if (entry.Entity.NonKey == 501)
{
results.Add(entry.Entity);
}
}
}
[Benchmark]
public void DbSet_Local_GetEntriesByProperty_non_key()
{
var results = new List<Dependent2>();
foreach (var entry in Context.Dependent2s.Local.GetEntriesByProperty(NonKeyProperty, 501))
{
if (entry.Entity.NonKey == 501)
{
results.Add(entry.Entity);
}
}
}
}
public class TestContext : DbContext
{
public DbSet<Principal> Principals { get; set; } = null!;
public DbSet<Dependent1> Dependent1s { get; set; } = null!;
public DbSet<Dependent2> Dependent2s { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseInMemoryDatabase("X");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Principal>()
.HasMany(e => e.Dependent2s)
.WithOne(e => e.Principal)
.HasPrincipalKey(e => e.AltId);
}
public void Seed()
{
for (var i = 1; i <= 1000; i++)
{
var principal = new Principal { Id = i, AltId = i };
for (var j = 1; j <= 20; j++)
{
principal.Dependent1s.Add(new Dependent1 { Id = (i * 20) + j, NonKey = i });
principal.Dependent2s.Add(new Dependent2 { Id = (i * 20) + j, NonKey = i });
}
Add(principal);
}
SaveChanges();
}
}
public class Principal
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AltId { get; set; }
public List<Dependent1> Dependent1s { get; } = new();
public List<Dependent2> Dependent2s { get; } = new();
}
public class Dependent1
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public int? PrincipalId { get; set; }
public int NonKey { get; set; }
public Principal? Principal { get; set; }
}
public class Dependent2
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public int? PrincipalId { get; set; }
public int NonKey { get; set; }
public Principal? Principal { get; set; }
}
Using the internal indexes that the change tracker already builds for fixup. Currently this can only be done by iterating over all tracked entities.
Split off from #7391.