Alexandre Jobin
Alexandre Jobin

Reputation: 2861

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

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 = ?????;
}

Upvotes: 7

Views: 2344

Answers (2)

n0099
n0099

Reputation: 1353

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.

Upvotes: -1

Pierre
Pierre

Reputation: 9052

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; }
}

Upvotes: 1

Related Questions