I simply try to update an entity with method ProjeleriGuncelle below.
When I try to access the original values of the object in an overridden SaveChangesAsync with EntityEntry.OriginalValues.ToObject(), I see that the object contains the current values instead of the old ones.
Am I missing something? I expect clonedTypedEntity to have the values before update.
public async Task<ActionResult<int>> ProjeleriGuncelle(Proje proje)
{
var projeFound = DataContext.Projeler.AsNoTracking().FirstOrDefault(p => p.EntegrasyonId == proje.EntegrasyonId);
var entry = DataContext.Entry<Proje>(projeFound);
//Database entry is updated with the proje object as expected
entry.CurrentValues.SetValues(proje);
entry.State = EntityState.Modified;
await DataContext.SaveChangesAsync();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
ChangeTracker.DetectChanges();
var modifiedEntries = this.ChangeTracker.Entries();
foreach (var modifiedEntry in modifiedEntries)
{
//modifiedEntry.OriginalValues.ToObject() returns the object with the currrent values instead of the original values before update
if (modifiedEntry.OriginalValues.ToObject() is not IVersionable clonedTypedEntity) continue;
clonedTypedEntity.Id = 0;
clonedTypedEntity.UstSurumId = (modifiedEntry.Entity as IVersionable)?.Id;
Add(clonedTypedEntity);
}
return base.SaveChangesAsync(cancellationToken);
}
Update :
When I remove AsNoTracking() when querying for projeFound, modifiedEntry.OriginalValues.ToObject() really returns values before update. But I don't understand the behaviour, because with var entry = DataContext.Entry<Proje>(projeFound); statement, I expect that entry object is tracked, and by updating it with entry.CurrentValues.SetValues(proje); I expect to have access to original values.
For EF the original values are the values when it starts tracking, which is when entry.State = EntityState.Modified; is called. EF is oblivious of the changes that happened in the previous line of code.
You could solve it by swapping both lines:
entry.State = EntityState.Modified; // attaches and stores original values
entry.CurrentValues.SetValues(proje);
An improvement would be to attach the entry and let the change tracker figure out if the entity was really modified.
entry.State = EntityState.Unchanged; // attaches and stores original values
entry.CurrentValues.SetValues(proje);
Now SetValues only marks actually changed properties as modified and the update statement can be much slimmer, or even not happen at all.
I had the same problem in this days....
In my Dbcontext i set this property:
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
this property disable all tracking and you can not have their OriginalValue.
Related
I am trying to update two tables Situation and SituationCategory, but it is not updating as mentioned in below code and image.
public async Task<bool> UpdateSituation(int id, SituationsDto data)
{
Situations result = _mapper.Map<SituationsDto, Situations>(data);
result.Deleted = true;
_context.Entry(result).State = EntityState.Modified;
await _context.SaveChangesAsync();
SituationCategories situationCategory = new SituationCategories();
if (result.SituationCategory != null)
{
if (situationCategory != null)
{
situationCategory.Description = result.SituationCategory.Description;
}
}
await _context.SaveChangesAsync();
}
In this screenshot, I have highlighted the data which should be updated:
Please answer
An EF context knows nothing about objects unless you attach a given object to a context, or, you initially retrieved an object from a context.
Instead of just marking the entity as modified:
_context.Entry(result).State = EntityState.Modified;
You'll need to call Update(), which, begins tracking the entity & marks it as modified, so, when you call SaveChanges(), changes will be written to DB:
_context.Update(result);
PS. I would only call SaveChanges() once, in this case, at the end of your method.
I have read quite a few posts about this, and I can't see how my situation is different, but it must be because it still doesn't update.
Basically, my method receives a detached entity in a message. I check it's key to see if it already exists. If it does not exist I add it to the database. (this works fine) If it exists I would like to update its values.
Here is my code:
InteropObject clientObject = (InteropObject)message.ItemToAddUpdate;
bool exists = context.InteropObjects.Any(o => o.GUID == clientObject.GUID);
if (!exists)
{
context.InteropObjects.AddObject(clientObject);
}
else
{
context.Attach(clientObject);
context.ObjectStateManager.GetObjectStateEntry(clientObject).SetModified();
}
context.SaveChanges();
thanks for the help!
The problem is that in the old ObjectContext API, setting the state to Modified does not set the properties of the entity to Modified. In the DbContext API this is fixed internally by a call that does do that.
In the ObjectContext API, you can get the same effect by setting the properties of the attached entity:
context.Attach(clientObject);
var ose = context.ObjectStateManager.GetObjectStateEntry(clientObject);
// Obtain an object array containing current values
var values = new object[ose.CurrentValues.FieldCount];
ose.CurrentValues.GetValues(values);
// "Overwrite" CurrentValues by these values:
ose.CurrentValues.SetValues(values);
So you set the properties by the values they already have, but this triggers the state manager to mark the property as Modified.
Why is this not updating in the database, but the commented out version does work?
public bool InsertOrUpdateItems(Items item)
{
using (var dbContext = new MyEntities())
{
var items = dbContext.Items.Find(item.ItemId);
if (items != null)
{
dbContext.Items.Attach(items);
dbContext.Entry(items).State = EntityState.Modified;
//dbContext.Entry(items).CurrentValues.SetValues(item);
dbContext.SaveChanges();
}
There are no exceptions being thrown, it just wont update. Even though the record is found.
If the line in question is commented out, there are no new values to update. Notice that you:
Get object named items from DB
You attach it (although it is attached by default)
You try to save the same items
Object that you pass as a parameter for InsertOrUpdateItems method (item) is not even touched in your code, except reading its Id for use with Find()
I have this entity, want to update using entityframework
EmployeeModel employee = new EmployeeModel
{
Id = 1000, //This one must
FirstName = modifiedValue,
Email = modifiedValue,
LastName = originalValue,
Phone = originalValue
};
Code to update
_db.ObjectStateManager.ChangeObjectState(employee, EntityState.Modified);
_db.SaveChanges();
This is the SQL statement got once updated
Update Employee set Id=1138,FirstName='modifiedValue',Email='modifiedValue',LastName= 'OriginalValue',phone='originalValue' where Id=1138
But I am expecting this
Update Employee set FirstName='modifiedValue', Email='modifiedValue' where Id=1138.
I dont know what I am missing here. Please let me know.
This problem is common when dealing with DTOs. An employee entity is fetched from the database, mapped to a DTO and sent over the wire. The client then modifies this DTO and sends it back to the server.
When you touch (set) a property on an EF entity, EF will assume that the value has been changed. Even if the old value and the new value are exactly the same.
The same problem occurs when you map the DTO to a new Entity and attach it to EF and updating its status to 'Modified'.
Using AutoMapper:
// This will result in the full update statement
var employee = AutoMapper.Mapper.Map<EmployeeDto, Employee>(dto);
// This will result in a smaller update statement (only actual changes)
var employee = dbContext.Employees.Find(dto.Id);
AutoMapper.Mapper.Map(dto, employee);
Or, manually (I would avoid doing this, but just for the sake of completeness):
// This will result in a smaller update statement (only actual changes)
var employee = dbContext.Employees.Find(dto.Id);
if (employee.Email != dto.Email )
employee.Email = dto.Email;
There are probably some other ways for dealing with this problem... but using AutoMapper together with Entity Framework correctly is definitely one of the easiest ways.
This is the solution I got
var entity = _db.CreateObjectSet<Employee>();
entity.Detach(employee);
entity.Attach(employee);
foreach (string modifiedPro in employeeModel.ModifiedProperties){
_db.ObjectStateManager.GetObjectStateEntry(employee).SetModifiedProperty(modifiedPro);}
_db.SaveChanges();
Only modified values in the sql update statement
Update Employee set FirstName='modifiedValue', Email='modifiedValue' where Id=1138.
If anybody knows better answer than this, Please post your suggestions
You can try this way
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
var UpdatedObj = (Person) Entity.CheckUpdateObject(oldobj, model);
db.Entry(oldobj).CurrentValues.SetValues(UpdatedObj);
}
public static object CheckUpdateObject(object originalObj, object updateObj)
{
foreach (var property in updateObj.GetType().GetProperties())
{
if (property.GetValue(updateObj, null) == null)
{
property.SetValue(updateObj,originalObj.GetType().GetProperty(property.Name)
.GetValue(originalObj, null));
}
}
return updateObj;
}
Here's what I'd like to do:
var myCustomer = new Customer();
myCustomer.Name = "Bob";
myCustomer.HasAJob = true;
myCustomer.LikesPonies = false;
Then I'd like to pass it into an update method:
public UpdateCustomer(Customer cust)
{
using(var context = dbcontext())
{
var dbCust = context.Customers.FirstOrDefault(c => c.Name == cust.Name);
if(dbCust != null)
{
// Apply values from cust here so I don't have to do this:
dbCust.HasAJob = cust.HasAJob;
dbCust.LikesPonies = cust.LikesPonies
}
context.SaveChanges();
}
}
The reason for this is I'm working in multiple different parts of my application, and/or across DLLs. Is this possible?
EDIT: Found this question to be immensely useful:
Update Row if it Exists Else Insert Logic with Entity Framework
If you are sure that the entity is in the database and you have key you would just Attach the object you have to the context. Note that attached entities are by default in Unchanged state as the assumption is that all the values of properties are the same as in the database. If this is not the case (i.e. values are different) you need to change the state of the entity to modified. Take a look at this blog post: http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx it describes several sceanrios including the one you are asking about.