c# - 如何在以后保存对 EF Core 跟踪实体的更改?
问题描述
下午,
我正在尝试使用 EF Core 来修改实体以及相关子项。扭曲是我试图通过中间的“批准”步骤来做到这一点。
目前,我正在从数据库中加载实体,让用户更改它的属性和关系,但是您通常只Context.SaveChanges()
在表单提交时使用,我正在将实体序列化为 JSON。它当前存储在文件中,但稍后将存储在 db 列中。
稍后,实体将在 EF 上下文中再次从数据库加载并进行跟踪,并从文件中反序列化更新的版本。我希望能够简单地将更新后的实体映射到从上下文 ( Entity = UpdatedEntity
) 加载的实体上,并向批准更新的人提供更改内容的列表,但这当然不起作用:EF 不考虑那里是任何变化,因为整个对象已经改变。
它可以更改Entity
的属性:Entity.PropA = "test"
但不能将整个对象映射到具有匹配主键的另一个对象。通过相关实体的大型网络递归地将所有属性从上下文映射UpdatedEntity
到Entity
上下文会很麻烦,而且我认为容易出错。
有没有办法将整个实体映射到一个被跟踪的实体,以及 EF Core 中的所有相关子项?
我的其他想法包括序列化上下文的 ChangeTracker 并重新应用它,但我认为不太可能工作,并且会将更新的值与表单提交时存在的值进行比较,而不是在批准时的值。
解决方案
总是这样……挠头几个小时,但在发布问题后几分钟就解决了:
将更新的对象重新分配给从上下文加载的对象不起作用,但在基础上设置值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
),然后在需要批准时将其应用于最近加载的联系人数据。我可能会被相关实体(特别是删除)所吸引,但目前,这似乎是唯一可行的选择。
如果您有任何意见或替代想法,我将不胜感激!
谢谢,杰米。
推荐阅读
- sox - sox 转换为频谱图参数含义
- c# - 如何使两个物体反向绕一个点在曲线中这样做?统一,C#
- php - Wordpress 空白前空白页不起作用
- java - 抽象类的 Hibernate/JPA 映射
- c++ - 在基于范围的 for 循环中使用 shared_ptr 到 std::vector
- javascript - 将Javascript变量发送到文件中的PHP函数参数中?
- node.js - Nodejs SSL-证书验证
- linux - 配置 apache webserver 以使用 ip 地址公开查看 web 上的默认页面(动态)
- javascript - 使用 Angular 4/5 与 iframe(相同域)通信
- elasticsearch - 用于 ElasticSearch 6.2.4 的 filebeat-index-template.json