2011-08-16 16 views
9

Mam klasy bazowej że dziedziczą od tego ma dwa zera do wielu relacji z innymi podmiotami:ChangeTracker Entity Framework 4.1 - oryginalne wartości powiązanych obiektów

public abstract class WebObject 
{ 
    public WebObject() 
    { 
     RelatedTags = new List<Tag>(); 
     RelatedWebObjects = new List<WebObject>(); 
    } 

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    public Guid Id { get; set; } 

    public string MetaKeywords { get; set; } 
    public string MetaDescription { get; set; } 

    [InverseProperty("WebObjects")] 
    public virtual WebSite WebSite { get; set; } 

    [Required(ErrorMessage = "Every WebObject must be associated with a WebSite.")] 
    public Guid WebSiteId { get; set; } 

    public virtual ICollection<Tag> RelatedTags { get; set; } 
    public IList<Guid> RelatedTagIds { get; set; } 
    public virtual ICollection<WebObject> RelatedWebObjects { get; set; } 
    public IList<Guid> RelatedWebObjectIds { get; set; } 
} 

Mam trudności z oryginalnych wartości tych relacje (RelatedWebObjects & RelatedTags) podczas przeglądania obiektów używających ChangeTracker podczas SaveChanges. Widzę wszystkie wartości skalarne przed i po, i widzę nowe relacje, ale nie widzę starych. Próbowałem już używać metod członkowskich i kolekcji, ale te pokazują tylko bieżące wartości; nie stary. Również nie lubię ich używać, ponieważ wymaga to znajomości nazwy właściwości nawigacji, która nie jest generyczna.

Mogę znaleźć powiązane obiekty, których relacja jest zmieniana, ale oczywiście wartości w tych powiązanych obiektach się nie zmieniają, więc to też nie jest żadna pomoc.

Czy jest jakiś czysty sposób śledzić poprzednie relacje jednostki podczas SaveChanges z ChangeTracker?

Poniżej znajduje się fragment kodu, który pracuję nad:

public override int SaveChanges() 
    { 
     List<AuditObject> auditTrailList = new List<AuditObject>(); 

     foreach (DbEntityEntry entity in ChangeTracker.Entries().Where(obj => { return obj.State == EntityState.Added || obj.State == EntityState.Modified || obj.State == EntityState.Deleted; })) 
     { 
      if (!(entity.Entity is AuditObject)) 
      { 
       AuditObject auditObject = new AuditObject(); 

       auditObject.Id = Guid.NewGuid(); 

       auditObject.RevisionStamp = DateTime.Now; 

       auditObject.UserName = HttpContext.Current.User.Identity.Name; 

       auditObject.EntityType = Utilities.GetCleanClassNameIfProxyClass(entity.Entity.GetType().Name); 

       if (entity.State == EntityState.Added) 
        auditObject.Action = EntityState.Added.ToString(); 
       else if (entity.State == EntityState.Modified) 
        auditObject.Action = EntityState.Modified.ToString(); 
       else if (entity.State == EntityState.Deleted) 
        auditObject.Action = EntityState.Deleted.ToString(); 

       DbMemberEntry t1 = entity.Member("RelatedWebObjects"); 
       // cannot find original relationship collection... 

       DbCollectionEntry t2 = entity.Collection("RelatedWebObjects"); 
       // cannot find original relationship collection... 

       if (entity.State == EntityState.Added || entity.State == EntityState.Modified) 
       { 
        XDocument currentValues = new XDocument(new XElement(auditObject.EntityType)); 

        foreach (string propertyName in entity.CurrentValues.PropertyNames) 
        { 
         currentValues.Root.Add(new XElement(propertyName, entity.CurrentValues[propertyName])); 
        } 

        auditObject.NewData = Regex.Replace(currentValues.ToString(), @"\r\n+", " "); 
       } 

       if (entity.State == EntityState.Modified || entity.State == EntityState.Deleted) 
       { 
        XDocument originalValues = new XDocument(new XElement(auditObject.EntityType)); 

        foreach (string propertyName in entity.OriginalValues.PropertyNames) 
        { 
         originalValues.Root.Add(new XElement(propertyName, entity.OriginalValues[propertyName])); 
        } 

        auditObject.OldData = Regex.Replace(originalValues.ToString(), @"\r\n+", " "); 
       } 

       auditTrailList.Add(auditObject); 
      } 
     } 

     foreach (var audit in auditTrailList) 
      this.AuditObjects.Add(audit); 

     return base.SaveChanges(); 
    } 
+0

Obiekty pokrewne są również śledzone przez ObjectStateManager, a powinieneś być w stanie uzyskać ich wpisy stanu obiektu, podobnie jak otrzymujesz je dla głównego obiektu. Jeśli opublikujesz kod, z którym walczysz, być może będę mógł Ci pomóc. –

+0

Jeszcze raz dziękuję Morteza ... Wysłałem sekcję kodu, nad którą teraz pracuję - proszę wybaczyć tę niechlujstwo; w ogóle nie został refaktoryzowany - po prostu próbując go uruchomić. Mogę uzyskać właściwości skalarne obiektów bez problemu za pomocą ciągów IEnumerable: entity.OriginalValues.PropertyNames & entity.CurrentValues.PropertyNames. Mam problem z entity.Collection() i entity.Member(). Który powinien być użyty do tego, co próbuję osiągnąć? Czy istnieje sposób na zrobienie tego generycznego, aby nie musieć sztywno kodować nazw kolekcji? Może refleksja? – DMC

+0

Nie jestem w stanie wypróbować tego do jutra ... czy któreś z was wie, w jaki sposób mogę podzielić punkty pomiędzy dwie odpowiedzi? ponieważ uważam, że oboje przekazaliście bardzo cenne informacje. – DMC

Odpowiedz

4

Ponieważ zmiana EF śledzi każdy obiekt na wykresie, zawsze można przekazać dowolnej instancji na wykresie do śledzenia zmian i będzie podaje wartości śledzenia zmian. Na przykład, następujący kod dostanie oryginalne/aktualne wartości AuditObject rekreacyjnego nawigacja:

DbMemberEntry t1 = entity.Member("RelatedWebObjects"); 
// cannot find original relationship collection.... 

AuditObject currentAuditObject = (AuditObject) entity; 
var currValues = this.Entry(currentAuditObject.RelatedWebObjects).CurrentValues; 
var orgValues = this.Entry(currentAuditObject.RelatedWebObjects).OriginalValues; 

Albo można zastosować tę samą sztuczkę, gdy masz do czynienia z właściwością typu kolekcja nawigacja:

DbCollectionEntry t2 = entity.Collection("RelatedWebObjects"); 
// cannot find original relationship collection.... 

foreach (WebObject item in currentAuditObject.RelatedWebObjects) 
{ 
    var currValues = this.Entry(item).CurrentValues; 
} 
+0

dzięki jeszcze raz Morteza! Doceniam to, że tak szybko do mnie wracasz. również dzięki za blog! – DMC

12

To jest trochę trudne. Przede wszystkim trzeba się różnić two types of relationships oferowanych przez EF:

  • Niezależne Zrzeszenie (wszystkich relacji wiele-do-wielu i niektórzy jeden-do-wielu) (wszystkie stosunki
  • Zagranicznych stowarzyszenie klucza jeden do jednego i niektóre jeden-do-wielu)

Teraz, jeśli chcesz poznać poprzednią wartość obcego powiązania kluczy, po prostu musisz śledzić zmiany w jednostce zależnej, w której ujawniasz właściwość klucza obcego - to jest dokładnie to samo, co śledzenie wszelkich innych zmian właściwości.

Jeśli chcesz śledzić zmiany w niezależnym powiązaniu, sytuacja stanie się trudniejsza, ponieważ DbContext API doesn't provide operations to track them. Musisz powrócić do interfejsu API ObjectContext i jego ObjectStateManager.

ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext; 
foreach (ObjectStateEntry entry = objectContext.ObjectStateManager 
               .GetObjectStateEntries(~EntityState.Detached) 
               .Where(e => e.IsRelationship)) 
{ 
    // Track changes here 
} 

Teraz masz dostęp do instancji ObjectStateEntry dla relacji. Te wystąpienia nigdy nie powinny mieć stanu Modified. Będą one albo Added, Deleted lub Unchanged, ponieważ "modyfikacja" jest przetwarzana jako usunięcie starej relacji i dodanie nowej. ObjectStateEntry zawiera również kolekcje CurrentValues i OriginalValues. Kolekcje te powinny również zawierać dwie pozycje reprezentujące po jednej stronie relacji EntityKey.

+0

dziękuję bardzo za pomoc! widzę, że to działa. również dowiedziałem się o operatorze bitowym ~ nigdy wcześniej go nie stosowałem. czy wiesz, czy możliwe jest przypisanie punktów zarówno tobie, jak i Mortezie? jego odpowiedź pojawiła się pierwsza i działa również, i wyciągnąłem do niego specjalnie przez jego bloga, ale chcę, żebyś dostał kredyt ... – DMC

+0

@DMC: Możesz zaznaczyć tylko jedną odpowiedź jako zaakceptowaną, ale w zależności od twojej reputacji możesz również rzuca upvotes na tyle odpowiedzi, ile chcesz. –

+0

Przepraszam - dam to Mortezzie, ponieważ dostał ją pierwszy i zszedłem na mój sposób, aby skontaktować się z nim bezpośrednio w tej sprawie. ale zrobiłem dla ciebie upominkę. – DMC

Powiązane problemy