Znajdź różnice między dwoma encjami tego samego typu

Pracuję nad aplikacją internetową mvc3. Gdy użytkownik coś aktualizuje, chcę porównać stare dane z nowymi, które użytkownik wprowadza i dla każdego innego pola dodać je do dziennika, aby utworzyć dziennik aktywności.

W tej chwili tak wygląda moja akcja save:

[HttpPost]
public RedirectToRouteResult SaveSingleEdit(CompLang newcomplang)
{
    var oldCompLang = _db.CompLangs.First(x => x.Id == newcomplang.Id);

    _db.CompLangs.Attach(oldCompLang);
    newcomplang.LastUpdate = DateTime.Today;
    _db.CompLangs.ApplyCurrentValues(newcomplang);
    _db.SaveChanges();

    var comp = _db.CompLangs.First(x => x.Id == newcomplang.Id);

    return RedirectToAction("ViewSingleEdit", comp);
}

Odkryłem, że mogę użyć tego do iteracji przez moją własność oldCompLang:

var oldpropertyInfos = oldCompLang.GetType().GetProperties();

Ale to nie pomaga, ponieważ pokazuje mi tylko właściwości (Id, Nazwa, Status...) i nie wartości tych właściwości (1, Hello, Ready...).

I could just go the hard way:

if (oldCompLang.Status != newcomplang.Status)
{
    // Add to my activity log table something for this scenario
}

Ale naprawdę nie chcę tego robić dla wszystkich właściwości obiektu.

Nie jestem pewien, jaki jest najlepszy sposób iteracji obu obiektów, aby znaleźć niedopasowania (na przykład użytkownik zmienił nazwę lub status...) i zbudować listę z tych różnic, które mogę przechowywać w innej tabeli.

Author: LanFeusT, 2011-04-28

5 answers

Nie jest tak źle, możesz porównać właściwości "ręcznie" za pomocą refleksji i napisać metodę rozszerzenia do ponownego użycia - możesz to wziąć jako punkt wyjścia:

public static class MyExtensions
{
    public static IEnumerable<string> EnumeratePropertyDifferences<T>(this T obj1, T obj2)
    {
        PropertyInfo[] properties = typeof(T).GetProperties();
        List<string> changes = new List<string>();

        foreach (PropertyInfo pi in properties)
        {
            object value1 = typeof(T).GetProperty(pi.Name).GetValue(obj1, null);
            object value2 = typeof(T).GetProperty(pi.Name).GetValue(obj2, null);

            if (value1 != value2 && (value1 == null || !value1.Equals(value2)))
            {
                changes.Add(string.Format("Property {0} changed from {1} to {2}", pi.Name, value1, value2));
            }
        }
        return changes;
    }
}
 23
Author: BrokenGlass,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-27 23:57:33

Jeśli używasz EntityFramework, możesz uzyskać zmiany bezpośrednio z ObjectContext

Otrzymywanie wpisów zmiany stanu:

  var modifiedEntries= this.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);

Uzyskanie zapisanych nazw właściwości oraz oryginalnych i zmienionych wartości:

  for (int i = 0; i < stateChangeEntry.CurrentValues.FieldCount - 1; i++)
            {
                var fieldName = stateChangeEntry.OriginalValues.GetName(i);

                if (fieldName != changedPropertyName)
                    continue;

                var originalValue = stateChangeEntry.OriginalValues.GetValue(i).ToString();
                var changedValue = stateChangeEntry.CurrentValues.GetValue(i).ToString();
            }

Jest to lepsze niż odpowiedź @BrokenGlass, ponieważ zajdzie to głęboko w grafie obiektu dla wszystkich zmienionych stanów i da ci zmienione właściwości powiązanych zbiorów. Jest to również lepsze, ponieważ odzwierciedla to wszystko, co Obiektkontekst będzie ostatecznie zapisać do bazy danych. Dzięki zaakceptowanemu rozwiązaniu możesz uzyskać zmiany właściwości, które nie zostaną utrzymane w sytuacji, gdy zostaniesz odłączony przez kontekst obiektu.

W EF 4.0 można również nadpisać metodę SaveChanges() i zawinąć wszelkie audyty lub działania w tę samą transakcję, co ewentualna Jednostka save, co oznacza, że ścieżka audytu nie będzie istnieć bez zmian jednostek i na odwrót. Gwarantuje to dokładny dziennik. Jeśli nie możesz zagwarantować audyt jest dokładny niż prawie bezużyteczny.

 9
Author: jfar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-28 01:38:12

Rozszerzenie odpowiedzi jfara, aby zapewnić pewne wyjaśnienia i zmiany, które musiałem wprowadzić, aby go uruchomić:

// Had to add this:
db.ChangeTracker.DetectChanges();
// Had to add using System.Data.Entity.Infrastructure; for this:
var modifiedEntries = ((IObjectContextAdapter)db).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);

foreach (var stateChangeEntry in modifiedEntries)
{
    for (int i = 0; i < stateChangeEntry.CurrentValues.FieldCount; i++)
    {
        var fieldName = stateChangeEntry.OriginalValues.GetName(i);
        var changedPropertyName = stateChangeEntry.CurrentValues.GetName(i);

        if (fieldName != changedPropertyName)
            continue;

        var originalValue = stateChangeEntry.OriginalValues.GetValue(i).ToString();
        var changedValue = stateChangeEntry.CurrentValues.GetValue(i).ToString();
        if (originalValue != changedValue)
        {
            // do stuff
            var foo = originalValue;
            var bar = changedValue;
        }

    }
}
 1
Author: Hugh Seagraves,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-27 22:30:53

Użyj tego niestandardowego kodu, aby porównać 2 obiekty. Jeśli się nie uda, Zaloguj się. Design point of view Utwórz to w klasie użytkowej.

Użyj go jako object1.IsEqualTo(object2)

 0
Author: Praneeth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:16:42

Zaimplementuj IEquatable<T> na swojej jednostce. Innym sposobem jest implementacja custom IComparer<T>, gdzie można na przykład wystawić zdarzenie, które zostanie wywołane, jeśli właściwość jest inna.

 0
Author: Ladislav Mrnka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-27 22:56:56