Cezary Rynkowski
Cezary Rynkowski

Reputation: 317

ChangeTracker.Entries() CurrentValue equals OriginalValue in EF7

I have a issue in EF7/asp.Net Core application. In my context I create a method Save:

    public int Save()
    {
        ChangeTracker.DetectChanges();
        var modifiedEntities = ChangeTracker.Entries()
            .Where(p => p.State == EntityState.Modified || p.State == EntityState.Added || p.State == EntityState.Deleted || p.State == EntityState.Modified || p.State == EntityState.Detached).ToList();
        var now = DateTime.UtcNow;

        foreach (var change in modifiedEntities)
        {
            var entityName = change.Entity.GetType().Name;
            var primaryKeyValue = GetPrimaryKeyValue(change.Entity);
            foreach (var prop in change.Entity.GetType().GetTypeInfo().DeclaredProperties)
            {
                if (!prop.GetGetMethod().IsVirtual)
                {
                    var currentValue = change.Property(prop.Name).CurrentValue;
                    var originalValue = change.Property(prop.Name).OriginalValue;
                    if (originalValue.ToString() != currentValue.ToString())
                    {
                        var changeLoged = new ChangeLog
                        {
                            PropertyName = prop.Name,
                            EntityName = entityName,
                            PrimaryKeyValue = primaryKeyValue,
                            DateChange = now,
                            OldValue = originalValue.ToString(),
                            NewValue = currentValue.ToString(),
                            ChangedBy = "test"
                        };
                        ChangeLog.Add(changeLoged);
                    }
                }
            }
        }
        return base.SaveChanges();
    }

and method GetPrimaryKeyValue:

protected virtual int GetPrimaryKeyValue<T>(T entity)
    {
        var test = entity;
        var test2 = test.GetType();
        var keyName = this.Model.FindEntityType(test2).FindPrimaryKey().Properties
            .Select(x => x.Name).Single();
        var result = (int)entity.GetType().GetProperty(keyName).GetValue(entity, null);
        if (result < 0)
            return -1;

        return result;
    }

Unfortunatlly the change.Property(prop.Name).CurrentValue always equals OriginalValue, so the if originalValue.ToString() != currentValue.ToString()

always return false.

Upvotes: 7

Views: 9354

Answers (3)

Mark
Mark

Reputation: 439

Based on the answers above I refactored my SetChanges method to this, and its working fine now! (EF 6 and .NET 6)

private void SetChanges()
    {
        TableName = Entry.Metadata.GetTableName();

        var entsInDB = Entry.GetDatabaseValues();

        foreach (PropertyEntry property in Entry.Properties)
        {
            if (property != null)
            {
                string propertyName = property.Metadata.Name;

                if (property.Metadata.IsPrimaryKey())
                {
                    KeyValues[propertyName] = property.CurrentValue ?? "";
                    continue;
                }

                switch (Entry.State)
                {
                    case EntityState.Added:
                        NewValues[propertyName] = property.CurrentValue ?? "";
                        AuditType = AuditType.Create;
                        break;

                    case EntityState.Deleted:
                        OldValues[propertyName] = property.OriginalValue ?? "";
                        AuditType = AuditType.Delete;
                        break;

                    case EntityState.Modified:
                        if (property.IsModified)
                        {
                            var originalValue = entsInDB?.GetValue<object>(property.Metadata.Name);
                            if (originalValue?.ToString() != property.CurrentValue?.ToString())
                            {
                                ChangedColumns.Add(propertyName);
                                OldValues[propertyName] = originalValue?.ToString() ?? "";
                                NewValues[propertyName] = property.CurrentValue ?? "";
                                AuditType = AuditType.Update;
                            }
                        }
                        break;
                }
            }
        }
    }

Logging it to the database audit table: Audit logs

Upvotes: 3

Paweł Pastucha
Paweł Pastucha

Reputation: 161

Replace:

var originalValue = change.Property(prop.Name).OriginalValue;

to:

var originalValue = change.GetDatabaseValues().GetValue<object>(prop.Name);

Upvotes: 15

Jonathan Magnan
Jonathan Magnan

Reputation: 11347

This will not exactly answer your question since I cannot reproduce this issue but this may help you.

In EF Core, the PropertyEntry class has now an IsModified property which let you know if the value has been modified or not.

You should use it instead:

if (change.Property(prop.Name).IsModified)
{
    var changeLoged = new ChangeLog
    {
        PropertyName = prop.Name,
        EntityName = entityName,
        PrimaryKeyValue = primaryKeyValue,
        DateChange = now,
        OldValue = originalValue.ToString(),
        NewValue = currentValue.ToString(),
        ChangedBy = "test"
    };
    ChangeLog.Add(changeLoged);
}

Disclaimer: I'm the owner of the project Entity Framework Plus

The Library has an Audit Feature (Supporting EF Core) which you may use or get inspired by to create your auditing (The code is Open Source).

Documentation: EF+ Audit

Upvotes: 3

Related Questions