Reputation: 372
I've followed the documentation here, and I'm using SQL Server 2016.(https://ef.readthedocs.io/en/latest/modeling/concurrency.html)
I've added a timestamp column to my table named VersionCol, and then when I run Scaffold-DbContext it places the following property on my table entity in my DbContext.
entity.Property(e => e.VersionCol)
.IsRequired()
.HasColumnType("timestamp")
.ValueGeneratedOnAddOrUpdate()
It's missing .IsConcurrencyToken()
so I've added that on myself, but still no exceptions are thrown in situations that should suffer concurrency issues. It instead simply over-writes the data.
Is there something I am missing?
Edit:
I'm using a Database-first approach (so no [Timestamp] or any other annotations), and my DbContext is being injected into a service, configured with services.AddScoped<IPoRepository, PoRepository>()
in Startup.cs
It generates a public byte[] VersionCol { get; set; }
field in my model, which is right I believe.
In my PoRepository I'm trying to update my Po with the following:
public void SavePo(PoListing poListing) {
Po po;
try {
po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
po.AssignedUserId = poListing.AssignedUserId;
po.VersionCol = poListing.VersionCol;
_context.Entry( po ).State = EntityState.Modified;
_context.SaveChanges();
}
The PoListing is essentially just a part of a Po, so It only has some of it's columns (it's not a table in the database), and it has the VersionCol of the Po when it is first generated. If the PoListing has an older VersionCol than the Po it's based off, then it should give an exception.
Edit2:
This works, but I can't figure out how to make it work without needing to make this second context, and just use the injected context.
public void SavePo(PoListing poListing) {
DbContextOptionsBuilder<TMS_1000Context> options = new DbContextOptionsBuilder<TMS_1000Context>();
options.UseSqlServer( "Server=DEVSQL16;Database=TMS_1000_Dev;Trusted_Connection=True;MultipleActiveResultSets=true" );
TMS_1000Context context1;
try {
po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
using ( context1 = new TMS_1000Context( options.Options ) ) {
po.AssignedUserId = poListing.AssignedUserId;
po.VersionCol = poListing.VersionCol;
context1.Update( po );
context1.SaveChanges();
}
}
Edit3:
This is currently working. Is there another way?
public void SavePo(PoListing poListing) {
Po po;
try {
po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
po.AssignedUserId = poListing.AssignedUserId;
_context.Entry( po ).Property( u => u.VersionCol ).OriginalValue = poListing.VersionCol;
_context.Update( po );
_context.SaveChanges();
}
Upvotes: 5
Views: 843
Reputation: 372
The reason I believe this is happening is because EF Core tracking cares only if the original values are the same as what's currently in the database, and if they aren't then that's when a concurrency exception is thrown.
Here are 3 fixes I've found.
Change the original value so that it is now different than what exists in the database.
public void SavePo(PoListing poListing) {
Po po;
try {
po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
po.AssignedUserId = poListing.AssignedUserId;
_context.Entry( po ).Property( u => u.VersionCol ).OriginalValue = poListing.VersionCol;
_context.Update( po );
_context.SaveChanges();
}
Get the entity with AsNoTracking(). Now EF Core won't just compare the original values.
public void SavePo(PoListing poListing) {
Po po;
try {
po = _context.Po.AsNoTracking().Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
po.AssignedUserId = poListing.AssignedUserId;
po.VersionCol = poListing.VersionCol;
_context.Update( po );
_context.SaveChanges();
}
Detach the entity from the context. Similar function as fix #2
public void SavePo(PoListing poListing) {
Po po;
try {
po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
po.AssignedUserId = poListing.AssignedUserId;
po.VersionCol = poListing.VersionCol;
_context.Entry( po ).State = EntityState.Detached;
_context.Update( po );
_context.SaveChanges();
}
Upvotes: 3