EF ObjectStateEntry OriginalValues contains CurrentValues - c#

I am trying to override Entity Framework's SaveChanges() method to save auditing information. I begin with the following:
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext;
List<ObjectStateEntry> objectStateEntryList = ctx.ObjectStateManager.GetObjectStateEntries(
EntityState.Added
| EntityState.Modified
| EntityState.Deleted).ToList();
foreach (ObjectStateEntry entry in objectStateEntryList)
{
if (!entry.IsRelationship)
{
//Code that checks and records which entity (table) is being worked with
...
foreach (string propertyName in entry.GetModifiedProperties())
{
DbDataRecord original = entry.OriginalValues;
string oldValue = original.GetValue(original.GetOrdinal(propertyName)).ToString();
CurrentValueRecord current = entry.CurrentValues;
string newValue = current.GetValue(current.GetOrdinal(propertyName)).ToString();
if (oldValue != newValue)
{
AuditEntityField field = new AuditEntityField
{
FieldName = propertyName,
OldValue = oldValue,
NewValue = newValue,
Timestamp = auditEntity.Timestamp
};
auditEntity.AuditEntityField.Add(field);
}
}
}
}
}
Problem I'm having is that the values I get in entry.OriginalValues and in entry.CurrentValues is always the new updated value:

The problem has been found:
public ActionResult Edit(Branch branch)
{
if (ModelState.IsValid)
{
db.Entry(branch).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(branch);
}
Saving an update in the above way seems to cause the ChangeTracker to not pick up the old values. I fixed this by simply making use of ViewModels for all the required updates:
public ActionResult Edit(BranchViewModel model)
{
if (ModelState.IsValid)
{
Branch branch = db.Branch.Find(model.BranchId);
if (branch.BranchName != model.BranchName)
{
branch.BranchName = model.BranchName;
db.SaveChanges();
}
return RedirectToAction("Index");
}
return View(model);
}

To get a copy of the old values from the database without updating the EF entity you can use
context.Entry(yourEntity).GetDatabaseValues().ToObject()
or
yourEntity.GetDatabaseValues().ToObject()
Caution: this does instigate a database call.

The easiest way to do for me was:
var cadastralAreaDb = await _context.CadastralAreas.FindAsync(id).ConfigureAwait(false);
var audit = new Audit
{
CreatedBy = "Edi"
};
_context.Entry(cadastralAreaDb).CurrentValues.SetValues(cadastralArea);
await _context.SaveChangesAsync(audit).ConfigureAwait(false);
This is tested in ASP NET Core 3.1

Related

EntityFramework Core: Get changes occured in entity and related data

I want to get changes occured in an entity and related datas attached to it.
I know how to get the property names that have changed in one entity:
dbContext.Entry(entity).Properties.Where(x => x.IsModified).Select(x => x.Metadata.Name).ToList();
How to do the same for related data in navigation properties ?
Based on this article (Entity Change Tracking using DbContext in Entity Framework 6), you should override SaveChanges() method to track entity changes and its related entities.
public override int SaveChanges()
{
return base.SaveChanges();
}
Actually, You should change the above code to the following sample:
public override int SaveChanges()
{
var modifiedEntities = ChangeTracker.Entries()
.Where(p => p.State == EntityState.Modified).ToList();
var now = DateTime.UtcNow;
foreach (var change in modifiedEntities)
{
var entityName = change.Entity.GetType().Name;
var primaryKey = GetPrimaryKeyValue(change);
foreach(var prop in change.OriginalValues.PropertyNames)
{
var originalValue = change.OriginalValues[prop].ToString();
var currentValue = change.CurrentValues[prop].ToString();
if (originalValue != currentValue) //Only create a log if the value changes
{
//Create the Change Log
}
}
}
return base.SaveChanges();
}

UnModified fields are not getting WebAPI

I am working in webapi. I have one update method to perform modification. There are number of properties but i only wants to update few fields.
So i just skipped the unwanted fields using entry.Property(propertyName).IsModified = false; in data layer. All my logic working fine but after update, when i got the new update entry, it didn't have the fields which are not updated
My Controller Code:
[Route("{id:int}")]
public async Task<IHttpActionResult> Put(int id, MyModel model)
{
model.Id = id;
bool result = await _modelLogic.UpdateData(item);
if (!result)
{
return BadRequest("Could not Save to the database");
}
return await GetModel(item.Id);
}
[Route("{id:int}", Name = "GetModelById")]
public async Task<IHttpActionResult> GetModel(int id)
{
MyModel model = await _modelLogic.GetModelAsync(id);
if (Model == null)
{
return NotFound();
}
return Ok(model);
}
My Business Logic:
public async Task<bool> UpdateData(MyModel model)
{
model.RecordStatus = DataStatus.Active;
string[] excludedProperties = new[] {"RegistrationId", "StartDate", "ProtocolType", "Code" };
_repo.Update(model, excludedProperties);
bool status = await _repo.SaveAsync();
return status;
}
Here RegistrationId is a foreign key.
My Data Code:
public void Update(MyModel model, string[] excludedProperties)
{
excludedPropertiesInUpdate = excludedPropertiesInUpdate.Union(excludedProperties).ToArray();
base.Update(model);
}
My Generic Repository / base repository
internal string[] excludedPropertiesInUpdate = new[] { "CreatedDate", "CreatedBy" };
public void Update(T entity)
{
entity.UpdatedDate = DateTime.UtcNow;
var entry = _context.Entry(entity);
entry.State = EntityState.Modified;
//Restrict Modification for Specified Properties
foreach(string propertyName in excludedPropertiesInUpdate)
{
entry.Property(propertyName).IsModified = false;
}
}
Like i told, all logic working fine. But when it shows the response, the fields which are not updated shown as null. eg: RegistrationId, Code etc. But in database its there not updated anything
You only store new values in database, but do not load unmodified properties to entity.
internal string[] excludedPropertiesInUpdate = new[] { "CreatedDate", "CreatedBy" };
public void Update(T entity)
{
entity.UpdatedDate = DateTime.UtcNow;
var entry = _context.Entry(entity);
entry.State = EntityState.Modified;
//Restrict Modification for Specified Properties
foreach(string propertyName in excludedPropertiesInUpdate)
{
entry.Property(propertyName).IsModified = false;
}
_context.SaveChanges();
//load actual values from the database.
entry.Reload();
}

Search all entities with specific property and their entries

I'm using EF Code First but my models have NO relationships (PKs - FKs). So I'm trying to find a way to workaround it by using EF6 Reflections in order to avoid an entry deletion that would have relationships (same property name).
Lookup over all my context entities in which has any specific property (FK);
For every entity found, check if this entity has any entry;
If its true, instead of deleting my entry, set a property "Canceled" as true;
If its false, keep entity state deleted and save my context changes;
public override int SaveChanges()
{
foreach (var myEntity in ChangeTracker.Entries<IAuditable>())
{
if (myEntity.State == EntityState.Deleted)
{
ObjectContext objContext = ((IObjectContextAdapter)this).ObjectContext;
var container = objContext.MetadataWorkspace.GetEntityContainer(objContext.DefaultContainerName, DataSpace.CSpace);
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(myEntity.Entity);
var entityKeys = objectStateEntry.EntityKey.EntityKeyValues;
var entity = myEntity;
var hasAnyFk = false;
foreach (var entityKey in entityKeys)
{
if (hasAnyFk)
{
break;
}
var keyName = entityKey.Key;
foreach (var entitySet in container.EntitySets)
{
hasAnyFk = entitySet.ElementType.Members.Any(es => es.Name == keyName);
if (hasAnyFk)
{
break;
}
}
}
if (hasAnyFk)
{
var deletedProperty = myEntity.OriginalValues.PropertyNames.Where(p => myEntity.Property(p).Name == "Deleted").FirstOrDefault();
if (deletedProperty != null)
{
myEntity.State = EntityState.Modified;
myEntity.CurrentValues[deletedProperty] = true;
}
}
}
}
return base.SaveChanges();
}
You can handle this is an overload of SaveChanges:
public override int SaveChanges()
{
foreach (var entry in this.ChangeTracker.Entries().Where(e => e.State ==
System.Data.Entity.EntityState.Deleted).ToList())
{
var delPropName = "IsDeleted";
if (entry.OriginalValues.PropertyNames.Contains(delPropName))
{
var delProp = entry.Property(delPropName);
delProp.CurrentValue = true;
entry.State = System.Data.Entity.EntityState.Modified;
}
}
return base.SaveChanges();
}
Here, entry.OriginalValues.PropertyNames is used to check if the property exists in the entity and then its value is set and the entry's state is changed to Modified. Note that I loop through this.ChangeTracker.Entries() after applying ToList(), otherwise the content of the collection changes while looping through it.

How can I log all entities change, during .SaveChanges() using EF code first?

I'm using EF code first. I'm using a base Repository for all my repositories and an IUnitofWork that inject to the repositories, too:
public interface IUnitOfWork : IDisposable
{
IDbSet<TEntity> Set<TEntity>() where TEntity : class;
int SaveChanges();
}
public class BaseRepository<T> where T : class
{
protected readonly DbContext _dbContext;
protected readonly IDbSet<T> _dbSet;
public BaseRepository(IUnitOfWork uow)
{
_dbContext = (DbContext)uow;
_dbSet = uow.Set<T>();
}
//other methods
}
e.g my OrderRepository is like this:
class OrderRepository: BaseRepository<Order>
{
IUnitOfWork _uow;
IDbSet<Order> _order;
public OrderRepository(IUnitOfWork uow)
: base(uow)
{
_uow = uow;
_order = _uow.Set<Order>();
}
//other methods
}
And I use it in this way:
public void Save(Order order)
{
using (IUnitOfWork uow = new MyDBContext())
{
OrderRepository repository = new OrderRepository(uow);
try
{
repository.ApplyChanges<Order>(order);
uow.SaveChanges();
}
}
}
Is there any way to log change histories of all entities(include their navigation properties) during .SaveChanges()? I want to log original values(before save occurs) and changed values(after save occurs).
You can get the before and after values for all changed entities by going through DbContext.ChangeTracker. Unfortunately the API is a little verbose:
var changeInfo = context.ChangeTracker.Entries()
.Where (t => t.State == EntityState.Modified)
.Select (t => new {
Original = t.OriginalValues.PropertyNames.ToDictionary (pn => pn, pn => t.OriginalValues[pn]),
Current = t.CurrentValues.PropertyNames.ToDictionary (pn => pn, pn => t.CurrentValues[pn]),
});
You can modify that to include things like the type of the entity if you need that for your logging. There is also a ToObject() method on the DbPropertyValues (the type of OriginalValues and CurrentValues) you could call if you already have a way to log whole objects, although the objects returned from that method will not have their navigation properties populated.
You can also modify that code to get all entities in the context by taking out the Where clause, if that makes more sense given your requirements.
I have overridded the default SaveChanges method to log changes for add/update/delete in entity. Though it does not cover navigation property changes.
Based on this article: Using entity framework for auditing
public int SaveChanges(string userId)
{
int objectsCount;
List<DbEntityEntry> newEntities = new List<DbEntityEntry>();
// Get all Added/Deleted/Modified entities (not Unmodified or Detached)
foreach (var entry in this.ChangeTracker.Entries().Where
(x => (x.State == System.Data.EntityState.Added) ||
(x.State == System.Data.EntityState.Deleted) ||
(x.State == System.Data.EntityState.Modified)))
{
if (entry.State == System.Data.EntityState.Added)
{
newEntities.Add(entry);
}
else
{
// For each changed record, get the audit record entries and add them
foreach (AuditLog changeDescription in GetAuditRecordsForEntity(entry, userId))
{
this.AuditLogs.Add(changeDescription);
}
}
}
// Default save changes call to actually save changes to the database
objectsCount = base.SaveChanges();
// We don't have recordId for insert statements that's why we need to call this method again.
foreach (var entry in newEntities)
{
// For each changed record, get the audit record entries and add them
foreach (AuditLog changeDescription in GetAuditRecordsForEntity(entry, userId, true))
{
this.AuditLogs.Add(changeDescription);
}
// TODO: Think about performance here. We are calling db twice for one insertion.
objectsCount += base.SaveChanges();
}
return objectsCount;
}
#endregion
#region Helper Methods
/// <summary>
/// Helper method to create record description for Audit table based on operation done on dbEntity
/// - Insert, Delete, Update
/// </summary>
/// <param name="dbEntity"></param>
/// <param name="userId"></param>
/// <returns></returns>
private List<AuditLog> GetAuditRecordsForEntity(DbEntityEntry dbEntity, string userId, bool insertSpecial = false)
{
List<AuditLog> changesCollection = new List<AuditLog>();
DateTime changeTime = DateTime.Now;
// Get Entity Type Name.
string tableName1 = dbEntity.GetTableName();
// http://stackoverflow.com/questions/2281972/how-to-get-a-list-of-properties-with-a-given-attribute
// Get primary key value (If we have more than one key column, this will need to be adjusted)
string primaryKeyName = dbEntity.GetAuditRecordKeyName();
int primaryKeyId = 0;
object primaryKeyValue;
if (dbEntity.State == System.Data.EntityState.Added || insertSpecial)
{
primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName, true);
if(primaryKeyValue != null)
{
Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
}
// For Inserts, just add the whole record
// If the dbEntity implements IDescribableEntity,
// use the description from Describe(), otherwise use ToString()
changesCollection.Add(new AuditLog()
{
UserId = userId,
EventDate = changeTime,
EventType = ModelConstants.UPDATE_TYPE_ADD,
TableName = tableName1,
RecordId = primaryKeyId, // Again, adjust this if you have a multi-column key
ColumnName = "ALL", // To show all column names have been changed
NewValue = (dbEntity.CurrentValues.ToObject() is IAuditableEntity) ?
(dbEntity.CurrentValues.ToObject() as IAuditableEntity).Describe() :
dbEntity.CurrentValues.ToObject().ToString()
}
);
}
else if (dbEntity.State == System.Data.EntityState.Deleted)
{
primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName);
if (primaryKeyValue != null)
{
Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
}
// With deletes use whole record and get description from Describe() or ToString()
changesCollection.Add(new AuditLog()
{
UserId = userId,
EventDate = changeTime,
EventType = ModelConstants.UPDATE_TYPE_DELETE,
TableName = tableName1,
RecordId = primaryKeyId,
ColumnName = "ALL",
OriginalValue = (dbEntity.OriginalValues.ToObject() is IAuditableEntity) ?
(dbEntity.OriginalValues.ToObject() as IAuditableEntity).Describe() :
dbEntity.OriginalValues.ToObject().ToString()
});
}
else if (dbEntity.State == System.Data.EntityState.Modified)
{
primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName);
if (primaryKeyValue != null)
{
Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
}
foreach (string propertyName in dbEntity.OriginalValues.PropertyNames)
{
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntity.OriginalValues.GetValue<object>(propertyName),
dbEntity.CurrentValues.GetValue<object>(propertyName)))
{
changesCollection.Add(new AuditLog()
{
UserId = userId,
EventDate = changeTime,
EventType = ModelConstants.UPDATE_TYPE_MODIFY,
TableName = tableName1,
RecordId = primaryKeyId,
ColumnName = propertyName,
OriginalValue = dbEntity.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntity.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = dbEntity.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntity.CurrentValues.GetValue<object>(propertyName).ToString()
}
);
}
}
}
// Otherwise, don't do anything, we don't care about Unchanged or Detached entities
return changesCollection;
}
you have scared people away with the extra requirement
Include their navigation properties
This is simply a non trivial exercise.
And if this is important, you should manage/track changes across references with code.
this is a sample covering this topic
Undo changes in entity framework entities
There is a sample doing close top what you want here
undo changes
It can easily be converted to load before and after images elsewhere.
Given the ObjectState entry after DetectChanges is called, you can implement a simple entity by entity option. and per UOW. But the navigation / references version makes this very complex as you worded the requirement.
EDIT : How to access the changeList
public class Repository<TPoco>{
/....
public DbEntityEntry<T> Entry(T entity) { return Context.Entry(entity); }
public virtual IList<ChangePair> GetChanges(object poco) {
var changes = new List<ObjectPair>();
var thePoco = (TPoco) poco;
foreach (var propName in Entry(thePoco).CurrentValues.PropertyNames) {
var curr = Entry(thePoco).CurrentValues[propName];
var orig = Entry(thePoco).OriginalValues[propName];
if (curr != null && orig != null) {
if (curr.Equals(orig)) {
continue;
}
}
if (curr == null && orig == null) {
continue;
}
var aChangePair = new ChangePair {Key = propName, Current = curr, Original = orig};
changes.Add(aChangePair);
}
return changes;
}
///... partial repository shown
}
// FYI the simple return structure
public class ChangePair {
public string Key { get; set; }
public object Original { get; set; }
public object Current { get; set; }
}
DbContext has ChangeTracker property.
You can override .SaveChanges() in your context and log changes.
I don't think that entity framework can do it for you. Probably, you must detect changes directly in your model classes.
I've expanded on Steve's answer to provide a check for Changed, Added, and Deleted entities and print them in a sensible way.
(My use case is to ensure there are no unsaved changes before disposing of a DbContext instance, but this check could be done at any point)
/// <summary>Helper method that checks whether the DbContext had any unsaved changes before it was disposed.</summary>
private void CheckForUnsavedChanges(DbContext dbContext)
{
try
{
List<DbEntityEntry> changedEntityEntries = dbContext.ChangeTracker.Entries()
.Where(t => t.State != EntityState.Unchanged && t.State != EntityState.Detached).ToList();
if (!changedEntityEntries.Any())
return;
throw new Exception("Detected that there were unsaved changes made using a DbContext. This could be due to a missing call to `.SaveChanges()` or possibly " +
"some read-only operations that modified the returned entities (in which case you might wish to use `.AsNoTracking()` in your query). Changes:\n " +
String.Join("\n ", changedEntityEntries.Select(entry => $"{entry.Entity.GetType()} {entry.State}:\n " + String.Join("\n ",
entry.State == EntityState.Modified ? entry.CurrentValues.PropertyNames
// Only output properties whose values have changed (and hope they have a good ToString() implementation)
.Where(pn => entry.OriginalValues?[pn] != entry.CurrentValues[pn])
.Select(pn => $"{pn} ({entry.OriginalValues?[pn]} -> {entry.CurrentValues[pn]})") :
// Added or Deleted entities are output in their entirety
entry.State == EntityState.Added ? entry.CurrentValues.PropertyNames.Select(pn => $"{pn} = {entry.CurrentValues[pn]}") :
/* entry.State == EntityState.Deleted ? */ entry.CurrentValues.PropertyNames.Select(pn => $"{pn} = {entry.OriginalValues[pn]}")))));
}
catch (Exception ex)
{
_logger.Error("Issue encountered when checking for unsaved changes.", ex);
}
}

Access object context in override SaveChanges

So I want to keep track of the last change done to any of my objects, this last change is saved on the "head" object. I'll sketch it.
I have an object Project containing a list of Activities, which in turn has a list of Actions both with different properties and so on.
The Project has a property LastUpdateDate on which I want to keep the last update done on any property of the Project itself or any of its 'subobjects'.
For this I override the SaveChanges method of the entity framework as follows:
public override int SaveChanges() {
var changedEntries = ChangeTracker.Entries();
if (changedEntries != null)
{
var dbEntityEntries = changedEntries as IList<DbEntityEntry> ?? changedEntries.ToList();
foreach (var entry in dbEntityEntries.Where(c => c.State != EntityState.Unchanged))
{
var proj = entry.Entity as Project;
if (proj != null)
{
proj.LastUpdateDate = DateTime.UtcNow;
}
var prop = entry.Entity as Activity;
if (prop != null)
{
var changedProject = dbEntityEntries.Single(x => (x.Entity is Project) && ((Project) x.Entity).Id == prop.ProjectId);
((Project) changedProject.Entity).LastUpdateDate = DateTime.UtcNow;
}
}
}
return base.SaveChanges();
}
My dataset is larger and has more properties than in the example above, therefor I dont want to be looping through every entity because this will take up too much time.
In stead of this, I would like to access my ObjectContext so I can say for instance: project[0].Activity[1]...
Has anyone here got any idea on how to reach this situation?
I can answer the first part of this question: Here's how you access the ObjectContext:
public override int SaveChanges()
{
this.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
}
Try this:
public override int SaveChanges()
{
var changedEntries = ChangeTracker.Entries();
if (changedEntries != null)
{
var dbEntityEntries = changedEntries as IList<DbEntityEntry> ?? changedEntries.ToList();
dbEntityEntries.Select(i=>i.Entity).OfType<Project>().Distinct()
.Union(dbEntityEntries.Select(i=>i.Entity).OfType<Activity>().Select(activity=>activity.Project))
.Union(dbEntityEntries.Select(i=>i.Entity).OfType<Action>().Select(action => action.Activity.Project))
.Distinct().ToList()
.ForEach(p => p.LastUpdateDate = DateTime.UtcNow);
}
return base.SaveChanges();
}

Categories

Resources