C
C#2y ago
Natty

Multiple Users Concurrency Issue? EF6

It's a backend API with EF6 and frontend React/js coupled together (can't run the API separately). When multiple users try to upload a file at the same time, or run the main loop to perform calculations, the app crashes. The error message from the log is:
System.InvalidOperationException: Unexpected connection state. When using a wrapping provider ensure that the StateChange event is implemented on the wrapped DbConnection.
System.InvalidOperationException: Unexpected connection state. When using a wrapping provider ensure that the StateChange event is implemented on the wrapped DbConnection.
There was also a duplicate key error, but that must stem from the concurrency issues we're seeing. The site works as intended even if users upload and run the main loop seconds apart, just not at the same time. Can anyone pinpoint what this error actually means and the resolution? I've spent several hours reading, and it seems like it comes down to improper db context usage, but we instantiate a db context whenever we need it. I found an article that says this (see attached). I think we're doing that, but I don't know how to confirm.
30 Replies
Natty
Natty2y ago
None of the db contexts are wrapped in a using statement either. There is also no direct _db.Dispose() being called. It's all being handled by the GC, would this be any issue? There's also endless static methods being used nearly everywhere, that I would imagine are manipulating state as well....
D.Mentia
D.Mentia2y ago
Old contexts not being disposed sounds like it could be it. Eventually it'd just run out of connections on the db
RDasher
RDasher2y ago
I've had this issue before, and it was because of Undisposed DbConnections Always be sure to dispose of DbConnections and IDisposables that have fufilled their purpose (or use the using statement) And, are you sure you are running your backend API multi-threaded? Using something like the Semaphore class?
Natty
Natty2y ago
So you think every time a _db context is being used, do it within a using? Right now, we're just directly instantiating at the top of the controller. To be honest, I don't know, I wasn't sure how to confirm if it was single or multi. Not using Semaphore. So if we're not using any out of the ordinary things, then the api is single threaded?
RDasher
RDasher2y ago
Yes, I would say from the sounds of, it sounds like a single threaded application Can you share an example if possible?
Natty
Natty2y ago
Ok, I'd imagine that's correct. It's really just a poorly structured EF6 Api... Hm, sure, let me share the Upload Controller, which the app was failing here when multiple users were trying to upload. https://hastepaste.com/view/nlX1D
RDasher
RDasher2y ago
There is nothing wrong with single threaded applications, they are much easier to work with Lemme check
Natty
Natty2y ago
Again, very poorly structured.
RDasher
RDasher2y ago
What's happening in Context()
Natty
Natty2y ago
Can you tell me too, is the Dispose method at the bottom actually doing anything? Some other dev put it there, but that's the only implementation of it, and it's never being called, etc. Meaning? It's just the db context.
RDasher
RDasher2y ago
Oh, I see
Natty
Natty2y ago
Usually in .net core it's through DI with the service, but not in EF6 and this project was near "done" by the time i started, so didnt implement Automapper, etc
RDasher
RDasher2y ago
You mean, a class like this?
public partial class SchoolDBEntities : DbContext
{
public SchoolDBEntities()
: base("name=SchoolDBEntities")
{
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}

public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Standard> Standards { get; set; }
public virtual DbSet<Student> Students { get; set; }
public virtual DbSet<StudentAddress> StudentAddresses { get; set; }
public virtual DbSet<Teacher> Teachers { get; set; }
public virtual DbSet<View_StudentCourse> View_StudentCourse { get; set; }
}
public partial class SchoolDBEntities : DbContext
{
public SchoolDBEntities()
: base("name=SchoolDBEntities")
{
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}

public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Standard> Standards { get; set; }
public virtual DbSet<Student> Students { get; set; }
public virtual DbSet<StudentAddress> StudentAddresses { get; set; }
public virtual DbSet<Teacher> Teachers { get; set; }
public virtual DbSet<View_StudentCourse> View_StudentCourse { get; set; }
}
Natty
Natty2y ago
I am aware too that this is "fake async" methods, I am having the one dev fix that at the very least, awaiting and making all possible methods async. Yes, correct! Let me grab a copy.. one sec...
RDasher
RDasher2y ago
The dispose is not doing anything if it is not being invoked
RDasher
RDasher2y ago
Actually, no, I might be wrong about this I don't think you should dispose of it
Natty
Natty2y ago
yeah since it's overrirding, i wasnt sure if it is still in fact doing something? even though there's no direct db.Dispose() call.
RDasher
RDasher2y ago
You approach is much more rudimentary than ours Okay, yeah I've figured it DBContext will dispose of itself fine I was wrong about that bit So you don't need to manually dispose, nor do you need to use using statements
Natty
Natty2y ago
Yeah, through the GC, regardless if using using or not.
RDasher
RDasher2y ago
What you do need to do, is don't initialize on top of the controller
Natty
Natty2y ago
However, the timing of it could differ, and i'm not sure if that matters, when there's multiple users.
RDasher
RDasher2y ago
No, not even through the GC, DBContext manages it's own lifetime What you should do, is move the initialization of db into the methods
Natty
Natty2y ago
Ok, so having it at the top means it's being shared rather than one instance per request?
RDasher
RDasher2y ago
Example:
private Context db = new Context();

public async Task<IHttpActionResult> Post()
{
//
user.Uploads.Add(upload);
//
await db.SaveChangesAsync();
//
}
private Context db = new Context();

public async Task<IHttpActionResult> Post()
{
//
user.Uploads.Add(upload);
//
await db.SaveChangesAsync();
//
}
Becomes
public async Task<IHttpActionResult> Post()
{
Context db = new Context();
//
user.Uploads.Add(upload);
//
await db.SaveChangesAsync();
//
}
public async Task<IHttpActionResult> Post()
{
Context db = new Context();
//
user.Uploads.Add(upload);
//
await db.SaveChangesAsync();
//
}
Natty
Natty2y ago
Gotcha yea
RDasher
RDasher2y ago
That, and what happens is likely that that DBContext becomes cached
Natty
Natty2y ago
I did read about that too, and the cache becomes stale, but i couldnt really inquire further about when/how/what that all meant lol
RDasher
RDasher2y ago
Hopefully this solves your issue, good luck 👍
Natty
Natty2y ago
Thank you!