Asked by:
[EF Core / DbContext] If I .Add() a row in a seperate thread, related tables/rows' state are set to EntityState.Added => Exception

Question
-
User-1866878674 posted
Just to make it clear:
I use an injected instance of DbContext when it runs outside the thread, and a new DbContext() inside the thread.
Whether I run the code in a seperate thread or not, the ChangeTracker has Entries for the row I want to add, plus rows from all related tables.
The difference is that when I try to add a row in a seperate thread, all the related rows' EntityState are set to EntityState.Added:
db.VehicleWarehouse.Add(vehicleWarehouse); entries = db.ChangeTracker.Entries(); cnt = entries.Count(); if (cnt > 1) { foreach (var entry in entries) { var entity = entry.Entity; var state = entry.State; if (entity != vehicleWarehouse) { // all related tables ends up in a seperate entity, up to the very top (VehicleWarehouse->Vehicle->Company)
// if the code is in a seperate thread, State is always set to Added => Exception if (state == EntityState.Added) db.Entry(entity).State = EntityState.Unchanged; // prevent insert exception } } } db.SaveChanges();I start the thread like this:
Task.Run(() => { FindVehicleWarehouse(vehicle, isLooking); });
What could be causing this, and is there a way to prevent it from happening?
Friday, February 5, 2021 11:09 AM
All replies
-
User1120430333 posted
delete this post.
Saturday, February 6, 2021 3:54 PM -
User1120430333 posted
What could be causing this, and is there a way to prevent it from happening?
DBcontext is not thread safe.
Managing DbContext the right way with Entity Framework 6: an in-depth guide (mehdi.me)
Saturday, February 6, 2021 3:56 PM -
User-1866878674 posted
DBcontext is not thread safe.
Using the same instance of a DBContext is not thread safe. If you use a seperate instance, it is supposed to be ok. From your link:
In a multi-threaded application, you must create and use a separate instance of your DbContext-derived class in each thread.
..and at the very top of my post, I did say that I use a new instance of my DBContext in the thread:
I use an injected instance of DbContext when it runs outside the thread, and a new DbContext() inside the thread.
Every query outside the thread uses an injected context, and I create a new instance inside the thread.
So thread safety isn't an issue here, it's something else.
Monday, February 8, 2021 11:03 AM -
User1120430333 posted
What you are doing is not working, and you may need to take another approach altogether.
Monday, February 8, 2021 12:42 PM -
User-1866878674 posted
What you are doing is not working, and you may need to take another approach altogether.
If you're refering to the code provided, it does indeed work. As long as I set Entity.State to EntityState.Unchanged I'm able to insert the data in the db.
The code provided + all the stuff I left out is within this (and runs in it's own thread):
using (var db = new DatabaseContext()) { // your code here }
According to the link you provided that should be enough, but the behaviour is for some reason a bit different when it is in a seperate thread.
I was hoping someone could tell my why the Entity State of rows in related tables is set to EntityState.Added when the code is running in a seperate thread, and maybe tell me how I can avoid that. Unfortunately it seems like that somone didn't read my post.
Is running a new DbContext instance inside a seperate thread that uncommon?
Monday, February 8, 2021 1:46 PM -
User1120430333 posted
if you're refering to the code provided, it does indeed work. As long as I set Entity.State to EntityState.Unchanged I'm able to insert the data in the db.
Sorry, but I consider it Frankenstein programming, a hack around, and one shouldn't have to check if an entity is unchanged. One should know what objects have changed and only persist those objects that have changed.
This kinds of warrants the usage of a data persistence design pattern to keep rouge programming under control, like using the generic repository I don't like to mention or data access object pattern.
Repository Pattern C# - Code with Shadman
Data Access Object (DAO) design pattern in Java - Tutorial Example (javarevisited.blogspot.com)
Monday, February 8, 2021 7:01 PM -
User-1866878674 posted
if you're refering to the code provided, it does indeed work. As long as I set Entity.State to EntityState.Unchanged I'm able to insert the data in the db.
Sorry, but I consider it Frankenstein programming, a hack around, and one shouldn't have to check if an entity is unchanged. One should know what objects have changed and only persist those objects that have changed.
This kinds of warrants the usage of a data persistence design pattern to keep rouge programming under control, like using the generic repository I don't like to mention or data access object pattern.
Repository Pattern C# - Code with Shadman
Data Access Object (DAO) design pattern in Java - Tutorial Example (javarevisited.blogspot.com)
Example of how I do things when I have a related table (and that should be the proper way, aswell):
// Job.cs
public class Job
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity]
public int Id { get; set; }
public string Name { get; set; }
}
// Person.cs
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity]
public int Id { get; set; }
public string Name { get; set; }
// a seperate JobId field is only needed
// for cascade on delete
public Job Job { get; set; }
}
// code in a service
//
// using is only used if the code is inside a thread, outside a thread, db is injected
using (var db = new DatabaseContext()) {
// the person has Job set to an existing table row with id and everything
// (basically the only thing missing at this point is the Id, which is set by the db on insert)
// // if this code is run inside a seperate thread, EF Core
// will also try to insert person.Job (=> exception), outside a thread it will not db.Add(person); // or db.Person.Add(person) db.SaveChanges(); }I'm always using a normal model class (in this example called 'Person' and 'Job') for the table(s) , and using DbSet in the DbContext (my derived class is called DatabaseContext).
I'm not a fan of the 'solution' I came up with, but that's the only 'solution' I've got at the moment. If I did think it was ok (and it's not), making this post would be pointless (no offence, it's just a fact).
Wednesday, February 10, 2021 10:25 AM -
User1120430333 posted
Myself, I would just keep it simple by using the DTO pattern. A DTO Parent with child DTO collection within parent. I would use the DAO pattern a DOA for each table and called the DAO(s) to persist their DTO to the database using EF.
It's just me the simpler the better. :)
Wednesday, February 10, 2021 11:07 AM