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();
}
}
}
Related
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;
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 have a class ReportConfigurationManager which manages the CRUD operations against a UserReport entity. The two operations of interest are "Get" and "SaveUpdate". In both cases I wrap the operation in a using statement so that the DbContext is disposed at the end of the query.
Now eventually these methods will form part of a WCF service, but they may also be called internally within the service. My present difficulties are with getting a set of Unit Tests to work which call the ReportConfigurationManager directly.
I can create a new UserReport and save it (this took me a while to solve as the entity has several nested objects which already exist in the database - I needed to "Attach" each of these in turn to the context before calling Add on the UserReport in order to get it to save correctly.
My issues now are with Updates.
Despite having
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
on ALL methods which use the ReportConfigurationManager, when I came to attach a UserReport, it failed with the classic "an object with the same key already exists in the ObjectStateManager" (I thought disabling Change Tracking was meant to handle this?).
So now I have switched to using the following code which I found here
public UserReport SaveUpdateUserReport(UserReport userReport)
{
using (var context = new ReportDataEF())
{
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
if (userReport.Id > 0)
{
{
UserReport oldReport = context.UserReports.Where(ur => ur.Id == userReport.Id).FirstOrDefault();
context.Entry(oldReport).CurrentValues.SetValues(userReport);
}
}
else
{
//Need to attach everything to prevent EF trying to create duplicates in the database
context.ReportTopTypes.Attach(userReport.ReportTopType);
context.ReportWindows.Attach(userReport.ReportWindow);
context.ReportSortOptions.Attach(userReport.ReportSortOption);
foreach (var col in userReport.ReportColumnGroups)
{
context.ReportColumnGroups.Attach(col);
}
context.ReportTemplates.Attach(userReport.ReportTemplate);
//just add the new data
context.UserReports.Add(userReport);
}
context.SaveChanges();
}
return userReport;
}
My concern is that my code seems laborious - I need to get a copy of the old object before I can save the updated copy? And I'm not convinced by my Save New logic either.
So is this approach correct, or is there a better way of writing the above?
Further details of other stuff going on:
Because I'll be sending the object graphs over WCF. I've implemented Eager Loading:
public static DbQuery<ReportTemplate> IncludeAll(this DbQuery<ReportTemplate> self)
{
return self
.Include("ReportColumnGroups.ReportColumns.ReportColumnType")
.Include("ReportColumnGroups.ReportColumnType")
.Include("ReportSortOptions.ReportSortColumns.ReportColumn.ReportColumnType")
.Include("ReportSortOptions.ReportSortColumns.ReportSortType");
}
public static DbQuery<UserReport> IncludeAll(this DbQuery<UserReport> self)
{
return self
.Include("ReportTemplate")
.Include("ReportTopType")
.Include("ReportWindow")
.Include("ReportSortOption.ReportSortColumns.ReportColumn.ReportColumnType")
.Include("ReportSortOption.ReportSortColumns.ReportSortType")
.Include("ReportColumnGroups.ReportColumns.ReportColumnType")
.Include("ReportColumnGroups.ReportColumnType");
}
public static DbQuery<ReportSortOption> IncludeAll(this DbQuery<ReportSortOption> self)
{
return self
.Include("ReportSortColumns.ReportColumn.ReportColumnType")
.Include("ReportSortColumns.ReportSortType");
}
public static DbQuery<ReportColumnGroup> IncludeAll(this DbQuery<ReportColumnGroup> self)
{
return self
.Include("ReportColumn.ReportColumnType")
.Include("ReportColumnType");
}
public static DbQuery<ReportColumn> IncludeAll(this DbQuery<ReportColumn> self)
{
return self
.Include("ReportColumnType");
}
public static DbQuery<ReportSortColumn> IncludeAll(this DbQuery<ReportSortColumn> self)
{
return self
.Include("ReportColumn.ReportColumnType")
.Include("ReportSortType");
}
I have a set of static, cached data that I obtain as follows:
using (var context = new ReportDataEF())
{
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
reportConfigurationData = new ReportingMetaData()
{
WatchTypes = context.WatchTypes.ToList(),
ReportTemplates = context.ReportTemplates.IncludeAll().ToList(),
ReportTopTypes = context.ReportTopTypes.ToList(),
ReportWindows = context.ReportWindows.ToList(),
ReportSortOptions =
context.ReportSortOptions.IncludeAll().ToList()
};
}
and I retrieve UserReports as follows:
public UserReport GetUserReport(int userReportId)
{
using (var context = new ReportDataEF())
{
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
var visibleReports =
context.UserReports.IncludeAll().Where(ur => ur.Id == userReportId).FirstOrDefault();
return visibleReports;
}
}
The test I am concerned with gets an existing UserReport from the DB, Updates its ReportTemplate and ReportColumnGroups properties with objects from the static data class and then attempts to save the updated UserReport.
Using the code from Ladislav's answer, this fails when I try to attach the UserReport, presumably because one of the objects I've attached to it, already exists in the database.
Yes there is another way. First think you should know is that EF doesn't support partially attached object graphs so both Attach and Add have side effects to attach or add all entities in the graph which are not yet tracked by the context. This will simplify your insertion code a lot.
public UserReport SaveUpdateUserReport(UserReport userReport)
{
using (var context = new ReportDataEF())
{
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
// Now all entities in the graph are attached in unchanged state
context.ReportTopTypes.Attach(userReport);
if (userReport.Id > 0 &&
context.UserReports.Any(ur => ur.Id == userReport.Id))
{
context.Entry(userReport).State = EntityState.Modified;
}
else
{
context.Entry(userReport).State = EntityState.Added;
}
context.SaveChanges();
}
return userReport;
}
This is equivalent to your original code. You don't load user report again - you just check its existence in DB. This code has a lot of problems - for example if you changed any other related object it will not be persisted to database because currently its state is Unchanged. It can be even more complicated if you need to change relations.
I have a model class which contains 5 properties but only 3 properties are shown in the view.
When I call create action, I need to update just this 3!!
I'm trying this:
[HttpPost]
public ActionResult Create([Bind(Exclude = "Id")]Location location)
{
if (ModelState.IsValid)
{
db.Location.Add(location);
bool car_in_database = db.Car.Any(c => c.Id == location.Car.Id);
if (car_in_database)
{
db.Entry(location.Car).State = EntityState.Modified;
db.Entry(location.Car).Property(l => l.ClientId).IsModified = false;
}
db.SaveChanges();
return RedirectToAction("Index");
}
but it doesn't work.. anyone know why or what I have to do?
Only .NET 4.5 and EF 5.0 support unmarking property as not modified so if you are using .NET 4.0 you must not set entity state to modified. Instead you must manually set each property you want to modify with IsModified = true
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.