【问题标题】:Database transactions in AngularJS/.NET Core architectureAngularJS/.NET Core 架构中的数据库事务
【发布时间】:2017-01-08 01:02:15
【问题描述】:
+==========+      +==========+      +=========+      +======+
+  Angular +<---->+ ASP.NET  +<---->+ Service +<---->+  EF  +<---->(Database)
+  Client  +      + Core API +      +  Layer  +      + Core +
+==========+      +==========+      +=========+      +======+

问题 - 在 AngularJS/.NET Core 架构中,您在哪里实现数据库事务? (上图说明了一种可能的架构。)

示例场景 - 在一个数据库事务中,您希望 (1) 插入一条新的客户记录,(2) 插入一条新的订单记录,以及 (3) 插入两条新的订单详细记录。

【问题讨论】:

  • 您需要更具体一点,哪个层执行对数据库的更改?根据您的信息,我猜是在 EF Core 中
  • 我将举一个例子。以下是插入客户记录的流程:(1) Angular 客户端向客户控制器执行 HTTP POST(在 ASP.NET Core API 中),(2) 客户控制器调用服务层,反过来,调用 EF Core 方法将客户记录添加并提交到数据库。不需要显式事务,因为只有一条记录(并且对 EF SaveChanges() 的调用将插入包装在隐式事务中)。
  • 所以,当我想使用单个事务将多条记录插入多个表时,问题就来了。代码应该去哪里,管理插入多条记录的事务?
  • @stickian:避免在方法/类/命令或路由中使用诸如“Create”、“Delete”、“Update”之类的词,而更喜欢使用域的语言(PlaceOrder、CancelOrder 而不是 CreateOrder、DeleteOrder )。比如“DeleteOrder”就不太清楚了,为什么要删除一个订单?这可能很糟糕,对于业务专家来说听起来像是数据丢失。当您说“取消订单”时,更明显的是订单仍然存在于数据库中,但只是被取消了

标签: angularjs transactions asp.net-core .net-core entity-framework-core


【解决方案1】:

使用客户端代码(JavaScript、Angular 等)时,您必须从 HTTP 协议的角度来看待它。单个 HTTP 请求应被视为一个原子操作(=事务)。

其中一个原因是,HTTP 是一种无状态协议,默认情况下不会保留状态,虽然您可以解决它(会话),但拥有状态(保存在会话中的值,临时数据等)。 are request 所需的一切都应随 request 一起传递。

此外,像 EntityFramework Core 这样的 ORM 的默认生命周期是“范围内的”,这意味着它在请求开始时创建并在请求结束时释放。这对于 ACID/事务操作和内存管理很重要(否则对象将被永远跟踪并导致内存泄漏)。因此,您不应篡改或尝试解决此限制。

话虽如此,如果您希望将来自客户端(浏览器)的多个操作视为单个操作,则需要将它们批处理成一个结构,然后将其与单个请求一起发送到数据库。

但是,在坚持 RESTful 服务原则的同时,这可能有点棘手。

一种方法是创建一个专门的 ViewModel 来确定事务的范围,我们称之为 OrdersTransactionScopeViewModel,而不是创建单个 OrderOrderDetail 模型,而是创建一个包含两者的 OrderViewModel 模型。

public class OrdersTransactionScopeViewModel
{
    public Customer Customer { get; set; }
    public List<OrderViewModel> Orders { get; } = new List<OrderDetailViewModel>();
}

public class OrderViewModel 
{
    public List<OrderDetailViewModel> { get; } = new List<OrderDetailViewModel>();
    public ShippingMethod ShippingMethod { get; set; }
}

public class OrderDetailViewModel 
{
    public int OrderPosition { get; set; }
    public string PartNumber { get; set; }
    public decimal Quantity { get; set; }
}

然后在客户端全部收集并一次性提交。

{
    "customer": { "name": "Firstname", "lastname": "Lastname", "address": { ...},
    "order": [{
        orderDetails: [{
            "orderPosition": 1,
            "partNumber": "12345",
            "quantity": 2
        },{
            "orderPosition": 2,
            "partNumber": "11111",
            "quantity": 1
        },{
            "orderPosition": 1,
            "partNumber": "666",
            "quantity": 12
        }]
    }]
}

然后您将一个对象发送到 REST 服务,您可以将其作为一个事务处理。读取属性,从中生成持久性模型并将其插入。

我假设当所有这些都通过时,您会考虑下订单并执行订单。上述工作,但可能无法适当地涵盖业务需求,并且无法跟踪具体发生的事情。

处理它的另一种方法是不将其视为原子操作。

例如:

  • 用户将商品放入购物车(如果您希望它是幂等的,请调用 POST /api/cart 或 POST /api/cart/{position:int})
  • 用户将另一件商品放入购物车
  • 用户结帐并被要求添加送货地址和付款方式
  • 如果不存在,用户创建一个帐户(调用 POST /api/register)
  • 用户获得概览并必须确认(POST /api/cart/processOrder)

每个操作都将对象放入一个容器(此处为购物车)中,最后您处理它并从那里获取它并创建订单。

如果订单是由公司的员工创建的(即客户通过电话订购)并且流程不同,则此方法的工作方式会有所不同。

那里没有购物车。不过处理步骤类似。

  • 员工打开一个新订单 (POST /api/orders) 并可能收到这样的 JSON 响应(可能包含状态和填充的一些内容,例如当前日期、部门和打开它的员工 ID)

    {
        "orderId": "unique-guid",
        "createdOn": "2017-01-07T15:01:24",
        "createdBy": "employee-guid-here",
        "state": "open",
        "orderNum": null,
        "orderDetails": []
    }
    

    这是向员工显示的。

  • 员工从客户那里接过订单并不断向其中添加新的订单详细信息,每个订单都会触发对 API 的请求 (POST /api/order/unique-guid)
  • 完成后,员工向客户询问其客户编号。他检查它的存在 (GET /api/customer/{id})。如果没有找到,他会向客户询问详细信息并发布结果(POST /api/customer)
  • 最后,他要求客户确认他的订单并点击“下订单”按钮 (PUT /api/order/unique-guid) 并在成功处理后创建一个 orderNum。流程从打开变为“已放置”,并设置了“orderPlacedOn”字段
  • 如果客户决定不订购,员工点击取消并注明原因“客户改变主意”或“价格太高”,订单将被标记为“已取消”或“关闭”

在这种情况下,订单仍保留在数据库中,但您有额外的业务价值。您可以查看所有下达的订单以及取消订单的原因(可用于在未来提高客户满意度并对其做出反应)以及跟踪员工在此订单上使用的时间(无投资回报率)(对于通过对取消订单的原因做出反应来提高获取率)。

在这种情况下,您完全无需交易即可工作并获得额外的商业价值。此外,如果浏览器崩溃或员工意外按 F5,则不会丢失任何内容(可能是上述单一事务方法的情况)。

【讨论】:

    【解决方案2】:

    以下内容可能对您有所帮助。

    1. 首先,我想说明的是,如果您为实际项目构建应用程序,您会缺少一个重要的层:存储库层。介于 Service 层和 EF 层之间。

    2. 您可以将 EF 层视为工作单元,但在这种情况下,您必须将其公开给服务层,因此您最好也创建工作单元概念。

    3. 然后在服务层,您可以将存储库与工作单元一起使用。在这里,工作单元将在其他存储库中启动事务分发。

    http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work

    您可以在网上找到更多示例。

    【讨论】:

    • 这里的问题不是在服务器端实现它,而是应用程序在客户端(浏览器、JavaScript)上运行,并且应该将几个独立的请求捆绑到一个事务中。由于 DbContext 的作用域特性(从资源和性能的角度来看,Singleton 没有任何意义),这不能很好地工作。因此,几乎不可能通过 5 个 http 请求(创建客户、创建订单、创建 3 个详细信息)向单个交易发送垃圾邮件
    猜你喜欢
    • 2022-11-26
    • 2019-11-14
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 2019-03-03
    • 2020-09-12
    相关资源
    最近更新 更多