Breeze SaveChanges always throws DbUpdateConcurrencyException when deleting entity - c#

I have just enabled the "Concurrency Mode" property to fixed of one of my entity.
Everything works great when I try to update.
But when I try to delete the entity, I always get this error :
DBUpdateConcurrencyException
Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded. Refresh ObjectStateManager entries.
Is there any way to disable DBUpdateConcurrencyException for delete operation? If not, how can I manage this type of exception?
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
try
{
return _breezeComponent.SaveChanges(saveBundle);
}
catch (DbUpdateConcurrencyException ex)
{
//Workaround needed
}
}
BTW, I have already looked at these kinds of solution : How to ignore a DbUpdateConcurrencyException when deleting an entity . Is there any way I can integrate this code with Breeze engine?
EDIT:
I have upgraded from version 1.4.5 to 1.4.7 and I still have the same problem.
If I look at the JSON object, would changing the entityState from "Deleted" to "Detached" would be a solution? Is there any setting in Breeze that can help me do that?
{
"entities": [
{
"EventId": 11111,
"EventName": "Jon Doe",
"EventCity": "Montreal",
"EventDate": "2014-01-24T00:00:00Z",
"TermDate": "2014-01-08T00:00:00Z",
"Insertedby": "Terry",
"InsertDate": "2014-01-06T14:31:14.197Z",
"Updatedby": "Terry",
"UpdateDate": "2014-01-07T15:50:53.037Z",
"entityAspect": {
"entityTypeName": "Event:#Cds.Corpo.GuestList.Models",
"defaultResourceName": "Events",
"entityState": "Deleted",
"originalValuesMap": {},
"autoGeneratedKey": {
"propertyName": "EventId",
"autoGeneratedKeyType": "Identity"
}
}
}
],
"saveOptions": {}
}

Just a guess, but do you have some form of cascaded delete turned on in your database. If so, then the issue may be that the delete of a parent is causing the child to also be deleted and when the breeze server code kicks in it tries to delete the 'already' deleted child again. When this delete cannot find the child it throws the concurrency exception.
The way to avoid this is to use the BeforeSaveEntities interception point and remove all of the children of any deleted entities ( that are part of the cascade delete relationship) from the 'saveMap'. See the Breeze documentation on BeforeSaveEntities on this page: http://www.breezejs.com/documentation/contextprovider

There was a mecanism hidden in our application framework that did update the property before saving changes to the database. So this wasn't a Breeze issue.
A filter has now been added to the solution to exclude this pattern when deleting.

Related

How to audit role removals?

When a role is removed from a user, I need to track who did it (ie. which AbpUser) and when they did it.
The obvious solution is to redefine the UserRole entity so that it inherits from FullAuditedEntity instead of CreationAuditedEntity, but the UserRole entity is defined in a nuget package so I cannot simply change the definition.
Is there a way to achieve this behavior that I am not seeing?
Here is what I have tried so far.
Approach 1: I tried handling this at the database level by setting up a delete trigger on the AbpUserRole table which would insert a record into a AbpUserRoleDeleted table, but I can't think of a way to find out which AbpUser made the deletion with this approach. I can only track when the action happened.
Approach 2: I tried listening for the EntityDeleted domain event on UserRole entities, but it does not seem to get triggered. Interestingly, the EntityUpdated event is triggered when I remove a role from a user, but even assuming that this event would only ever be triggered when a UserRole is deleted, the event data still does not include who made the deletion. If it did, I could manually save the audit information in a separate table just like a database delete trigger would, but this time I would have the AbpUser that was responsible for the deletion.
Approach 3: I tried extending the UserRole entity by following the steps here. I was able to implement the IDeletionAudited interface and generate a migration that creates the associated columns on the AbpUserRoles table, but removing a role from a user performs a hard delete instead of a soft delete so I can't tell if the columns even get populated. I am assuming they do not.
Approach 4: I tried enabling Entity History for the UserRole entity, but it seems to only track when a UserRole entity is created.
This seems to work fine.
//src\aspnet-core\src\Company.App.EntityFrameworkCore\EntityFrameworkCore\AppDbContext.cs
namespace Company.App.EntityFrameworkCore
{
public class AppDbContext : AbpZeroDbContext<Tenant, Role, User, AppDbContext>, IAbpPersistedGrantDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
ChangeTracker.StateChanged += OnEntityStateChanged;
}
private void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
if (e.Entry.Entity is UserRole && e.NewState == EntityState.Deleted)
{
//update instead of delete
e.Entry.State = EntityState.Modified;
e.Entry.CurrentValues["IsDeleted"] = true;
e.Entry.CurrentValues["DeletionTime"] = DateTime.Now;
e.Entry.CurrentValues["DeleterUserId"] = AbpSession.UserId;
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//use query filter on the `IsDeleted` shadow property
modelBuilder.Entity<UserRole>().HasQueryFilter(p => !EF.Property<bool>(p, "IsDeleted"));
modelBuilder.Entity<UserRole>().Property<bool>("IsDeleted");
modelBuilder.Entity<UserRole>().Property<DateTime?>("DeletionTime").IsRequired(false);
modelBuilder.Entity<UserRole>().Property<long?>("DeleterUserId").IsRequired(false);
}
}
}

Update entity framework entity with multiple relationships to other entities

I am using web api with entity framework core. I have a Worker entity with relationships to Company (via CompanyId), Status (via StatusId) and Position (via PositionId). Using a request through the API I want to be able to update a worker.
I want to pass a request of WorkerId and PositionId only. In the data layer I check to find the PositiionId exists from the request, if not the return back to the controller with message of Position not found. Below is a sample of the code without the Company and Status checks.
Public void Update(WorkerEntity worker)
{
var workerRecord = _context.WorkerEntity.SingleOrDefault(w => w.Id == worker.Id);
if (workerRecord == null)
{
Log.Logger("Cannot find the worker with Id " + worker.Id);
return;
}
var positionRecord = _context.PositionEntity.SingleOrDefault(w => w.Id == worker.PositionId);
if (positionRecord == null)
{
Log.Logger("Cannot find the position with Id " + worker.PositionId);
return;
}
workerRecord.Position = positionRecord;
_context.SaveChanges();
}
I have also tried a simple approach using .Single to catch an error if the record does exist but the catch only output the generic "Sequence contains no elements" which isn't helpful in knowing with entity failed.
Is there an easier way to check the relationship Ids the user has added in the request are not bogus without having lines and code checking the Id exists? This table may have 3 relationships but I have another table that has 5 which would have 5 if null checks.
To get a meaningful message you would have to check each field manually. It can be simplified little bit using Any() method.
if (!context.PositionEntity.Any(w => w.Id == worker.PositionId)
{
Log.Logger("Cannot find the position with Id " + worker.PositionId);
return;
}
From the EF exception you wont be able to get a descriptive error message.
But you do you really want to do this? I assume you have these lookup values (Positions, statuses etc) as dropdowns in front-end where you load them with values from your database. So in the normal scenario an incorrect value cannot be selected and send to the API. However somehow if it does, you have the EF core default error to avoid screwing up things.
Cheers,

Are ElasticSearch query results cached?

I'm using ElasticSearch 2.3 and ASP.NET web application. I'm calling ElasticSearch through its REST API using C# HttpClient.
I have a problem when trying to add new values to an array.
Here's essentially the queries that I'm performing:
Step 1: Get note
POST /notes/note/_search
{
"query" : {
"term" : { "_id" : 1 }
}
}
There's only 1 note and the result shows that the note contains an empty array of attachments
Step2: Update note attachments array
POST: /notes/note/1/_update
{
"doc" : {
"Attachments" : [ "Test" ]
}
}
Step3: Get note again
POST: /notes/note/_search
{
"query" : {
"term" : { "_id" : 1 }
}
}
The result shows that the note still contains an empty Attachments array.
However, when I Search/MatchAll from Kibana, I see that the Attachments array has been updated with the new item. However, when running all those steps from ASP.NET, I'm not getting the updated document when immediately searching for it right after the update.
Is this caused by some kind of caching ?
What must I do to get consistent state of the document after an update has been performed ?
Any help appreciated!
Thanks
What could be happening is that the refresh did not come by yet. Than you are able to do a get by id, but the update is not available yet for search. So force a refresh before executing the search. When inserting a lot of documents this does have a performance impact. But for testing you should be fine.

EF6 The operation cannot be completed because the DbContext has been disposed

I know there are a bunch of these error messages in SO because I've read them all, sadly to no avail.
I have a WebApi controller that is getting a set of 'Persons' via EF6 from a SQL Server DB. Very simple example
Things I've tried so far, with no success:
- Disabling proxy generation
- Disabling lazy loading
- Adding Includes to get child refs with both linq and string parameters.
- Replacing the using with try/finally -> dispose of DbContext.
- Remove "application/xml" from supported media types via WebApiConfig
- Ensured circular dependencies are attributed with [IgnoreDataMember]
- ... More I cannot remember :)
Here is the PersonController Get method:
public IEnumerable<Person> Get()
{
try
{
IEnumerable<Person> persons = null;
using (PersonContext entities = new PersonContext())
{
entities.Configuration.ProxyCreationEnabled = false;
entities.Configuration.LazyLoadingEnabled = false;
persons = entities.Persons.Take(5);
}
return persons;
}
catch(Exception ex)
{
...
}
}
Now no exception is thrown at ANY point in the controller. The exception is however displayed in the browser:
"<Error><Message>An error has occurred.<\Message>
<ExceptionMessage>The 'ObjectContent`1' type failed to serialize the response body for content type 'application\/json; charset=utf-8'.
<\ExceptionMessage>
<ExceptionType>System.InvalidOperationException<\ExceptionType>
<StackTrace/>
<InnerException>
<Message>An error has occurred.<\/Message>
<ExceptionMessage>**The operation cannot be completed because the DbContext has been disposed.**<\/ExceptionMessage>
<ExceptionType>System.InvalidOperationException<\/ExceptionType>
<StackTrace> at
System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
The error tells me that something else is trying to read the context after the using clause has popped but I'm at a loss to know what that could be? As you can see I copy the enumerated data from the context into the local list before returning that. Got me stuffed!
Any suggestions appreciated.
The line
persons = entities.Persons.Take(5);
is a definition of how to retrieve data, but the data itself is not yet retrieved at that point ('delayed execution'). The line is located inside the using(){} construct, so right after that the DbContext is disposed. A while later the View needs the data, the DbContext is consulted, but it is closed already.
Solution:
Retrieve all data before closing the DbContext. This is frequently done using ToArray() or ToList(), whichever suits you best.
So the line should be e.g.:
persons = entities.Persons.Take(5).ToArray();
persons = entities.Persons.Take(5).ToList() or ToArray.
You are actually closing the connection before fetching the data.
If this doesn't work, try remove using clause for dbcontext just to check whats happening.

How to use TryUpdateModel

I am readying this tutorial. I see from this tutorial that for update the author is using the following code:
....
var studentToUpdate = db.Students.Find(id);
if (TryUpdateModel(studentToUpdate, "",
new string[] { "LastName", "FirstMidName", "EnrollmentDate" }))
{
try
{
db.Entry(studentToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
...
}
But I don't understand why the following line is needed:
db.Entry(studentToUpdate).State = EntityState.Modified;
When I remove this line, the code still works well and update is done perfectly.
Can someone help me with whether that line is needed? If so, why when I remove it, the update works well.
It works well because you find the studentToUpdate from your context, that's way the entity is attached and the changes that are made by the TryUpdateModel method are saved when you call the SaveChanges method.
If you were working with a detached entity, for example doing this:
var studentToUpdate=new Student(){Id=id};
if (TryUpdateModel(studentToUpdate, "",
new string[] { "LastName", "FirstMidName", "EnrollmentDate" }))
{
try
{
db.Entry(studentToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
...
}
In this case you have to call the Entry method to attach the entity to your context and change its state.
That line is explicitly telling the EF context that the entity has been modified and needs to be updated the next time SaveChanges() is called. The reason everything still works when you remove that line is that the context usually tracks those changes automatically for you. I have yet to come across a situation where I have needed to fiddle with EF's automatic change tracking in production, it seems to work well.
See How change tracking works in Entity Framework for a bit more info.

Categories

Resources