How to get the original values from a navigation collection property in Entity Framework Core?
Asked Answered
C

2

7

With Entity Framework Core, I need to check the original values of a navigation property inside my entity but i can't find a way to do it.

I'm able to read the original and the current values of the actual entity and also his reference property but the OriginalValue property is missing when I read the navigation property.

Here's what I been able to do so far.

var entries = ChangeTracker.Entries<Book>()
                .Where(x => x.State == EntityState.Modified)
                .ToList();

foreach (var entry in entries)
{   
   // read the entity current & original values 
   var currentTitleValue = entry.Property(x => x.Title).CurrentValue;
   var originalTitleValue = entry.Property(x => x.Title).OriginalValue;

   // read the reference object current & original values
   var promotionReferenceEntry = entry.Reference(x => x.Promotion);
   var currentPromotionPriceValue = promotionReferenceEntry.TargetEntry.Property(x => x.Price).CurrentValue;
   var originalPromotionPriceValue = promotionReferenceEntry.TargetEntry.Property(x => x.Price).OriginalValue;

   // read the navigation object current & original values
   var authorsCollectionEntry = entry.Collection(x => x.AuthorBooks);
   var currentAuthorIds = authorsCollectionEntry.CurrentValue.Select(x => x.AuthorId).ToList();
   var originalAuthorIds = ?????;
}
Calendula answered 18/4, 2019 at 17:19 Comment(5)
Have you tried looking at all the tracked AuthorBook entities and finding ones whose Author, or AuthorID OrignialValue matches the ID of entry?Maiolica
I haven't tried that but if I go that way, it will tell me which AuthorBook are tracked but not which AuthorBook are acutally linked to my Book or was linked to my book. Am I wrong?Calendula
Did you find a solution to this? Looking to get the Original Navigation Property values and the new navigation property valuesMisrepresent
@Pierre, I have'nt found a way to do it. I had to rethink everything to bypass this limitation.Calendula
@AlexandreJobin, Got it to work by loading the navigation property using the context: context.Entry(entry.Entity).Reference(x => x.Promotion).Load(); then afterwards have the values. To get the current and original values from the reference property, had to first create temp vars and clone the current/original values, set the original values as current values on the entity then load the reference to get the original values, set the Entity current values back to the cloned current values, load the reference to get its current values, done. You can temp modify state as well for Add/DeleteMisrepresent
M
1

Here is a way to get the reference properties in the context as well as its original values:

var entries = ChangeTracker.Entries<Book>()
                .Where(x => x.State == EntityState.Modified)
                .ToList();

foreach (var entry in entries)
{
    // If it is Added/Deleted, you can't get the OriginalValues/CurrentValues respectively.
    // So make it Modified for the meanwhile.
    var tempState = entry.State;
    entry.State = EntityState.Modified;
    
    // Clone the Entity values (the original ID, Current ID of the navigation
    var currentValues = Entry(entry.Entity).CurrentValues.Clone();
    var originalValues = Entry(entry.Entity).OriginalValues.Clone();
    
    // Set the Entity values to the OriginalValues and load the reference
    Entry(entry.Entity).CurrentValues.SetValues(originalValues);
    Entry(entry.Entity).Reference(x => x.Promotion).Load();
    
    // Store the Original Reference value in a variable
    var promotionReferenceEntryOriginalValue = entry.Reference(x => x.Promotion).CurrentValue;
    
    // Set the Entity values back to CurrentValues and load the reference
    Entry(entry.Entity).CurrentValues.SetValues(currentValues);
    Entry(entry.Entity).Reference(x => x.Promotion).Load();
    
    // Store the Current Reference value in a variable
    var promotionReferenceEntryCurrentValue = entry.Reference(x => x.Promotion).CurrentValue;
    
    // Set the Entry State back to its original State Added/Modified/Deleted
    entry.State = tempState;
    
    // read the entity current & original values 
    var currentTitleValue = entry.Property(x => x.Title).CurrentValue;
    var originalTitleValue = entry.Property(x => x.Title).OriginalValue;

    // read the reference object current & original values
    //var promotionReferenceEntry = entry.Reference(x => x.Promotion);
    var currentPromotionPriceValue = promotionReferenceEntryCurrentValue.Price; // promotionReferenceEntry.TargetEntry.Property(x => x.Price).CurrentValue;
    var originalPromotionPriceValue = promotionReferenceEntryOriginalValue.Price; // promotionReferenceEntry.TargetEntry.Property(x => x.Price).OriginalValue;
}

To make it dynamic, remove the type <Book> from ChangeTracker.Entries<Book>() and loop though the entry.Entity properties inside the foreach (var entry in entries) loop:

foreach (var propertyInfo in entry.Entity.GetType().GetProperties())
{
    var propertyName = propertyInfo.Name;
    //Do the loads here...
    //Get the values
}

Check if it is a navigation reference or a collection reference by trying to access it using extra methods and check if it is null or not, not null means it is a valid property to try and load() it:

private DbPropertyEntry GetProperty(DbEntityEntry entry, string propertyName)
{
    try
    {
        return entry.Property(propertyName);
    }
    catch { return null; }
}

private DbReferenceEntry GetReference(DbEntityEntry entry, string propertyName)
{
    try
    {
        return entry.Reference(propertyName);
    }
    catch { return null; }
}

private DbCollectionEntry GetCollection(DbEntityEntry entry, string propertyName)
{
    try
    {
        return entry.Collection(propertyName);
    }
    catch { return null; }
}
Misrepresent answered 15/9, 2020 at 5:17 Comment(0)
W
-1

https://github.com/dotnet/efcore/issues/9050

Original values are the values that were present in the database when the entity was queried. So for a relationship, the FK property will have an original value. Navigation properties are synthesized from the FK value rather than being directly mapped to anything in the database. This means that navigation properties (both reference and collections) do not have original values.

Woundwort answered 22/7 at 16:36 Comment(1)
That does not apply to the question. They know that reference and collection entries don't have OriginalValues properties (see the code). That's not the problem.Roxyroy

© 2022 - 2024 — McMap. All rights reserved.