0

I'm using EntityFramework 5 running on .NET 4.5

I'm trying to perform a record update, using approach as below:

var rec = DbCntx.MyRecords.Attach(changedRecord);
var entry = DbCntx.Entry(changedRecord);
if (entry.State == EntityState.Modified) DbCntx.SaveChanges();

The passed in data object changedRecord contained with at least 1 field value modified, compare to the actual db. However the Entry.State is always Unchanged, so SaveChanges() never triggered.

I'm writing this update routine to support any data changes over the changedRecord, the data changes is totally depend on the client/caller, so I shouldn't specifically change any field value in this routine.

My problem using this approach, I can't get the original data values. All the entity I can accessed are the exact of changedRecord, which is as a disconnected record with changes I haven't commit into the db.

Without the original data, I can't identify which properties are changes. If I try to use Find(), First/SingleOrDefault() to locate the record 1st, I can't use Attach() later as it would cause error "ObjectStateManager cannot track multiple objects with the same key".

from Googling, some hinted to use Detach(), but Detach is not available in DbContext.

Kelmen
  • 1,053
  • 1
  • 11
  • 24
  • I found the workaround to get the Detach work: http://stackoverflow.com/questions/4168073/entity-framework-code-first-no-detach-method-on-dbcontext – Kelmen Apr 11 '14 at 08:37

2 Answers2

0

This is typically a case where you'd want to use AddOrUpdate (or this for version 6):

using System.Data.Entity.Migrations;
....

DbCntx.MyRecords.AddOrUpdate(changedRecord);
DbCntx.SaveChanges();

This marks all changed properties as modified. By default, the entity is fetched from the database by its key value(s). You can tell AddOrUpdate to use an alternate key, if necessary.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • I looked into this method, but it didn't fit my need. The only good about it is I won't get EF error, I can achieve this simply changing the EntityState to Modified using initial approach. – Kelmen Apr 11 '14 at 08:36
  • Your problem was "I can't get the original data values". That's what `AddOrUpdate` does for you. Of course you can mark a whole entity as modified but this will generate an UPDATE statement updating *all* values (including unchanged values). If that's OK for you can leave it that way. – Gert Arnold Apr 11 '14 at 09:35
  • I performed an actual test with AddOrUpdate(), it does only update the fields with different value. I don't even need to perform code to compare, in a way, it does fulfill the question. Thank you. But in reality, I need to capture the differences in codes for audit logging. Anyway, it's the right answer for the question. Thank you. – Kelmen Apr 11 '14 at 10:51
0

AddOrUpdate() proposed by Gert does worked. However, I need to capture the differences in codes. At the end, this is what I worked out, using DbPropertyValues:

            dbCntx.MyRec.Attach(chgRec);

            var entry = dbCntx.Entry(chgRec);

            var dbVals = entry.GetDatabaseValues();
            var dbValsPs = dbVals.PropertyNames;

            foreach (var p in dbValsPs)
            {
                var ep = entry.Property(p);
                var cv = ep.CurrentValue;
                var dbv = dbVals.GetValue<object>(p);

                if (!MyHelper.AreValuesEqual(cv, dbv))
                    ep.IsModified = true;
            }

            if (entry.State == EntityState.Modified)
                affectedCount = dbCntx.SaveChanges();

    public static bool AreValuesEqual<T>(T valueA, T valueB)
    {
        // array handling (such as byte[])
        if (valueA != null)
        {
            if (valueB == null) return false;

            Type t = valueA.GetType();
            if (t.IsArray)
            {
                IEnumerable<object> listA = (valueA as Array).Cast<object>();
                IEnumerable<object> listB = (valueB as Array).Cast<object>();
                return listA.SequenceEqual(listB);
            }
        }

        return EqualityComparer<T>.Default.Equals(valueA, valueB);
Kelmen
  • 1,053
  • 1
  • 11
  • 24