Given the following function the variable currentModel is already the modified model that I want to update and it might have some properties different from the ones in the database and this function correctly updates the modified values.
Now I want to track the changes made before the update, the problem is that the ChangeTracker is detecting all properties as modified even when only one is acctualy different from the original model.
Is there a way to use ChangeTracker while also updating the statement with EntityState.Modified (reference)?
Here is the function used:
public void SaveCustomer(Clients currentModel)
{
var objFromDbAct = _db.Clients.Local.FirstOrDefault(u => u.Recid == currentModel.Recid);
if (objFromDbAct != null) { _db.Entry(objFromDbAct).State = EntityState.Detached; }
_db.Entry(currentModel).State = EntityState.Modified;
_db.ChangeTracker.DetectChanges();
string trackChanges = _db.ChangeTracker.DebugView.LongView;
_db.SaveChanges();
}
Here is the return from ChangeTracker.DebugView.LongView (I have removed some fields to simplify, but the same applies to all of them. In this case only Zip was changed.
Clients {Recid: 6391} Modified
Recid: 6391 PK
Additional: '' Modified
Addr1: '12345 Somewhere' Modified
Addr2: '' Modified
Addr3: <null> Modified
Zip: '000002222' Modified
PortalUsers: <null>
You can use existing methods on DbEntityEntry to reload database values.
public void SaveCustomer(Clients currentModel)
{
var objFromDbAct = _db.Clients.Local.FirstOrDefault(u => u.Recid == currentModel.Recid);
if (objFromDbAct != null) { _db.Entry(objFromDbAct).State = EntityState.Detached; }
_db.Entry(currentModel).GetDatabaseValues();
_db.ChangeTracker.DetectChanges();
string trackChanges = _db.ChangeTracker.DebugView.LongView;
_db.SaveChanges();
}
Optionally, if you track only this exact entity, you may clear change tracker to simplify the code.
public void SaveCustomer(Clients currentModel)
{
_db.ChangeTracker.Clear();
_db.Entry(currentModel);.GetDatabaseValues();
_db.ChangeTracker.DetectChanges();
string trackChanges = _db.ChangeTracker.DebugView.LongView;
_db.SaveChanges();
}
UPDATE:
I have initially missed one additional method call as GetDatabaseValues method returns values from database, but not internally setting those anywhere. Also _db.Entry(value); is adding entity into the change tracking, but in detached state, thus changes are not detected.
public void SaveCustomer(Clients currentModel)
{
// clear tracked entries
_db.ChangeTracker.Clear();
// attach current model
var entry = _db.Attach(currentModel);
// get database values
var originalValues = entry.GetDatabaseValues();
// set database values as original values
entry.OriginalValues.SetValues(originalValues);
string trackChanges = _db.ChangeTracker.DebugView.LongView;
_db.SaveChanges();
}
Related links:
DbEntityEntry.GetDatabaseValues Method
DbPropertyValues.SetValues Method
ChangeTracker.Clear Method
It is possible to copy the entity's values from a other class instance as follows :
public void SaveCustomer(Client currentModel)
{
var objFromDbAct = _db.Clients.Local.FirstOrDefault(c => c.Recid == currentModel.Recid);
if (objFromDbAct == null)
{
_db.Update(currentModel);
}
else
{
var preEntity = _db.Entry(objFromDbAct);
preEntity.CurrentValues.SetValues(currentModel);
}
_db.ChangeTracker.DetectChanges();
string trackChanges = _db.ChangeTracker.DebugView.LongView;
_db.SaveChanges();
}
trackChanges value :
Client {Recid: 1} Modified
Recid: 1 PK
Addr1: 'Address 1'
Addr2: 'Address 2'
Zip: 'Modified' Modified Originally 'Zip'
And the SQL executed by SaveChanges:
UPDATE [Clients] SET [Zip] = #p0
WHERE [Recid] = #p1;
Related
I set up a Generic repository using this code for update
private void AttachIfNot(TEntity entityToActive)
{
if (_dbContext.Entry(entityToActive).State == EntityState.Detached)
{
_dbSet.Attach(entityToActive);
}
}
private void UpdateEntity(TEntity entityToUpdate)
{
AttachIfNot(entityToUpdate);
_dbContext.Entry(entityToUpdate).State = EntityState.Modified;
}
It just attach the entity and set the modified state to save.
But when I use efocre ownsone to map a value object,the update entity function is not working.
I found out that it only works when I set Valueobject to modified too.
_dbContext.Entry(entityToUpdate).State = EntityState.Modified;
_dbContext.Entry(entityToUpdate.Valueobject).State = EntityState.Modified;
But It is hard for me to specify all the value objects in a Generic Repository.
This is code also has problems with one to many or other relations.
The working way is like this:
Classroom classroom = new Classroom
{
Id = 1,
Name = "b",
Students = new List<Student>
{
new Student()
{
Name = "aa",
Id = 2
}
}
};
if (_defaultDbContext.Entry(classroom).State == EntityState.Detached)
{
_defaultDbContext.Classrooms.Attach(classroom);
foreach(var stu in classroom.Students)
{
_defaultDbContext.Students.Attach(stu);
}
}
_defaultDbContext.Entry(classroom).State = EntityState.Modified;
foreach (var stu in classroom.Students)
{
_defaultDbContext.Entry(stu).State = EntityState.Modified;
}
_defaultDbContext.SaveChanges();
I found out one way is get the entity form repo then update it using automapper:
targetEntity = repo.GetById(entityId);
automapper.map(souceEntity,targetEntity);
//or
automapper.map(souceDto,targetEntity);
_dbContext.Save();
The entity comes by query, so the change will be tracked.
But I have to configure the automapper with this entity map when I want to change entity
CreateMap<EntityType, EntityType>();
I think it's not the best solution. Is there a bettere way?
DbContext.Update would be fine to fix this problem.
see:
https://www.learnentityframeworkcore.com/dbcontext/change-tracker
We have overriden the SaveChanges method because we want to set some final properties automatically upon saving and we have to set SETCONTEXT in each connection. Our current override looks as follows:
public override int SaveChanges()
{
// Use basic SaveChanges if SessionInfo is not initialized
if (SessionInfo.ContextInfo == null)
{
return base.SaveChanges();
}
// SessionInfo was initialized, so use custom logic now
// Set the SqlId according to sessioninfo for each entity to add
foreach (DbEntityEntry entry in ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added))
{
string sqlIdPropertyName =
entry.CurrentValues.PropertyNames.First(x=>x.EndsWith("SqlId");
entry.Property(sqlIdPropertyName).CurrentValue = SessionInfo.ServerSqlId;
}
// Set the IsDeleted boolean to true for each entity to delete
foreach (DbEntityEntry entry in ChangeTracker.Entries()
.Where(x => x.State == EntityState.Deleted))
{
entry.Property("IsDeleted").CurrentValue = true;
entry.State = EntityState.Modified;
}
// Begin custom transaction if SessionInfo was set
this.Database.Connection.Open();
SessionInfo.SetContextInfo(this);
int result = base.SaveChanges();
this.Database.Connection.Close();
return result;
}
As you can see, when we add a new record to the database, the save logic sets the SqlId for the object according to the SessionInfo. However, this now depends of PropertyNames.First(), which is a risk.
The PropertyName of the SqlId we want to set is always equal to the name of the POCO class type + SqlId, so for the class "Invoice" it would be "InvoiceSqlId".
How can we get the typename of the original POCO class from a DbEntityEntry?
Try this: entry.Entity.GetType().Name
EDIT - for when you may be using proxies
var entityType = entry.Entity.GetType();
string name;
if (entityType.BaseType != null &&
entityType.Namespace == "System.Data.Entity.DynamicProxies")
{
name = entityType.BaseType.Name;
}
else
{
name = entityType.Name
}
I'm trying to update a record using EntityFramework 6 by attaching a disconnected entity. I want to update a single boolean field to false but it doesn't work. I have used sql server profiler and EF doesn't generate an update statement when calling SaveChanges on the context. But if i set the value to true, it works. Example:
This doesn't work:
private void UpdateUser()
{
var user = new User { ID = 5 };
this.Context.Users.Attach(user);
user.Locked = false;
this.Context.SaveChanges();
}
This works:
private void UpdateUser()
{
var user = new User { ID = 5 };
this.Context.Users.Attach(user);
user.Locked = true;
this.Context.SaveChanges();
}
I can fix by marking the property as modified like this:
this.Context.Entry(user).Property(e => e.Locked).IsModified = true;
But don't understand why does it work if the value is true and not if the value is false. There is a similar issue when trying to set a field to null.
There's probably something wrong with the entity data model or database but can't figure out what.
Default value for Boolean is false, so setting to false after attaching won't flag the property as changed.
You can load the entity first, then do your modifications then save changes.
var user = db.Users.Find(5);
user.Locked = false;
db.SaveChanges();
Or you can attach the entity with the field you want to update set to a different value for Boolean fields
var user = new User { Id = 5, Locked = true; };
db.Users.Attach(user);
user.Locked = false;
db.SaveChanges();
False is the default value of boolean field, and, thus, setting false again will not report to change tracker that the property was changed. as a result it is not part of insert/update statement.
If it is database first approach, your property should look similar to this(see the setter):
public global::System.Boolean Locked
{
get
{
return _Locked;
}
set
{
if (_Locked != value)
{
OnLockedChanging(value);
ReportPropertyChanging("Locked");
_Locked = StructuralObject.SetValidValue(value);
ReportPropertyChanged("Locked");
OnLockedChanged();
}
}
}
I need to update all fields except property1 and property2 for the given entity object.
Having this code:
[HttpPost]
public ActionResult Add(object obj)
{
if (ModelState.IsValid)
{
context.Entry(obj).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
return View(obj);
}
How to change it to add an exception to obj.property1 and obj.property2 for not being updated with this code?
Let's assume that you have a collection of the properties to be excluded:
var excluded = new[] { "property1", "property2" };
With EF5 on .NET 4.5 you can do this:
var entry = context.Entry(obj);
entry.State = EntityState.Modified;
foreach (var name in excluded)
{
entry.Property(name).IsModified = false;
}
This uses a new feature of EF5 on .NET 4.5 which allows a property to be set as not modified even after it has been previously set to modified.
When using EF 4.3.1 or EF5 on .NET 4 you can do this instead:
var entry = context.Entry(obj);
foreach (var name in entry.CurrentValues.PropertyNames.Except(excluded))
{
entry.Property(name).IsModified = true;
}
You can't define such an exception. You can however mark single properties as modified:
context.Entry(obj).Property(o => o.Property3).IsModified = true;
context.Entry(obj).Property(o => o.Property4).IsModified = true;
// etc.
Note that setting IsModified to false is not supported once you have marked the state of the whole entity to Modified.
For your purpose I would actually prefer to load the entity from the database and then update it using normal change tracking:
var objInDB = context.Objects.Single(o => o.Id == obj.Id);
obj.Property1 = objInDB.Property1;
obj.Property2 = objInDB.Property2;
context.Entry(objInDB).CurrentValues.SetValues(obj);
context.SaveChanges();
Note that only changed properties will be saved by default by Automatic Detect changes.
See EF 6 and EF Core articles
This question was already nicely answered, but I wanted to provide an extension method for anyone who would like to use it.
This code was developed for EF 4.3.1
//You will need to import/use these namespaces
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
//Update an entity object's specified columns, comma separated
//This method assumes you already have a context open/initialized
public static void Update<T>(this DbContext context, T entityObject, params string[] properties) where T : class
{
context.Set<T>().Attach(entityObject);
var entry = context.Entry(entityObject);
foreach(string name in properties)
entry.Property(name).IsModified = true;
context.SaveChanges();
}
Usage Example
using (FooEntities context = new FooEntities())
{
FooEntity ef = new FooEntity();
//For argument's sake say this entity has 4 columns:
// FooID (PK), BarID (FK), Name, Age, CreatedBy, CreatedOn
//Mock changes
ef.FooID = 1;
ef.Name = "Billy";
ef.Age = 85;
context.Update<FooEntity>(ef, "Name", "Age"); //I only want to update Name and Age
}
This is an update that works for .net CORE and maybe can help someone who needs a generic solucion and wants to exclude some properties base on different conditions.
I'm using reflection to iterate through the properties and update base on its property value, in this case, as example, i'm excluding the null properties.
public virtual TEntity Update(TEntity entity)
{
dbSet.Attach(entity);
dbContext.Entry(entity).State = EntityState.Modified;
var entry = dbContext.Entry(entity);
Type type = typeof(TEntity);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.GetValue(entity, null) == null)
{
entry.Property(property.Name).IsModified = false;
}
}
dbContext.SaveChanges();
return entity;
}
The answers above (most of them) use DbContext. For those who is using ObjectContext these solutions arent accessible.
Here is solution for ObjectContext strictly (EF5 .NET 4.5):
ctx.AddObject("ENTITYNAME", item);
ctx.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
var entry = ctx.ObjectStateManager.GetObjectStateEntry(item);
entry.RejectPropertyChanges("PROPERTY_TO_EXCLUDE");
The controller above has a standard edit ActionResult. I simply find rows in a database by ID and update it. Before db.SaveChanges() there is log.Save() static function that saves all changes in model to separate tables in the database.It simply check old and new values from ChangeTracker.
The problem is, i want use log.Save() after db.SaveChanges(), not before, to be sure that data was really saved.
But after, in the ChangeTracker there aren't any changes so log.Save() doesn't have anything to save.
Controller:
[HttpPost]
public ActionResult edit(int id, MyModel model)
{
var hihi = db.MyModel.First(s => s.ID == model.ID);
hihi.col1 = model.col1;
hihi.col2 = model.col2;
...
log.Save(Log.ChangeType.Edit, db, id);
^ Here i save changes to log.
db.SaveChanges();
return RedirectToAction("Index");
}
Log Class:
public void Save(ChangeType changeType, DBContext parentContext, int id)
{
DBContext db = new DBContext();
foreach (System.Data.Entity.Infrastructure.DbEntityEntry ee in parentContext.ChangeTracker.Entries())
{
foreach (string column in ee.OriginalValues.PropertyNames)
{
string oldValue = ee.OriginalValues[column].ToString();
string newValue = ee.CurrentValues[column].ToString();
if (oldValue != newValue)
{
var model = new LogModel
{
Log_Time = DateTime.Now,
Log_Operator = User.Ope_ID,
Log_Table = ee.Entity.ToString().Replace("xxx.Models.", ""),
Log_Key = id,
Log_Column = column,
Log_Type = (int)changeType,
Log_OldValue = oldValue,
Log_NewValue = newValue
};
var log = db.Log.Add(model);
db.SaveChanges();
}
}
}
}
public enum ChangeType
{
Create = 1,
Delete = 2,
Edit = 3
}
... or maybe someone has another way to save all changes in a database to another table on all controller actions, so after the project release I can see what users do.
PS. I don't what user triggers.
SaveChanges in EF4 is virtual, so you can override it, add custom logging etc.
Why don't you use try{} catch{} within Log Class and change the return parameter from 'void' to 'bool'. This would return true if the db.SaveChanges() succeeds. Then within "ActionResult edit" use bool result = log.Save(Log.ChangeType.Edit, db, id); to retrieve if the log saved the changes, then use a simple if-sentence to validate if you can save all changes to db or not.