Skip to content
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

Lookup tracked entities by primary key, alternate key, or foreign key #29685

Closed
ajcvickers opened this issue Nov 27, 2022 · 1 comment · Fixed by #29686
Closed

Lookup tracked entities by primary key, alternate key, or foreign key #29685

ajcvickers opened this issue Nov 27, 2022 · 1 comment · Fixed by #29686
Labels
area-change-tracking area-dbcontext closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Milestone

Comments

@ajcvickers
Copy link
Contributor

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.

@ajcvickers
Copy link
Contributor Author

ajcvickers commented Nov 27, 2022

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; }
}

ajcvickers added a commit that referenced this issue Nov 30, 2022
ajcvickers added a commit that referenced this issue Dec 3, 2022
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Dec 3, 2022
@ajcvickers ajcvickers changed the title Lookup entities by primary key, alternate key, or foreign key Lookup tracked entities by primary key, alternate key, or foreign key Jan 5, 2023
@ajcvickers ajcvickers modified the milestones: 8.0.0, 8.0.0-preview1 Jan 29, 2023
@ajcvickers ajcvickers modified the milestones: 8.0.0-preview1, 8.0.0 Nov 14, 2023
@ajcvickers ajcvickers removed their assignment Aug 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-change-tracking area-dbcontext closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant