-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Thanksgiving project: lookup entities by primary key, alternate key, or foreign key #29686
Conversation
Benchmarks:
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; }
} |
14e8736
to
58ff853
Compare
src/EFCore/ChangeTracking/Internal/CompositePrincipalKeyValueFactory.cs
Outdated
Show resolved
Hide resolved
src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs
Outdated
Show resolved
Hide resolved
/// <para> | ||
/// Call <see cref="ChangeTracker.DetectChanges" /> before calling this method to ensure all entries returned reflect | ||
/// up-to-date state. | ||
/// </para> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might need something in the API to indicate whether a DetectChanges
is needed for any particular call. Or perhaps we just call it always ourselves unless the user opts-out temporarily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do this in another PR so that it can be reviewed.
58ff853
to
39c59fc
Compare
Fixes #29685.