微风:保存时的多对多问题

Posted

技术标签:

【中文标题】微风:保存时的多对多问题【英文标题】:breeze: many-to-many issues when saving 【发布时间】:2013-12-17 15:59:27 【问题描述】:

我一直在为微风应用中的多对多关联而苦苦挣扎。我在客户端和服务器端都有问题,但现在,我只会公开我的客户端问题。我不知道我想出的方法是否正确,我真的很想得到微风团队的反馈:

我的商业模式:

public class Request 

    public virtual IList<RequestContact> RequestContacts  get; set; 


public class RequestContact 

    public virtual Contact Contact  get; set; 
    public virtual Guid ContactId  get; set; 
    public virtual Request Request  get; set; 
    public virtual Guid RequestId  get; set; 



public class Contact 

    public virtual Client Client  get; set; 
    public virtual Guid? ClientId  get; set; 
    public virtual string Username  get; set; 

在我的 getRequest 查询的成功回调中,我将 contacts 属性添加到请求并填充它:

request.contacts = [];
request.requestContacts.forEach(function (reqContact) 
    request.contacts.push(reqContact.contact);
);

视图绑定到一个联系人数组,在控制器中定义:

<select ng-multiple="true" multiple class="multiselect" data-placeholder="Select Contacts" ng-change="updateBreezeContacts()" ng-model="request.contacts" ng-options="c as c.username for c in contacts | filter:clientId: request.client.id track by c.id"></select>

控制器:

//the collection to which the multiselect is bound: 

$scope.contacts = dataService.lookups.contacts;

每当在多选中选择或取消选择一个项目时,都会调用此方法:

$scope.updateBreezeContacts = function () 
  //wipe out all the RequestContact entities
  $scope.request.requestContacts.forEach(function (req) 
    req.entityAspect.setDeleted();
  );

  //populate the RequestContact based on selected contacts 
  for (var i = 0; i < $scope.request.contacts.length; i++) 
     var requestContact = dataService.createRequestContact($scope.request,    $scope.request.contacts[i]);
     $scope.request.requestContacts.push(requestContact);
  

dataService 的 createRequestContact 方法实际上是这样做的:

manager.createEntity('RequestContact',  request: myRequest, contact: myContact);

用例场景:


请求有一个选定的联系人。 用户取消选择联系人,然后从列表中选择另一个。 然后她决定重新选择之前未选择的那个。我们现在有两个选定的联系人。 用户点击保存按钮并调用 saveChanges。 Breeze 向服务器发送 3 个实体:第一个联系人处于“已删除”状态,同一联系人再次处于“已添加”状态,最后另一个被选中的联系人也处于“已添加”状态。

这是在处理多对多关联时应该做的吗?

我实际上得到了一个服务器错误(“非空属性引用了一个空值或瞬态值 Business.Entities.RequestContact.Request”),但在得出任何结论之前,我想知道我在客户端上做了什么-side 是正确的。

【问题讨论】:

C# RequestContact 类的 PK 在哪里(例如,Request.IdContact.Id)? 我在 SO 帖子中省略了它们,但它们在我的代码中。抱歉,我的模型描述不准确。非常感谢这么长的回答,我刚看到它,所以在我回复之前给我一些时间来处理它:) 【参考方案1】:

服务器端

您首先要处理服务器端建模问题。我在对您的问题的评论中注意到没有 PK。我建议你先搞定它,然后再打扰客户。

客户端

我对这种情况有很长时间的经验。对我来说,典型案例是用户可以拥有任意数量的角色,并且他/她拥有的角色在 UserRoles 表中。

典型的用户界面:

选择并呈现用户 使用前面的复选框显示该用户所有可能角色的列表 如果用户具有该角色,则选中该复选框;如果他/她没有,则未选中

哦哦

我多次看到人们将所有可能的角色列表绑定到UserRole实体列表。这很少奏效。

我经常看到人们在用户单击复选框时创建和销毁UserRole 实体。这很少奏效。

我经常看到UserRoleentities 添加和删除以及添加和删除到缓存中。这通常是致命的,因为客户端无法跟踪 UserRole 实体此时是否对应于数据库中的记录。

如果我正确阅读了您的代码,那么您就犯了所有这些错误。

改为项目视图模型

当我将此用户的角色表示为“Item ViewModel”实例的列表并将实体操作推迟到保存用户选择的时间时,我取得了更大的成功。

为了我们的讨论,我们称这个对象为 UserRoleVm。在 javascript 中可能定义如下


    role,
    isSelected,
    userRole

当你构建屏幕时,

填充UserRoleVm 实例列表,每个Role 一个

用适当的Role实体设置每个虚拟机的role属性

将视图绑定到vm.role.name

将每个虚拟机的 userRole 属性设置为相关用户的 UserRole 实体当且仅当这样的实体已经存在时

如果 vm 有 userRole 并且如果 vm.userRole.entityAspect.entityState 未被删除,则设置 vm 的 isSelected=true

将 vm 的 isSelected 绑定到复选框

现在用户可以随意勾选和取消勾选。

在此过程中,我不会创建/删除/修改任何UserRole 实体。我等待 UI 信号保存(无论该信号是什么)。

在保存准备期间,我遍历 UserRoleVm 实例列表

如果未选中且没有vm.userRole,则什么也不做

如果没有选中并且有vm.userRole,那么vm.userRole.entityAspect.setDeleted()。如果vm.userRole.entityAspect.entityState 是Detached(意味着它之前处于Added 状态),则设置vm.userRole = null。

如果选中但没有vm.userRole,则创建一个新的UserRole并将其分配给vm.userRole

如果选中并且有vm.userRole,那么如果vm.userRole.entityAspect.entityState

不变,什么都不做 已修改(为什么?如何?),通过调用 vm.userRole.entityAspect.rejectChanges() 恢复 已删除(必须是预先存在的 UserRole,它“未选中”但仍未保存;这是怎么发生的?),通过调用 vm.userRole.entityAspect.rejectChanges() 恢复

现在拨打manager.saveChanges()

如果保存成功,则一切正常。

如果失败,最干净的方法是调用manager.rejectChanges()。这会清除牌组(并丢弃用户自上次保存后所做的任何更改)。

不管怎样,从头开始重建列表。

理想情况下,在异步保存成功或失败返回之前,不要让用户对用户角色进行更多更改。

我相信你可以比这更聪明。但这种方法很稳健。

变体 不要打扰UserRoleVm.userRole。不要在UserRoleVm 中携带现有的UserRole 实体。而是在初始化UserRoleVm.isSelected 属性时引用用户缓存的UserRole 实体。然后在保存准备期间评估列表,根据相同的逻辑查找和调整缓存的UserRole 实例。

启用保存按钮(12 月 19 日更新)

山姆问:

当 EntityManager 发生更改时,Save 按钮的 disabled 属性绑定到设置为 true 的属性。但是,由于我的 ViewModel 不是 EntityManager 的一部分,因此当用户添加/删除联系人时,这不会更改附加到 EntityManager 的模型。因此,保存按钮永远不会启用(除非我更改模型的另一个属性)。你能想出一个解决方法吗?

是的,我能想到几个。

    isSelected 属性定义为具有get 和set 方法的ES5 属性;在 set 方法中,您向 outer VM 发出 UserRoleVm 实例已更改的信号。这是可能的,因为如果你让 Angular 和 Breeze 一起工作,你必须使用 ES5 浏览器。

    将 ngClick(或 ngChanged)添加到绑定到 outer vm 中的函数的复选框 html,例如,

    ... ...

    利用 Angular 对“视图更改”检测的原生支持(我认为是“isPristine”)。我通常不走这条路,所以我不知道细节。只要您不允许用户离开此屏幕并返回期待对 UserRoleVm 列表的未保存更改已被保留,这是可行的。

vm.userRoleClicked 可以将 vm.hasChanges 属性设置为 true。将保存按钮的 isEnabled 绑定到vm.hasChanges。现在,当用户单击复选框时,保存按钮会亮起。

如前所述,保存按钮单击操作遍历userRoleVm 列表,创建和删除UserRole 实体。当然这些动作是被EntityManager检测到的。

你可以变得更漂亮。您的 UserRoleVm 类型可以在创建时记录其原始选定状态 (userRoleVm.isSelectedOriginal) 并且您的 vm.userRoleClicked 方法可以评估整个列表以查看是否有任何当前选定状态与其原始选定状态不同......并设置 @987654373 @ 因此。这完全取决于您的用户体验需求。

重建列表时不要忘记清除vm.hasChanges

我想我更喜欢#2;这对我来说似乎既简单又清晰。

2014 年 2 月 3 日更新:plunker 中的示例

我已经写了a plunker to demonstrate the many-to-many checkbox technique 我在这里描述过。 readme.md 解释了一切。

【讨论】:

谢谢沃德。关于方法2,请问userRoleClicked函数应该做什么?我真的不明白。更具体地说,你如何告诉微风 EntityManager 应该是脏的?或者,方法 3 对我来说可能是一个选择。 更新了更新,试图澄清userRoleClicked的行为 好的,现在很有意义!感谢您的澄清。 哦,我使用 manager.getChanges('RequestContact') 而不是 manager.rejectChanges(),然后遍历实体以拒绝每个实体。否则,整个表单会被重置,这对用户来说很不方便。【参考方案2】:

Breeze.js 客户端目前不支持“多对多”关系。您必须将连接/映射表作为实体公开。关于同一主题还有其他几篇文章可用。

我们确实计划在未来添加多对多支持。抱歉,还没有日期...

【讨论】:

我知道目前还不支持多对多。如我的帖子中所述,我已经暴露了连接表。这就是为什么我将这段代码放在一起,以便根据选定的联系人创建 RequestContact 实体。但我不确定这种方法是否正确,因此我无法判断我得到的服务器错误是否与此代码或其他内容有关。 Ward,我还有一个关于您的解决方案的问题。在保存失败的情况下,您说最干净的方法是调用 manager.rejectChanges()。但这不是我想做的事情,因为我的表单中有其他字段,它们当然不应该重置为原始值,这肯定会激怒我的用户:) 我想要的只是恢复所做的更改到 RequestContacts 集合(在您的情况下为 UserRoles 集合)。我不知道该怎么做。有什么想法吗?

以上是关于微风:保存时的多对多问题的主要内容,如果未能解决你的问题,请参考以下文章

如何保存具有直通关系的多对多字段

如何在 django 中处理未保存的多对多关系?

Laravel 上的多对多问题关系

推进:没有交叉表的多对多关系

没有链接表/模型的多对多关系?

(十三)Hibernate中的多表操作:单向多对多