简体   繁体   中英

How to save changes to EF Core tracked entity at a later date?

Afternoon,

I'm trying to use EF Core to modify an Entity along with related children. The twist is I'm trying to do this with an intermediate "approval" step.

Presently, I'm loading the Entity from the database, letting a user make changes to it's properties and relations, but where you'd normally just use Context.SaveChanges() on form submit, I'm serializing the Entity out to JSON. It's currently being stored in a file, but will later be stored in a db column.

At a later date, the Entity will again loaded from the db in an EF context with tracking, and the updated version is deserialized from the file. I was hoping to simply be able to map the updated entity onto the one loaded from the context ( Entity = UpdatedEntity ) and present the person approving the update with a list of what's changed, but of course that doesn't work: EF doesn't consider there to be any changes because the whole object has changed.

It works changing the Entity 's properties: Entity.PropA = "test" but not mapping the whole object to another with matching primary key. Mapping all the properties from the UpdatedEntity to the Entity from the context recursively through the large network of related entities would be cumbersome and I think error prone.

Is there any way to map a whole entity to a tracked entity, with all related children in EF Core?

Other ideas I've had include serializing the context's ChangeTracker and reapplying that, but I think is unlikely to work and would be comparing updated values to those present at the time of the form submission, not the values at approval time.

Always the way...scratch your head for hours but solve it minutes after posting a question:

Reassigning the updated object to the object loaded from the context doesn't work, but setting the values on the underlying EntityEntry does. Full example below using "Contact" as my Entity type.

// Value loaded from JSON file elsewhere.
Contact UpdatedEntity;

// Example from Blazor component, hence the factory
DbContext Context = DbFactory.CreateDbContext(); 

// Load the entity from db to context
Contact CurrentEntity = Context.Contacts.Where(c => c.ID == UpdatedEntity.ID);

// Create underlying EntityEntry for the loaded entity
EntityEntry<Contact> ee = Context.Entry(CurrentEntity);

// Update the values
ee.CurrentValues.SetValues(UpdatedEntity);

EF Core is now correctly tracking the changes between the current entity (in DB) and the updated values loaded from the JSON file.


EDIT:

This doesn't work as well as I thought, as no child entities are tracked.

Using this...

        Context.ChangeTracker.TrackGraph(ContactChanges.UpdatedContact, e =>
        {
            if (e.Entry.IsKeySet)
            {
                e.Entry.State = EntityState.Unchanged;
            }
            else
            {
                e.Entry.State = EntityState.Added;
            }
        });

... does get me tracked children, but of course all data looks like it's been updated.

Still yet to find a good solution.

EDIT FOR @freeAll

Contact is loaded from the database

Contact = Context.Contacts
  .Where(c => c.Code == ContactCode)
  .Include(contact => contact.FormerNames
    .OrderBy(formerName => formerName.Sequence)
  )
  .Include(contact => contact.TitleNavigation)
  .Include(contact => contact.MaritalStatusNavigation)
  .First();

Contact is then edited in a Blazor form. That includes any fields on the Contact but also related entities such as FormerName, including addition and deletion of these related entities.

These changes are then serialized into a JSON file like so:

ContactChanges changes = new ContactChanges() {
  UpdatedContact = Contact,
  ChangeRequestedAt = DateTime.UtcNow
};

string jsonString = JsonConvert.SerializeObject(
  changes,
  new JsonSerializerSettings() {
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
  });

string filePath = Path.Combine(
  Environment.ContentRootPath,
  "wwwroot",
  "contact_changes",
  $"{DateTime.UtcNow.ToString("yyyy-MM-dd--HH-mm-ss")}.json"
);

File.WriteAllText(filePath, jsonString);

Later on, the file is loaded and deserialized by another user:

string jsonString = System.IO.File.ReadAllText(filePath);
ContactChanges change = JsonConvert.DeserializeObject<ContactChanges>(jsonString);

At this point I had hoped to load the Contact from the database again (same query as above) and then just map the updated Contact data that has been deserialized onto the newly loaded/tracked Contact:

Contact = ContactChanges.UpdatedContact;

However that only tracks the changes made to the root Contact, not all the related entities like FormerName.

Just loading the deserialized data into `ChangeTracker.TrackGraph' without first loading the Contact as it currently exists in the db gets me all the correct changes loaded into EF, but they all show as modified because EF has nothing to compare it with. Ideally I'd like to show the user approving this change of data exactly what specific changes they'll be making rather than just "here's the new data that will replace the old, I don't know what's different".

After thinking this over the past week, I might try serializing the initial changes made to the JSON file rather than the updated state (a little like the ChangeTracker.DebugView ) and then apply that to recently loaded Contact data when it's time to approve it. I might get caught out with the related entities (particularly deletions) but at the moment, this seems the only viable option.

If you have any input or alternative ideas I'd be very grateful!

Thanks, Jamie.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM