Du må være registrert og logget inn for å kunne legge ut innlegg på freak.no
X
LOGG INN
... eller du kan registrere deg nå
Dette nettstedet er avhengig av annonseinntekter for å holde driften og videre utvikling igang. Vi liker ikke reklame heller, men alternativene er ikke mange. Vær snill å vurder å slå av annonseblokkering, eller å abonnere på en reklamefri utgave av nettstedet.
  7 6775
nso
popålol
nso's Avatar
Administrator
Det er ikke ofte jeg spør om ting på dette underforumet, men jeg vet det er et par racere som gjemmer seg i krokene så vi kjører på.

Det luktet litt svidd på live, så jeg fyrte opp en profiler og fikk meg en liten bakoversveis. Det er alltid gøy når antagelser man har gjort om hvordan verktøy man bruker fungerer ikke viser seg å holde vann.

Kort fortalt så sliter jeg med at EF genererer 1+(N-children) antall spørringer når jeg spør etter data til lenkede tabeller.

Kode

// Generates 1 SQL query against Foo with an inner join with FooBar
await db.Foo
  .Where(foo => 
    foo.FooId == fooId
    && !foo.IsDeleted)
  .Select(foo => new
    {
      FooBarList = foo.FooBars.ToList()
    })
  .ToListAsync();

// Generates 1 (+ number of FooBars) SQL queries, no inner join
await db.Foo
  .Where(foo => 
    foo.FooId == fooId
    && !foo.IsDeleted)
  .Select(foo => new
    {
      FooBarList = foo.FooBars
        .Where(fooBar => 
          fooBar.FooId = foo.FooId
          && !fooBar.IsDeleted)
        .ToList()
    })
  .ToListAsync();
Det hadde vært skikkelig gøy å kunne holde spørringantallet rundt 1 istedetfor rundt noen tusen. Jeg tipper jeg joiner på feil måte, fordi det føles som om jeg prøver å gjøre noe jævla basic men resultatet er helt på trynet.

Anyways, gi meg en lyd hvis noen har noen innspill til hvordan jeg kan løse dette.
sindre@puse.cat:~$
Synderen's Avatar
Jeg får ikke gjenskapt dette med EF Core 3.1.3, hvilken versjon er det dere bruker? Bruker dere code first? Jeg lurer på om dette kanskje kan være at at EF ikke forstår relasjonene riktig imellom entitetene, kunne du prøvd å eksplisitt definere forholdet mellom entitetene i database contexten? Å filtrere på FooId skal være helt unødvendig inne i Select expression, må den være der? Det kan isåfall være et tegn på at EF ikke klarer å tyde helt hvordan relasjonene er. Prøv også med å fjerne kallet til ToList() inne i Select expression. Det gjorde ingenting til eller fra for min del, utenom at typen på FooBarList ble List<> med kallet, og IEnumerable<> uten.
Ikke et svar på spørsmålet per se, men sjekk ut global query filters https://docs.microsoft.com/en-us/ef/...erying/filters, så slipper du å filtrere på IsDeleted overalt. Mener det går an å disable query filters på spørringer når man ikke trenger de også.
nso
popålol
nso's Avatar
Trådstarter Administrator
Kjører 2.2, men tror ikke det er synderen (excuse the pun). Er rimelig sikker på at det er selve relasjonsmodellen som mangler noe.

Code first, ja.

Her er relevante entities og context. Jeg tror kanskje EF ikke forstår modellen min. Kanskje jeg trenger å explisit definiere forholdene mellom entities med modelBuilder for at den skal forstå at det er left join jeg trenger?

Kode

    [Table("Foo")]
    public class Foo
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public long FooId { get; set; }

        public ICollection<FooBar> FooBars { get; set; }
    }

Kode

    [Table("FooBar")]
    public class FooBar
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public long FooBarId{ get; set; }

        public long FooId { get; set; }
        public Foo Foo { get; set; }

        public bool IsDeleted { get; set; }
    }

Kode

    public class Context : DbContext
    {
        public DbSet<Foo> Foo { get; set; }
        public DbSet<FooBar> FooBar { get; set; }

        private readonly DbConnection _connection;

        public Context()
        {
            _connection = Library.Database.ConnectionBuilder.Build("FooDbConnectionConfig");
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseNpgsql(_connection)
                .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.HasDefaultSchema("core");

... (egentlig ingenting relevant her, setter opp noen unique indexes)

            base.OnModelCreating(modelBuilder);
        }
    }
Sist endret av nso; 28. oktober 2020 kl. 18:42.
sindre@puse.cat:~$
Synderen's Avatar
Prøv å sette opp relations i OnModelCreating. Jeg gjør det vanligvis for alt, pga problemer som du har beskrevet, så det er mulig du slipper unna med å bare konfigurere det den ene veien.

Kode

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasDefaultSchema("core");

    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<Foo>.HasMany(x => x.Foobars).WithOne(x => x.Foo).HasForeignKey(x => x.FooId);
    modelBuilder.Entity<FooBar>.HasOne(x => x.Foo).WithMany(x => x.Foobars).HasForeignKey(x => x.FooId);
}
Sist endret av Synderen; 28. oktober 2020 kl. 19:37.
nso
popålol
nso's Avatar
Trådstarter Administrator
*ser på entity-liste med 100 tabeller, med koblinger og krysskoblinger og gudene vet hva*
Hvis det hadde vært lov så hadde jeg puttet inn det "(breathes heavily)" memet her nå. Nazi-regler.

Takk for innspill. Skal gi det en spinn å se hva som skjer.

Prøv også med å fjerne kallet til ToList() inne i Select expression. Det gjorde ingenting til eller fra for min del, utenom at typen på FooBarList ble List<> med kallet, og IEnumerable<> uten.
Vis hele sitatet...
Skal teste denne også. .To*() er jo magisk "terminering" av spørringer i EF så jeg er rett og slett litt usikker på hva som vil skje når jeg kjører den inne i en .Select()

Det ser ut som om det gjorde susen. Takk så meget. Jeg hadde faktisk vært inne på tanken og gjort noen spede forsøk, men jeg tror det jeg manglet var å også legge til regelen for undertabellen.

Sitat av lor3ntz Vis innlegg
Ikke et svar på spørsmålet per se, men sjekk ut global query filters https://docs.microsoft.com/en-us/ef/...erying/filters, så slipper du å filtrere på IsDeleted overalt. Mener det går an å disable query filters på spørringer når man ikke trenger de også.
Vis hele sitatet...
Takk for den. Visste ikke om denne funksjonen. Tviler på jeg kommer til å ta det i bruk ettersom det er en del logikk i koden som ikke bryr seg om slettet-status, og jeg føler det blir litt ekkelt å ha et "skjult" filter som man da må vite eksisterer for å evt overstyre. Føler allerede at jeg synder litt ved å ha .AsNoTracking() som default :shrug:
Sist endret av nso; 28. oktober 2020 kl. 22:24. Grunn: Automatisk sammenslåing med etterfølgende innlegg.
sindre@puse.cat:~$
Synderen's Avatar
Sitat av nso Vis innlegg
Takk for den. Visste ikke om denne funksjonen. Tviler på jeg kommer til å ta det i bruk ettersom det er en del logikk i koden som ikke bryr seg om slettet-status, og jeg føler det blir litt ekkelt å ha et "skjult" filter som man da må vite eksisterer for å evt overstyre. Føler allerede at jeg synder litt ved å ha .AsNoTracking() som default :shrug:
Vis hele sitatet...
Har noe lignende problem på et prosjekt, der løser vi dette med at vi har en metode i database contexten som gir deg en Queryable i retur, med et ferdig where filter. Entitetene vi vil ha dette filteret på arver av en SoftDeleteEntityBase klasse, så med litt generics så hiver vi på filteret når du spørr på den entiteten. Metoden ser ca slik ut:

Kode

public IQueryable<TEntityType> Queryable<TEntityType>(bool filterOnIsDeleted = true) where TEntityType : class
            => filterOnIsDeleted && typeof(SoftDeleteEntityBase).IsAssignableFrom(typeof(TEntityType))
                ? Set<TEntityType>().Where($"{nameof(SoftDeleteEntityBase.IsDeleted)} == false")
                : Set<TEntityType>();
Den slenger på filteret på alle klasser som arver av SoftDeleteEntityBase, men kan overstyres der man vil ved å sette filterOnIsDeleted som tile false. For entiteter som ikke arver etter den base klassen, så får du bare en vanlig Queryable i retur. Dette ender opp som en litt sånn halveis løsning imellom det lor3ntz foreslår og ingenting. Det blir da litt mer synlig at det er noe som skjer med Queryable siden man ikke går rett på DbSet propertyen. Men mer at den kommer ikke til å filtrere på joins, bare på den entiteten Queryable "starter" på.
nso
popålol
nso's Avatar
Trådstarter Administrator
Er fan av å ha ting i klartekst og så lite skjult kode som mulig. Ser hvorfor dere har gjort det dere har gjort, men jeg foretrekker heller litt ekstra kode "der man er nå" enn å ha magi under the hette. Ikke så mye for min egen del nødvendigvis, men mer for nye øyne som skal se på koden i etterkant.

Hele patternet vårt har jeg bygget opp rundt den filosofien, at det er lettere å unngå feil jo mindre mystiske ting man må forholde seg til, og har sett ved nyansettelser at det er veldig fort for folk å sette seg inn i hvordan ting skal være når det er slik.

Men takk så mye for kodesnutt. Lærte noe nytt i dag også.

Er sånn ca halvveis i å skrive modell-bindingene nå og ligger allerede på rundt 1000 linjer :O. Satse på at ef core får støtte for langvarig caching av bygget datamodell snart. https://github.com/dotnet/efcore/issues/1906
Sist endret av nso; 29. oktober 2020 kl. 01:19.