Breeze的EntityManager(javascript)有一对我们用来实现撤消功能的方法。当对话框打开时,我们使用exportEntities拍摄EntityManager
的快照。如果用户取消对话框,我们会调用importEntities并将调用的输出传递给exportEntities
。我们还传入MergeStrategy
OverwriteChanges
以确保快照中的版本“撤消”目标中的任何更改。
由于Added
处理具有存储生成密钥的新(importEntities
)实体的方式,此处需要一些额外的逻辑。
...可以为新实体分配新的临时键值。这个可以 如果在关闭并重新启动后导入,则会发生 应用。您不应该假设导入的实体将具有 与导出时相同的临时密钥。一个关键的价值 可能需要进行更改以防止导入的临时密钥 新实体与实体的临时键值冲突 已经在目标经理中。
请参阅Export and import entities页面上标有“导入新实体”的部分。
注意:应用程序似乎无需关闭并重新启动应用程序以应用此行为(与上述文档相反)。
不仅分配了新密钥,而且还将这些实体作为新实例导入。现有实例在EntityManager中保持不变。我们在importEntities
完成后手动删除这些(以及在拍摄快照后添加的任何其他内容)。
我们考虑在调用importEntities
之前删除所有实体,这样我们就不必在之后有选择地删除它们,但意识到这意味着我们在修复对原始引用的方面会有更多的工作实体实例。实际上,从EntityManager外部到我们做删除的实体的引用需要手动修复。
乍一看,好像保持了导航属性(EntityManager
中的实体之间的引用)。即引用在importEntities
期间替换的实体(因为它具有临时密钥)的导航属性将更新为指向新实体。因此,尽管EntityManager
之外的任何引用都需要修复,但其中的内容却没有。
我们后来发现了一种不维护导航属性的情况。考虑这种情况。
entityA - EntityState.Unchanged
entityB - EntityState.Added
entityA.navigationPropery = entity
entityA - EntityState.Modified
entityB - EntityState.Added
importEntities
完成后,entityB
已替换为与原始实例具有不同临时密钥的新实例。我希望entityA.navigationProperty
能够引用这个新实体,但它会继续保留对原始实体的引用。在我们的设置中,原始实体在EntityManager
之后从importEntities
手动分离,导航属性最终为空引用,并且临时ID与{{1}中的任何实体都不匹配}。最终,这会导致EntityManager
期间数据库中出现外键冲突。
请注意,我们看到导航属性正确维护,其中entityA具有状态saveChanges
。
我在DocCode测试中复制了这个问题,但没有成功(测试通过)。我猜测C#和JavaScript的实现有什么不同?是否有一些针对JavaScript的类似资源?还是其他一些推荐的再现问题的方法?
EntityState.added
我们如何确保维护所有导航属性?
我们可以采取其他方法来实现撤销,这不会产生这个问题(也许不需要我们必须手动修复对实体的外部引用)?
注意:我们正在使用[TestMethod]
public async Task ImportChangedEntityAndMaintainNavigationPropertyReferencesToIt()
{
var manager = new EntityManager(_serviceName);
await manager.FetchMetadata(); // Metadata must be fetched before CreateEntity() can be called
var order = manager.CreateEntity<Order>(new {OrderID = 1}, EntityState.Unchanged);
//var order = manager.CreateEntity<Order>(EntityState.Unchanged);
var employee = manager.CreateEntity<Employee>();
order.Employee = employee;
var exportData = manager.ExportEntities();
var imported = manager.ImportEntities(exportData, new ImportOptions(MergeStrategy.OverwriteChanges));
var importedOrder = imported.ImportedEntities.OfType<Order>().Single();
Assert.AreSame(order, importedOrder);
var managerOrder = manager.GetEntities<Order>().Single();
Assert.AreSame(order, managerOrder);
Assert.AreEqual(importedOrder.Employee.EntityAspect.EntityState, EntityState.Added);
Assert.AreNotSame(importedOrder.Employee, employee); //Entities with temporary keys are replaced with new instances.
Assert.IsFalse(importedOrder.Employee.EntityAspect.EntityKey.Equals(employee.EntityAspect.EntityKey)); //Entities with temporary keys are assigned new ones.
}
版本1.3,这是目前npm上的最新版本。
答案 0 :(得分:0)
As far as Breeze knows, an import bundle could have been created at any time in the past, and for any reason.
So Breeze defensively assumes that any entities in the import bundle that are in the Added
state are not really the same
as any similar entities that are already in the cache. If they have the same temporary keys as entities in the cache,
then the imported entities will get new temporary keys so that they will not conflict with any existing temporary
keys.
As you've observed, Breeze does merge other entities (those in the Deleted
, Modified
, or Unchanged
states).
So a possible workaround for your problem is to:
Added
state to Modified
SaveChanges
, process each entity with a negative key value:
Modified
, change to Added
Deleted
, change to Detached
The downside of this is if anything in your UI treats Added
entities differently from Modified
ones, it won't work. You would need to put a separate flag on the entity to tell the UI that it's really added.
I will look into the possibility of adding a flag on importEntities
that will allow for merging of Added
entities that
have the same temporary key.
UPDATE: The mergeAdds
flag has been added in breeze 1.7.0. So now you can do this:
entityManager.importEntities(exportData, {
mergeAdds: true,
mergeStrategy: breeze.MergeStrategy.OverwriteChanges
});
to merge added entities that have the same key.