ASP.NET Core 2 Controller PUT method failing with Concurrency Core - entity-framework

I have an ASP.NET Core 2.0 Site that has a scaffolded controller built directly from a simple model and simple context. I seeded the data by simply checking for the number of records in the GET method and if 0, then I added 100 records. GET is retrieving records as I would expect repeatedly.
I'm using the inmemory database provider.
services.AddDbContext<MyDbContext>
(opt => opt.UseInMemoryDatabase("CodeCampInMemoryDb"));
When I do a PUT with a record that I know existed in my GET, I get a concurrency error as shown at the bottom of this post. I've not used this method of changing the EntityState of a record I created myself, so I'm not sure how this was suppose to work in the first place, but clearly now it is not working.
Maybe it has something to do with a transaction being processed on the inmemory database? I'm not sure how to avoid that if that is the problem.
// PUT: api/Sessions/5
[HttpPut("{id}")]
public async Task<IActionResult> PutSessionRec([FromRoute] int id, [FromBody] SessionRec sessionRec)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != sessionRec.Id)
{
return BadRequest();
}
_context.Entry(sessionRec).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException xx)
{
if (!SessionRecExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Attempted to update or delete an entity that does not exist in the store.
at Microsoft.EntityFrameworkCore.Storage.Internal.InMemoryTable`1.Update(IUpdateEntry entry)
at Microsoft.EntityFrameworkCore.Storage.Internal.InMemoryStore.ExecuteTransaction

If you marked a property as a Timestamp and don't provide it, you will get this exception every time. You either need to load the entity with the latest Timestamp and update that (not ideal) or you have to send the Timestamp down to the client and have the client send it back up (correct way). However, if you are using an older version of JSON serialization, you have to convert the byte[] to base 64 and then convert it back.

Related

Issues with Automatically setting created and modified date on each record in EF Core

Using ASP.NET Core 2.2 with EF Core, I have followed various guides in trying to implement the automatic creation of date/time values when creating either a new record or editing/updating an existing one.
The current result is when i initially create a new record, the CreatedDate & UpdatedDate column will be populated with the current date/time.
However first time I edit this same record, the UpdatedDate column is then given a new date/time value (as expected) BUT for some reason, EF Core is wiping out the value of the original CreatedDate which results in SQL assigning a default value.
Required result I need as follows:
Step 1: New row created, both CreatedDate & UpdatedDate column is given a date/time value (this already works)
Step 2: When editing and saving an existing row, I want EF Core to update the UpdatedDate column with the updated date/time only, BUT leave the other CreatedDate column unmodified with the original creation date.
I'm using EF Core code first, and do no want to go down the fluent API route.
One of the guides i was partially following is https://www.entityframeworktutorial.net/faq/set-created-and-modified-date-in-efcore.aspx but neither this or other solutions I've tried is giving the result I am after.
Baseclass:
public class BaseEntity
{
public DateTime? CreatedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
}
DbContext Class:
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
var entries = ChangeTracker.Entries().Where(E => E.State == EntityState.Added || E.State == EntityState.Modified).ToList();
foreach (var entityEntry in entries)
{
if (entityEntry.State == EntityState.Modified)
{
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
}
else if (entityEntry.State == EntityState.Added)
{
entityEntry.Property("CreatedDate").CurrentValue = DateTime.Now;
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
}
}
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
UPDATE FOLLOWING ADVICE FROM STEVE IN COMMENTS BELOW
I spent a bit more time debugging today, turns out the methods I posted above are appear to be functioning as expected i.e. when editing an existing row and saving it, only the entityEntry.State == EntityState.Modified IF statement is being called. So what I'm finding is that after saving the entity, the CreatedDate column is being overwitten with a Null value, I can see this by watching the SQL explorer after a refresh. I believe the issue is along the lines of what Steve mentions below "If it is #null then this might also explain the behavior in that it is not being loaded with the entity for whatever reason."
But i'm a little lost in tracing where this CreatedDate value is being dropped somewhere through edit/save process.
Image below shows the result at the point just before the entity is saved following an update. In the debugger I'm not quite sure where to find the entry of the CreatedDate to see what value is held at this step, but it appears to be missing from the debugger list so wandering whether somehow it doesn't know about the existence of this field when saving.
Below is the method I have in my form 'Edit' Razor page model class:
public class EditModel : PageModel
{
private readonly MyProject.Data.ApplicationDbContext _context;
public EditModel(MyProject.Data.ApplicationDbContext context)
{
_context = context;
}
[BindProperty]
public RuleParameters RuleParameters { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
RuleParameters = await _context.RuleParameters
.Include(r => r.SystemMapping).FirstOrDefaultAsync(m => m.ID == id);
if (RuleParameters == null)
{
return NotFound();
}
ViewData["SystemMappingID"] = new SelectList(_context.SystemMapping, "ID", "MappingName");
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(RuleParameters).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!RuleParametersExists(RuleParameters.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool RuleParametersExists(int id)
{
return _context.RuleParameters.Any(e => e.ID == id);
}
}
Possibly one of the reasons for this issue is the fact that I have not included the CreatedDate field in my Edit Razor Page form, so when I update the entity which in turn will run the PostAsync method server side, there is no value stored for the CreatedDate field and therefore nothing in the bag by the tine the savechangesasync method is called in my DbContext Class. But I also didn't think this was necessary? otherwise I'd struggle to see what value there is in the this process of using an inherited BaseEntity class i.e. not having to manually add the CreatedDate & UpdatedDate attribute to every model class where I want to use it...
It may be easier to just give your BaseEntity a constructor:
public BaseEntity()
{
UpdatedDate = DateTime.Now;
CreatedDate = CreatedDate ?? UpdatedDate;
}
Then you can have your DbContext override SaveChangesAsync like:
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess,
CancellationToken token = default)
{
foreach (var entity in ChangeTracker
.Entries()
.Where(x => x.Entity is BaseEntity && x.State == EntityState.Modified)
.Select(x => x.Entity)
.Cast<BaseEntity>())
{
entity.UpdatedDate = DateTime.Now;
}
return base.SaveChangesAsync(acceptAllChangesOnSuccess, token);
}
Possibly one of the reasons for this issue is the fact that I have not included the CreatedDate field in my Edit Razor Page form, so when I update the entity which in turn will run the PostAsync method server side, there is no value stored for the CreatedDate field and therefore nothing in the bag by the tine the savechangesasync method is called in my DbContext Class.
That's true.Your post data does not contains the original CreatedDate,so when save to database, it is null and could not know what the exact value unless you assign it before saving.It is necessary.
You could just add below code in your razor form.
<input type="hidden" asp-for="CreatedDate" />
Update:
To overcome it in server-side,you could assign data manually:
public async Task<IActionResult> OnPostAsync()
{
RuleParameters originalData = await _context.RuleParameters.FirstOrDefaultAsync(m => m.ID == RuleParameters.ID);
RuleParameters.CreatedDate = originalData.CreatedDate;
_context.Attach(RuleParameters).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
I don't suspect EF is doing this, but rather your database, or you're inadvertently inserting records instead of updating them.
A simple test: Put break-points in your SaveChangesAsnc method within both the Modified and Added handlers and then run a unit test that loads an entity, edits it, and saves. Which breakpoint is hit? If the behavior seems to be normal with a simple unit test, repeat again with your code.
If the Modified breakpoint is hit, and only the Modified handler is hit then check the state of the CreatedDate value in the entity modified. Does it still reflect the original CreatedDate? If yes, then it would appear that something in your schema will be overwriting it on save. If no then you have a bug in your code that has caused it to update. If it is #null then this might also explain the behaviour in that it is not being loaded with the entity for whatever reason. Check that the property has not been configured as something like a Computed property.
If the Added breakpoint is hit at all, then this would point at a scenario where you're dealing with a detached entity, such as an entity that was read from a different DB Context and being associated to another entity in the current DB Context and saved as a byproduct. When a DbContext encounters an entity that was loaded and disassociated with a different DbContext, it will treat that entity as a completely new entity and insert a new record. The biggest single culprit for this is invariably MVC code where people pass entities to/from views. Entity references are loaded in one request, serialized to the view, and then passed back on another request. Devs assume they are receiving an entity that they can just associate to a new entity and save, but the Context of this request doesn't know about that entity, and that "entity" isn't actually an entity, it is now a POCO shell of data that the serializer created. It's no different to you newing up a new class and populating fields. EF won't know the difference. The result of this is you will trip the Added condition for your entity, and after completion you will have a duplicate record. (with different PK if EF is configured to treat PKs as Identity)
So an example is an Order screen: When presenting a screen to create a new order I may have loaded the Customer and passed that to the view to display customer information and will want to associate to the new order:
var customer = context.Customers.Single(x => x.CustomerId == 15);
var newOrder = new Order { Customer = customer };
return View(newOrder);
This looks innocent enough. When we go to save the new order after setting their details:
public ActionResult Save(Order newOrder)
{
context.Orders.Add(newOrder);
newOrder.Customer.Orders.Add(newOrder);
context.SaveChanges();
// ...
}
newOrder had a reference to Customer #14, so all looks good. We're even associating the new order to the customer's order collection. We might even want to have updated fields on the customer record to reflect a change to the Modified date. However, newOrder in this case, and all associated data including .Customer are plain 'ol C# objects at this point. We've added the new order to the Context, but as far as the context is concerned, the Customer referenced is also a new record. It will ignore the Customer ID if that is set as an Identity column and it will save a brand new Customer record (ID #15 for example) with all of the same details as Customer ID 14 and associate that to the new order. It can be subtle and easy to miss until you start querying Customers and spotting duplicate looking rows.
If you are passing entities to/from views, I'd be very wary of this gotcha. Attaching and setting modified state is one option, but that involves trusting that the data has not been tampered with. As a general rule, calls to update entities should never pass entities & attach them, but rather re-load those entities, validate row version, validate the data coming in, and only copy across fields you expect should ever be modified before saving the entity associated to the DbContext.
Hopefully that gives you a few ideas on things to check to get to the bottom of the issue.

Entity Framework DbContext Update Fails if No Change in Field Values

When we pass our DbContext an object whose values have not changed, and try to perform an Update we get a 500 internal server error.
A user may open a dialog box to edit a record, change a value, change it back and then send the record to the database. Also we provide a Backup and Restore function and when the records are restored, some of them will not have changed since the backup was performed.
I was under the impression that a PUT would delete and re-create the record so I didn't feel there would be a problem.
For example, having checked that the Activity exists my ActivityController is as follows:
var activityEntityFromRepo = _activityRepository.GetActivity(id);
// Map(source object (Dto), destination object (Entity))
_mapper.Map(activityForUpdateDto, activityEntityFromRepo);
_activityRepository.UpdateActivity(activityEntityFromRepo);
// Save the updated Activity entity, added to the DbContext, to the SQL database.
if (await _activityRepository.SaveChangesAsync())
{
var activityFromRepo = _activityRepository.GetActivity(id);
if (activityFromRepo == null)
{
return NotFound("Updated Activity could not be found");
}
var activity = _mapper.Map<ActivityDto>(activityFromRepo);
return Ok(activity);
}
else
{
// The save failed.
var message = $"Could not update Activity {id} in the database.";
_logger.LogWarning(message);
throw new Exception(message);
};
My ActivityRepository is as follows:
public void UpdateActivity(Activity activity)
{
_context.Activities.Update(activity);
}
If any of the fields have changed then we don't get the error. Do I have to check every record for equality before the PUT? It seems unnecessary.
Perhaps I have missed something obvious. Any suggestions very welcome.
There is a lot of code missing here.
In your code you call your SaveChangesAsync (not the EF SaveChangesAsync).
Probably (but there is not the code to be sure) your SaveChangesAsync is something that returns false if there is an exception (and is not a good pattern because you "loose" the exception info) or if DbSet.SaveChangesAsync returns 0.
I think (but there is a lot of missing code) that this is your case. If you don't make any changes, SaveChangesAsync returns 0.
EDIT
The System.Exception is raised by your code (last line). EF never throws System.Exception.

Why doesn't WebAPI2 with Entity Framework automatically create a transaction for me?

I have a WebAPI2 Restful services API and I am using SQL Server database with Entity Framework. I have PUT methods like this
/*
* This changes the Study Status.
*/
[HttpPut, Route("ResponseSetStatus/{id:int}")]
public IHttpActionResult UpdateResponseSetStatus(int id, [FromUri] string status = null)
{
var db = new MyContext(MyContext.EntityContextString);
var responseSet = db.ResponseSets.FirstOrDefault(x => x.ResponseSetId == id);
if (responseSet == null)
{
return NotFound();
}
// ADD ONE SECOND DELAY HERE FOR TESTING
Thread.Sleep(1000);
responseSet.Status = status;
db.SaveChanges();
return Ok();
}
I thought this would work! But it fails. One of the columns in the database is a rowVersion (to prevent lost updates). When I call this function from multiple clients I get exception...
An exception of type 'System.Data.Entity.Infrastructure.DbUpdateConcurrencyException' occurred in EntityFramework.dll but was not handled in user code
because of rowVersion mismatch. Do I really need an explicit transaction for all my update apis? I thought the framework is supposed to do that for me.
Since no one has answered, I will. Yes, WebAPI2 does not wrap the call in a transaction. That would be silly, if you think about it. Also the code
using (var db = new MyContext()) {
// do stuff
}
does not implicitly create a transaction. Therefore, when you implement a RESTFUL PUT method to update your database, you have three options: (1) call db.SaveChanges() one time only and hope for the best, as the OP code, or (2) you can add a rowVersion column, and call db.SaveChanges() with try-catch in a loop, or (3) you can create an explicit transaction.
In my opinion, option 1 is evil, and option 2 is a terrible hack that was invented because transactions did not exist prior to EF6.
The correct way to implement Update:
[HttpPut, Route("ResponseSetStatus/{id:int}")]
public IHttpActionResult UpdateResponseSetStatus(int id, [FromUri] string status = null)
{
using (var db = new MyContext(MyContext.EntityContextString))
{
using (var tran = db.Database.BeginTransaction())
{
var responseSet = db.ResponseSets.FirstOrDefault(x => x.ResponseSetId == id);
if (responseSet == null)
{
return NotFound();
}
// ADD ONE SECOND DELAY HERE FOR TESTING
Thread.Sleep(1000);
responseSet.Status = status;
tran.Commit();
}
}
return Ok();
}
Please note that try-catch is not necessary. If anything fails, the using tran will automatically rollback, and the WebAPI2 will send a nice 500 response to the client.
p.s. i put the db = new MyContext also in using, because it's the right way to do it.

Sqlite EF global lock write

I have an Sqlite database mapped in an Entity Framework context.
I write on this database from several threads (bad idea, i know). However i tried using a global lock for my application like this:
partial class MyDataContext : ObjectContext
{
public new int SaveChanges()
{
lock (GlobalWriteLock.Lock)
{
try
{
int result = base.SaveChanges();
Log.InfoFormat("fff Save changes performed for {0} entries", result);
return result;
}
catch (UpdateException e)
{
throw e;
}
}
}
}
Still, i get the database file locked exception all the way down from sqlite itself. How can this be possible?
The only explanation I can see is that the base.SaveChanges method returns before the database gets unlocked and continues work asynchronously after returning.
Is this the case? If yes, how can I overcome this issue?
Note: My commits are usually updates of 1-100 entries and/or inserts of about 1-100 entries at a time.

Getting access to newly inserted Identity ID before SaveChanges method will be called

I'm using the LINQ Entity Framework and I've came across the scenario where I need to access the newly inserted Identity record before performing multiple operations using procedure.
Following is the code sinppet:
public void SaveQuote(Domain.Quote currentQuote)
{
try
{
int newQuoteId;
//Add quote and quoteline details to db
if (currentQuote != null)
{
using (QuoteContainer quoteContainer = new QuoteContainer())
{
**quoteContainer.AddToQuote(currentQuote);**
newQuoteId = currentQuote.QuoteId;
}
}
else return;
// Execution of some stored Procedure by using above newly generated QuoteId
}
catch (Exception ex)
{
throw ex;
}
}
In the next function
quoteContainer.SaveChanges(); will get called to commit the DB changes.
Can any one suggest whether the above approach is correct?
correct so far.
remember: you cannot get IDENTITY until insert has occured! on an update, your entity already holds the IDENTITY (mainly PK)