如何通过 NHibernate 处理聚合根中持久计算属性的并发?

发布于 2024-09-27 08:55:46 字数 1361 浏览 10 评论 0原文

我需要保留具有聚合根的计算属性。计算基于子实体。我使用根通过域方法添加/删除子项,这些方法更新计算属性。

系统的多个用户可以将子实体添加到特定的根。例如,UserA 可以向 Root123 添加子项,UserB 也可以向 Root123 添加子项。

当多个用户可能在不同事务中将子实体添加到同一根时,如何确保这一计算属性准确保留?在我的特定情况下,计算的属性用于确保不超过根上另一个属性设置的某些限制。


下面是该问题的一个更具体的示例:

public class RequestForProposal : AggregateRoot {
    ...
    private ISet<Proposal> _proposals = new HashedSet<Proposal>();

    public virtual int ProposalLimit { get; set; }
    public virtual int ProposalCount { get; protected set; }

    public virtual IEnumerable<Proposal> Proposals {
        get { return _proposals; }
    }
    ...

    public virtual void AddProposal(User user, Content proposalContent) {
        if (ProposalCount >= ProposalLimit) {
            throw new ProposalLimitException("No more proposals are being accepted.");
        }

        var proposal = new Proposal(user, proposalContent);
        _proposals.Add(proposal);
        ProposalCount++;
    }

    public virtual void RemoveProposal(Proposal proposalToRemove) {
        _proposals.Remove(proposalToRemove);
        ProposalCount--;
    }
}

如果 2 个用户大致同时提交提案怎么办? UI 发现尚未达到限制,并显示为两个用户提交提案的网页。当第一个用户提交时,一切都很好。现在,只要第一个用户在第二个用户之前提交,第二个用户就可以了,这样当第二个用户提交时,从数据库中检索数据并且限制将是准确的。

这是一个有争议的问题吗?对于 2 个用户几乎同时提交的罕见情况,我是否应该依赖数据库中的约束(ProposalLimit >= ProposalCount)?

I have the need to persist a calculated property with an aggregate root. The calculation is based on child entities. I am using the root to add/remove the children via domain methods, and these methods update the calculate property.

A child entity can be added to a particular root by multiple users of the system. For example, UserA can add a child to Root123, and UserB can also add a child to Root123.

How can I ensure that this calculated property is persisted accurately when more than one user may be adding child entities to the same root in different transactions? In my particular case, the calculated property is used to ensure that some limit is not exceeded, as set by another property on the root.


Here is a more concrete example of the problem:

public class RequestForProposal : AggregateRoot {
    ...
    private ISet<Proposal> _proposals = new HashedSet<Proposal>();

    public virtual int ProposalLimit { get; set; }
    public virtual int ProposalCount { get; protected set; }

    public virtual IEnumerable<Proposal> Proposals {
        get { return _proposals; }
    }
    ...

    public virtual void AddProposal(User user, Content proposalContent) {
        if (ProposalCount >= ProposalLimit) {
            throw new ProposalLimitException("No more proposals are being accepted.");
        }

        var proposal = new Proposal(user, proposalContent);
        _proposals.Add(proposal);
        ProposalCount++;
    }

    public virtual void RemoveProposal(Proposal proposalToRemove) {
        _proposals.Remove(proposalToRemove);
        ProposalCount--;
    }
}

What if 2 users are submitting their proposals at roughly the same time? The UI sees that the limit has not yet been reached and shows the Web page to submit a proposal for both users. When the first user submits, all is well. Now, the second user will be okay as long as the first user submits before the second one, so that when the second user submits, the data is retrieved from the DB and the limit will be accurate.

Is this a moot point? Should I rely on constraints in the DB (ProposalLimit >= ProposalCount) for those rare cases where 2 users submit nearly simultaneously?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

恋竹姑娘 2024-10-04 08:55:46

如果您将业务规则检查(即限制检查)放在交易中,那就没问题了。即

  1. 用户单击按钮,触发添加提案命令
  2. 代码启动新事务。请参阅此处了解我建议您如何使用事务
  3. 从数据库加载RequestForProposal对象,或者刷新一下吧我建议你使用升级锁。
  4. 将新提案添加到根目录。检查限制约束,失败则抛出异常。
  5. 提交事务

通过这种方式,您可以使用数据库并发控制。我认为没有其他方法可以做到这一点。

这会产生一些争用,但您可以采取一些步骤来最大程度地减少争用。即确保在步骤 3 中您选择的数据库列有索引。这将导致行锁,而不是页锁。

如果您在步骤 3 中使用升级锁,这将避免死锁。基本上,当第二个用户提交同一聚合根的提案时,数据库将不会让您读取它,直到第一个事务提交为止。

您还应该考虑在 Proposal.RequestForProposalId 上添加数据库索引,这将有助于提高性能,因为这是加载提案的列。我不是 100% 认为它有助于最小化该表上任何锁的范围,但它可能......

If you were to put the Business Rule Check (ie limit check) inside a transaction, it will be fine. That is

  1. User Clicks button that fires the Add Proposal Command
  2. Code starts a new transaction. See here for how I suggest you use transactions
  3. Load the RequestForProposal object from the db, or Refresh it. I suggest you use an upgrade lock.
  4. Add the new proposals to the root. Check the limit constraint, throw an exception it fails.
  5. Commit the transaction

Doing it this way you are using the databases concurrency controls. I don't think there is any other way of doing it.

This will create some contention, but you can take a few steps to minimize that. Namely make sure in Step 3 that the db column you select by has an index on it. This will cause a row lock, instead of a page lock.

If you use an Upgrade lock in step 3, this will avoid deadlocks. Basically when the second users submits a proposal for the same Aggregate Root the db will not let you read it until the first transaction has committed.

You should also consider adding a db index on Proposal.RequestForProposalId, it will help performance as that is the column the Proposal's are loaded on. I'm not 100% if it helps minimize the scope of any locks on that table, but it might ...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文