Web services 如何执行面向服务的父子事务? 例子:

Web services 如何执行面向服务的父子事务? 例子:,web-services,entity-framework,asp.net-web-api,breeze,graphdiff,Web Services,Entity Framework,Asp.net Web Api,Breeze,Graphdiff,SalesOrder由SalesOrderHeader和一个或多个SalesOrderItems组成。编辑现有SalesOrder时,可以修改SalesOrderHeader,还可以添加、修改和删除SalesOrderItems。所有更改必须保存在单个事务中。多个用户可以以乐观并发方式同时编辑SalesOrder 我相信在单个事务中完成保存的要求鼓励我们在单个服务调用中同时传递SaleOrderHeader和SaleOrderItems。将子数据与其父数据打包的含义是,需要对是否添加、修改或删除

SalesOrder由SalesOrderHeader和一个或多个SalesOrderItems组成。编辑现有SalesOrder时,可以修改SalesOrderHeader,还可以添加、修改和删除SalesOrderItems。所有更改必须保存在单个事务中。多个用户可以以乐观并发方式同时编辑SalesOrder

我相信在单个事务中完成保存的要求鼓励我们在单个服务调用中同时传递SaleOrderHeader和SaleOrderItems。将子数据与其父数据打包的含义是,需要对是否添加、修改或删除子数据有一些了解

子实体的更改跟踪可以在服务器上进行,也可以在客户端进行

服务器上的更改跟踪 此策略的思想是,客户可以根据自己的意愿修改SalesOrder,而无需跟踪添加、修改或删除了哪些SalesOrderItems。调用保存服务时,将在服务器上确定SalesOrderItems的状态

服务器在服务调用之间应保持无状态。这意味着服务器无法在检索和最终保存之间保留有关SalesOrder状态的任何信息。如果服务器要通过比较修改后的对象图和数据库对象图来确定其实体的状态,则只剩下一个选项

对于nHibernate,有一个合并函数来实现这一点。在实体框架中,需要添加此项。还有一个针对EF的开源实现,名为

这在理论上听起来很棒,因为它使服务的设计和使用变得非常容易。然而,我认为这一战略存在两个主要问题。首先是性能。每次保存时都必须发送回整个对象图。无论是否修改了SalesOrderItem,都必须将其发回,否则服务器将假定它已被删除。第二个问题更为关键,它与并发性有关。如果用户1将SalesOrderItem添加到SalesOrder中,并且用户2对同一SalesOrder进行了更改,则当用户2保存时,服务器将假定用户1添加的SalesOrderItem应被删除,因为它未包含在用户2的对象图中。我认为在服务器端更改跟踪的任何实现中都无法防止这种情况

客户端上的更改跟踪 另一种方法是让客户端跟踪对其实体的更改,并在调用save服务时传达该状态。一个好处是客户端不需要发送其未更改的子实体。这有助于提高性能。缺点是所有实体都需要一个名为“ObjectState”的附加属性来跟踪它是被添加、修改还是被删除。这使得服务器上的实体模型非常混乱,充满了与业务领域无关的问题。这也让服务的不同消费者有责任保持这种状态。另一个问题是,处理已删除的实体变得很困难。SalesOrderHeader是否应维护已删除SalesOrderItems的列表?或者应该为SalesOrderItems分配一个必须由客户端UI过滤掉的已删除状态吗

我知道javascript库有自己的客户端实体跟踪实现,但我担心它的实现需要客户端和服务器端组件。服务层不应该隔离我们在任何一方使用的技术吗?如果非javascript客户端想要使用我的服务怎么办

问题:
我认为这是大多数服务实现都应该解决的常见场景。我是否做出了任何错误的假设,或者我是否在做一些不寻常的事情?你实施了什么策略?有什么合理的替代方案吗?

充分披露:我与客户合作,我认为跟踪客户的变化是一条可行之路。客户端上的更改跟踪允许无状态服务器,减少客户端和服务器之间的通信量,并允许脱机使用

在Breeze中,您提到的“ObjectState”称为,每个实体都有一个,但它不是域模型的一部分。服务器端实体不需要EntityAspect,但服务器端服务必须知道如何处理来自客户端的实体状态信息

基本上,服务需要根据来自客户端的信息创建、更新或删除实体。Breeze现有的服务器端后端已经完成了这一切(在、、和中),但您也可以编写自己的。您的服务器只需要知道如何与客户端通信

假设我们已经更新了一个
SalesOrder
,并添加了一个新的
SalesOrderItem
。Breeze客户端发送一个如下所示的保存包:

{
"entities": [
    {
        "Id": 123,
        "Title": "My Updated Title",
        "OrderDate": "2014-08-03T07:00:00.000Z",
        "entityAspect": {
            "entityTypeName": "SalesOrder:#My.DomainModel",
            "entityState": "Modified",
            "originalValuesMap": {
                "Title": "My Original Title"
            },
            "autoGeneratedKey": {
                "propertyName": "Id",
                "autoGeneratedKeyType": "Identity"
            }
        }
    },
    {
        "Id": -1,
        "SalesOrderId": 123,
        "ProductId": 456,
        "Quantity": 11,
        "entityAspect": {
            "entityTypeName": "SalesOrderItem:#My.DomainModel",
            "entityState": "Added",
            "originalValuesMap": {
            },
            "autoGeneratedKey": {
                "propertyName": "Id",
                "autoGeneratedKeyType": "Identity"
            }
        }
    }
]
}
在这里,Id为#123的SalesOrder已被修改(其标题已更改)。
entityAspect
包括显示上一个标题的
原始值映射。
服务器需要使用新值更新现有SalesOrder。服务器是否需要在应用更改之前从数据库查询现有SalesOrder取决于实现

已添加新的SalesOrderItem。在客户端上为其创建了一个临时Id,-1。服务器需要创建并持久化一个新的SalesOrderItem,并为其生成一个真实的Id

来自服务器的响应应该包含创建和更新的实体,以及显示服务器生成的密钥映射到临时客户端密钥的密钥映射信息,以便客户端可以替换它们


变更跟踪不是一个简单的问题,但Breeze会尽力为您完成困难的部分。

我想借用Steve的答案

我们