I've come across a strange problem with my Blazor app. The app has a background service that polls remote hosts for status information, and updates the database with that information. The webasm client sees any updates the background service makes, but if I update a record via the client through an API controller (for instance, changing the IP address) the background service doesn't see the new value unless I restart the server.
Here's some relevant code from the background service:
public class Poller : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private MyDbContext db;
public Poller(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var scope = _scopeFactory.CreateScope())
{
db = scope.ServiceProvider.GetRequiredService<MyDbContext>();
// Main loop
while (!stoppingToken.IsCancellationRequested)
{
// Get list of workers
foreach (var worker in db.Workers
.Where(w => w.Status != WorkerStatus.Updating)
.ToList())
{
var prevStatus = worker.Status;
// Poll for new status
string currentStatus = await worker.PollAsync(stoppingToken);
if (currentStatus != null)
{
worker.Status = currentStatus;
}
if (worker.Status != prevStatus)
{
await Log(worker, $"Status changed to {worker.Status}", stoppingToken);
}
await db.SaveChangesAsync(stoppingToken);
}
}
}
}
private async Task Log(Worker? worker, string text, CancellationToken token)
{
var entry = new LogEntry
{
Worker = worker,
Text = text
};
db.LogEntries.Add(entry);
await db.SaveChangesAsync(token);
}
Code from the API controller is basic boilerplate:
[HttpPut("{id}")]
public async Task<IActionResult> PutWorker(string id, Worker worker)
{
if (id != worker.WorkerId)
{
return BadRequest();
}
_context.Entry(worker).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!WorkerExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
Thanks!
CodePudding user response:
As soon as you are using the same DbContext instance, the updated data will not be seen in your entities.
You can check this blog post for details:
It turns out that Entity Framework uses the Identity Map pattern. This means that once an entity with a given key is loaded in the context’s cache, it is never loaded again for as long as that context exists. So when we hit the database a second time to get the customers, it retrieved the updated 851 record from the database, but because customer 851 was already loaded in the context, it ignored the newer record from the database
http://codethug.com/2016/02/19/Entity-Framework-Cache-Busting/
I could successfully reproduce this behavior.
For your case you could as an option create a new instance of context on each iteration
// Main loop
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _scopeFactory.CreateScope())
{
db = scope.ServiceProvider.GetRequiredService<MyDbContext>();
// Get list of workers
foreach (var worker in db.Workers
.Where(w => w.Status != WorkerStatus.Updating)
.ToList())
{
var prevStatus = worker.Status;
// Poll for new status
string currentStatus = await worker.PollAsync(stoppingToken);
if (currentStatus != null)
{
worker.Status = currentStatus;
}
if (worker.Status != prevStatus)
{
await Log(worker, $"Status changed to {worker.Status}", stoppingToken);
}
await db.SaveChangesAsync(stoppingToken);
}
}
}