首页 > 解决方案 > 如何在以后保存对 EF Core 跟踪实体的更改?

问题描述

下午,

我正在尝试使用 EF Core 来修改实体以及相关子项。扭曲是我试图通过中间的“批准”步骤来做到这一点。

目前,我正在从数据库中加载实体,让用户更改它的属性和关系,但是您通常只Context.SaveChanges()在表单提交时使用,我正在将实体序列化为 JSON。它当前存储在文件中,但稍后将存储在 db 列中。

稍后,实体将在 EF 上下文中再次从数据库加载并进行跟踪,并从文件中反序列化更新的版本。我希望能够简单地将更新后的实体映射到从上下文 ( Entity = UpdatedEntity) 加载的实体上,并向批准更新的人提供更改内容的列表,但这当然不起作用:EF 不考虑那里是任何变化,因为整个对象已经改变。

它可以更改Entity的属性:Entity.PropA = "test"但不能将整个对象映射到具有匹配主键的另一个对象。通过相关实体的大型网络递归地将所有属性从上下文映射UpdatedEntityEntity上下文会很麻烦,而且我认为容易出错。

有没有办法将整个实体映射到一个被跟踪的实体,以及 EF Core 中的所有相关子项?

我的其他想法包括序列化上下文的 ChangeTracker 并重新应用它,但我认为不太可能工作,并且会将更新的值与表单提交时存在的值进行比较,而不是在批准时的值。

标签: c#entity-framework

解决方案


总是这样……挠头几个小时,但在发布问题后几分钟就解决了:

将更新的对象重新分配给从上下文加载的对象不起作用,但在基础上设置值EntityEntry可以。下面使用“联系人”作为我的实体类型的完整示例。

// 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 现在可以正确跟踪当前实体(在 DB 中)与从 JSON 文件加载的更新值之间的更改。


编辑:

这并不像我想象的那么好,因为没有跟踪任何子实体。

使用这个...

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

...确实让我跟踪孩子,但当然所有数据看起来都已更新。

仍然没有找到好的解决方案。

编辑@freeAll

联系人是从数据库中加载的

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();

然后在 Blazor 表单中编辑联系人。这包括联系人上的任何字段,还包括相关实体,例如FormerName,包括这些相关实体的添加和删除。

然后将这些更改序列化为 JSON 文件,如下所示:

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

稍后,该文件被另一个用户加载和反序列化:

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

此时我希望再次从数据库中加载联系人(与上面的查询相同),然后将已反序列化的更新联系人数据映射到新加载/跟踪的联系人:

Contact = ContactChanges.UpdatedContact;

但是,它只跟踪对根联系人所做的更改,而不是所有相关实体(如FormerName)。

只需将反序列化的数据加载到“ChangeTracker.TrackGraph”中,而无需先加载当前存在于数据库中的联系人,就可以将所有正确的更改加载到 EF 中,但它们都显示为已修改,因为 EF 没有可比较的内容。理想情况下,我想向批准此数据更改的用户展示他们将进行哪些具体更改,而不仅仅是“这是将替换旧数据的新数据,我不知道有什么不同”。

在过去一周考虑了这一点之后,我可能会尝试序列化对 JSON 文件所做的初始更改而不是更新状态(有点像ChangeTracker.DebugView),然后在需要批准时将其应用于最近加载的联系人数据。我可能会被相关实体(特别是删除)所吸引,但目前,这似乎是唯一可行的选择。

如果您有任何意见或替代想法,我将不胜感激!

谢谢,杰米。


推荐阅读