Automatyczne dołączanie powiązanych encji, gdy nie zastosuje się AsNoTracking()

0

Z tego co się orientuję to EF w domyślnej konfiguracji działa jako eager loading.
Czyli żeby pobrać pobrać powiązane encje to muszę zastosować .Include().

I teraz mam taki problem, że jak robię zapytanie do bazy z wykorzystaniem Linq i bez .AsNoTracking() to pobierają mi się powiązane encje poprzez navigation property.
Chciałbym aby działało to tak, że powiązane encje ładują się tylko i wyłącznie wtedy gdy zastosuję .Include.

Ktoś podpowie jak to ugryźć i czemu tak się dzieje?

Entity Framework .NET 8

0

Musisz zmienić tryb z eager loading na lazy load i wtedy musisz używać .Include() dla powiązanych tabel. Szukaj pod hasłem "how to enable lazy loading ef core".

0

Ale AsNoTracking() jest do śledzenia zmian w kontekście przez EF, a nie do popierania "powiązanych encji" i nie ma z tym nic wspólnego 🤔

0
AdamWox napisał(a):

Ale AsNoTracking() jest do śledzenia zmian w kontekście przez EF, a nie do popierania "powiązanych encji" i nie ma z tym nic wspólnego 🤔

No właśnie dziwna sprawa. Bo gdy używam AsNoTracking() to tylko wtedy z automatu nie includuje powiązanych relacji.
Wygląda to tak jak by każda navigation property miała ustawione AutoInclude().

1

Pokaż co masz skonfigurowane w swoim DbContext lub w Program.cs, bo to mi wygląda jakbyś miał właśnie globalnie ustawione Include() dla encji.

#edit
Chyba, że MS zmienił coś w .NET 8 i najnowszym EF, że teraz domyślnie jest Include() dla każdej encji, ale to mało wydajne i głupie rozwiązanie. Patrząc, że to MS to bym się nie zdziwił, ze by tak zrobili.

0
AdamWox napisał(a):

Pokaż co masz skonfigurowane w swoim DbContext lub w Program.cs, bo to mi wygląda jakbyś miał właśnie globalnie ustawione Include() dla encji.

#edit
Chyba, że MS zmienił coś w .NET 8 i najnowszym EF, że teraz domyślnie jest Include() dla każdej encji, ale to mało wydajne i głupie rozwiązanie. Patrząc, że to MS to bym się nie zdziwił, ze by tak zrobili.

Dzieje mi się tak w projekcie z testami jednostkowymi. Jak odpalam sobie aplikację lokalnie to nie ma takiego zachowania.
Używam XUnita i testcontainers. W za pomocą testcontainers odpalam sobie instancję MS Sql Server do testów.

Poniżej wycinek mojego "Fixture", którego używam globalnie w ramach wszystkich testów z użyciem "CollectionDefinition".

public sealed class SetupFixture : IAsyncLifetime
{
    private readonly MsSqlContainer _msSqlContainer = new MsSqlBuilder().Build();

     public async Task InitializeAsync()
      {
          await _msSqlContainer.StartAsync();
          string connectionString = _msSqlContainer.GetConnectionString();
          var serviceCollection = new ServiceCollection();
          serviceCollection.AddDbContextPool<MyDbContext>(options =>
          {
              options.UseSqlServer(connectionString, x => x.MigrationsAssembly(typeof(MyDbContext).Assembly.FullName));
          });
          
          serviceCollection.AddScoped<IMyDbContext>(provider => provider.GetService<MyDbContext>());
          serviceCollection.AddScoped<DbContextProvider<IMyDbContext>>();
      }
}
0

A nie jest tak że najpierw pobierasz z Include a potem bez, wtedy jeśli encja jest w kontekście to ja zwróci dla kolejnego zapytania, bo po co ma odpytywać jeszcze raz bazę danych, poczytaj o 1st level cache

0

Ej no ale jak to, że na testach jest źle, a "normalnie" jest dobrze? To co te testy wnoszą? Jaki jest wniosek po takich testach? Kod jest źle, czy testy są źle? Pokaż co masz w MyDbContext...

0
AdamWox napisał(a):

Ej no ale jak to, że na testach jest źle, a "normalnie" jest dobrze? To co te testy wnoszą? Jaki jest wniosek po takich testach? Kod jest źle, czy testy są źle? Pokaż co masz w MyDbContext...

W testach dziwnie zachowuje się EF...

internal sealed class MyDbContext(DbContextOptions options) : DbContext(options), IMyDbContext
{
    private BaseUserContext _userContext;
    private SystemUserSettings _systemUser;
    private IDateTimeProvider _dateTimeProvider;

    //DbSety

    protected override void OnModelCreating(ModelBuilder builder)
    {
      builder.ApplyConfigurationsFromAssembly(GetType().Assembly);
      builder.RestrictCascadeDelete();
  
      base.OnModelCreating(builder);
    }
    
    public void SetDbContextFields(BaseUserContext userContext, SystemUserSettings mainUserSettings, IDateTimeProvider dateTimeProvider)
    {
        _userContext = userContext;
        _systemUser = mainUserSettings;
        _dateTimeProvider = dateTimeProvider;
    }

    public async Task<int> SaveChangesWithAuditAsync(CancellationToken cancellationToken = default)
    {
        //ustawianie danych audytowych przy EntityState.Added lub EntityState.Modified
    }

    return await base.SaveChangesAsync(cancellationToken);
}
0

Pokaż konfigurację relacji. Może korzystasz z metody

OwnsMany()

W najnowszych ef core'ach ( nie wiem od której wersji) jeśli korzystasz w konfiguracji metody OwnsMany to wtedy masz domyślnie eager loading dla tych encji - ostatnio się na tym złapałem.

Co ciekawe tak to działa w kodzie a w Unit Testach gdy robiłem bazę InMemory to nie do końca to tak działało :D

0
Terrored napisał(a):

Pokaż konfigurację relacji. Może korzystasz z metody

OwnsMany()

W najnowszych ef core'ach ( nie wiem od której wersji) jeśli korzystasz w konfiguracji metody OwnsMany to wtedy masz domyślnie eager loading dla tych encji - ostatnio się na tym złapałem.

Co ciekawe tak to działa w kodzie a w Unit Testach gdy robiłem bazę InMemory to nie do końca to tak działało :D

InMemory to w ogóle nie zachowuje relacji i działa jak chce... Dlatego przerzuciłem się na testcontainers z obrazem dockerowym SqlServera.

internal sealed class ActionConfiguration : IEntityTypeConfiguration<Action>
{
    public void Configure(EntityTypeBuilder<Action> builder)
    {
        builder.GenerateBasePropertiesRules();

        builder.Property(x => x.GoalId)
                    .HasConversion(x => x.Value, x => new BaseId(x))
                    .IsRequired();

        builder.Property(x => x.ActionId)
                    .HasConversion(x => x.Value, x => new BaseId(x))
                    .IsRequired();

        builder.ComplexProperty(x => x.Name, cpb =>
        {
            cpb.Property(x => x.Value)
                .HasColumnName(nameof(Action.Name))
                .HasMaxLength(250)
                .IsRequired();
            cpb.IsRequired();
        });

        builder.HasOne(x => x.Creator)
           .WithMany(x => x.ActionCreators)
           .HasForeignKey(x => x.CreatorId)
           .IsRequired();

        builder.HasOne(x => x.Modificator)
                    .WithMany(x => x.ActionModificators)
                    .HasForeignKey(x => x.ModificatorId)
                    .IsRequired(false);
        
        builder.HasOne(x => x.CreatorContext)
                    .WithMany(x => x.ReportActionCreatorsContexts)
                    .HasForeignKey(x => x.CreatorContextId)
                    .IsRequired(false);
        
        builder.HasOne(x => x.ModificatorContext)
                    .WithMany(x => x.ReportActionModificatorsContexts)
                    .HasForeignKey(x => x.ModificatorContextId)
                    .IsRequired(false);
        
        builder.HasOne(x => x.Goal)
                    .WithMany(x => x.Actions)
                    .HasForeignKey(x => x.GoalId)
                    .IsRequired();
        
        builder.HasOne(x => x.Action)
                    .WithMany(x => x.Actions)
                    .HasForeignKey(x => x.ActionId)
                    .IsRequired();
    }
}

Zastanawia mnie też jedna kwestia...

Czemu ChangeTracker ma ustawione LazyLoadingEnabled na true skoro EagerLaoding jest domyślnym zachowaniem EF?
Sprawdziłem w projekcie gdzie jest .NET 6 i tam też ChangeTracker posiada ustawione LazyLoadingEnabled na true.
Natomiast sam MS wskazuje, że aby LazyLoading działało to trzeba zastosować paczkę Microsoft.EntityFrameworkCore.Proxies.
https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy

screenshot-20240430114213.png

0
bagietMajster napisał(a):

Musisz zmienić tryb z eager loading na lazy load i wtedy musisz używać .Include() dla powiązanych tabel. Szukaj pod hasłem "how to enable lazy loading ef core".

Include to się używa raczej w przypadku EagerLoading...

1 użytkowników online, w tym zalogowanych: 0, gości: 1