Search code examples
c#oracleentity-framework-core.net-6.0

DbContext returns irrelevant data from the Oracle database


The DbContext returns irrelevant data from the Oracle database. Getting the database context is implemented through IServiceScopeFactory, because the method that adds the DbContext to the DI container is Scoped, and my part uses IHostedService, which requires creating an area (Scope).

protected SomeCtor(...)
{
   IServiceScope scope = scopeFactory.CreateScope();
   DbContext = scope.ServiceProvider.GetRequiredService<TContext>();
}

I then retrieve the data in this way:

protected override IAsyncEnumerable<TItem> GetItemsFromDBAsync(CancellationToken cancellationToken)
{
    return DbContext.Set<TItem>().Where(...).AsAsyncEnumerable();
}

My DbContext is set in a standard way by inheriting from Microsoft.EntityFrameworkCore.DbContext

public class SomeDbContext : DbContext
{
  (...)
   public SomeDbContext (DbContextOptions<SomeDbContext > options)
            : base(options) { }

    protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.HasDefaultSchema("SchemaName");

            builder.Entity<SomeItem1>(x => x
               .ToTable("SomeTable1")
            );

            builder.Entity<SomeItem2>(x => x
               .ToTable("SomeTable2")
            );

            base.OnModelCreating(builder);
        }
}

And often ( not every time) I get data from a table that is no longer in the database. It feels like the context is not updated. Correct data starts loading after several program restarts.

No one else works with this table.


Solution

  • DbContext = scope.ServiceProvider.GetRequiredService<TContext>();

    I would recommend against doing that in the service. It seems that you resolve the SomeCtor in your IHostedService which makes lifetime of the DbContext match the lifetime of the hosted service itself. DbContext is designed to be lightweight so I highly recommend to switch to "per iteration" approach, i.e. let the IHostedService create short-lived scopes and resolve needed stuff inside the scope:

    class MyHostedService : IHostedService
    {
        private readonly IServiceScopeFactory _scopeFactory;
    
        public MyHostedService(IServiceScopeFactory scopeFactory)
        {
            _scopeFactory = scopeFactory;
        }
    
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            // TODO: manage cancellation/stoppage 
    
            while (true)
            {
                await using var scope = _scopeFactory.CreateAsyncScope();
                // SomeService - usual scoped service using DbContext, etc. ...
                var service scope.ServiceProvider.GetRequiredService<SomeService>();
                await service.ProcessIterationAsync();
            }
        }
        
        // ...
    }
    

    The problem you are facing is highly likely related to the fact that you are not disposing/recreating/clearing context. EF Core is using tracking to manage changes and if there is some stale information in change tracker it can lead to reading stale data from it without hitting database (or even hitting it but still using some parts of stale data, also I have performance issues related to change tracker in such scenarios at least in earlier versions of EF Core). At least call Clear on the change tracker - DbContext.ChangeTracker.Clear();

    Also check out consuming a scoped service in a background task parts of the docs.