2

I'm creating a communication app (using .net core 1.1 mvc and signalr/knockout ) and I have the following pocos:

room user post postRevision

posts is a container of postRevision, which for convenience has a reference to the latest revision

everything else is as you would imagine it. I'm omitting a lot of details obviously.

My user has an avatar property. When I update the user's avatar I delete their old avatar, and update to the new one. Then in the room the posts (postRevisions really) by said user no-longer have a friendly avatar, now they have a broken image icon. I'm still developing, so this is not happening in a multi-user scenario. This is just me jumping from the mvc controller that changes avatars to the signalr/knockout page that displays posts.

When I stop and run the server the images are fixed. It looks like entity framework is caching the results and not updating.

in my controller I have this code snippet:

user.Avatar = imageId.ToString();
_context.Entry(user).State = EntityState.Modified;
await _context.SaveChangesAsync();

_context.Entry(user).State = EntityState.Detached;
return new JsonResult(new { status = "sucess" });

in my hub I have this snippet for fetching a page of posts:

data =  await _context.Posts
    .Include(x=> x.LatestRevision)
    .Include(x => x.LatestRevision.Blob)
    .Include(x => x.LatestRevision.Creator)
    .Where(x=> x.Room.Id == roomId 
        && x.Id < currentPostId)
    .OrderByDescending(x => x.Id)
    .Take(_roomPageSize).ToListAsync();

I'm not sure how to tell the signalr context to update, especially since that page isn't open, and in theory that page is out of scope, so that context isn't even created yet. This is the relevant portion of my Startup.cs

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();
Josiah
  • 484
  • 1
  • 5
  • 12
  • Do you the same instance of DbContext? If you do then this is likely the issue. – Pawel Jan 28 '17 at 05:47
  • Where/when do you create and dispose `_context`? – Gert Arnold Jan 28 '17 at 22:36
  • @GertArnold I'm using the standard IoC dependency injector with the context as a constructor parameter on the controller. That's what the startup.cs file is hinting at. I take it you've not had much experience with .net core. – Josiah Jan 29 '17 at 06:10
  • @Pawel : I don't think so, but that really depends on how the IoC DI is setup for entity framework contexts. I think not. Not sure though. – Josiah Jan 29 '17 at 06:13
  • It only shows that you register the context in the IoC container. But if you use constructor injection in the controller, there must be a new context for each request. So it's not the context caching stuff. Must be some other caching mechanism. – Gert Arnold Jan 29 '17 at 18:47

2 Answers2

1

The problem is that the caching system for each context were effectively not communicating their changes to each other.

I knew the exact user object that was being displayed with old data and I called

_context.Entry(user).State = EntityState.Detached;

before running my query in the hub. This did nothing. The old data was still being displayed. (in real use this wouldn't be so straightforward, but I'm the only user in the system right now)

What did end up working was adding this line to my query in the hub:

.AsNoTracking()

this means I lose all caching, and since 99% of the changes would be flowing correctly through the hub context that seems a shame, but it's working now.

I wonder If I shouldn't change how I'm saving the objects in on the MVC side so that my actions are using the hub context, that way it's always in the loop. I would have to add the .AsNoTracking() in a few places on the MVC side, but that is the better place to put it.

Here's a post on how to do that: SignalR + posting a message to a Hub via an action method

-edit: the above link is only for invoking client methods. There's no good way to invoke sever-side hub code yet. See Use Hub methods from controller?

Community
  • 1
  • 1
Josiah
  • 484
  • 1
  • 5
  • 12
1

I wound up squashing this by adding

ServiceLifetime.Transient

to my AddDbContext call, making the DB context have a transient lifespan. So a new context is created every time it is requested from the service container.

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#service-lifetimes

services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Transient);
Justin
  • 839
  • 1
  • 16
  • 24
Josiah
  • 484
  • 1
  • 5
  • 12