Nhibernate 更改跟踪DDD中的聚合根

Nhibernate 更改跟踪DDD中的聚合根,nhibernate,domain-driven-design,Nhibernate,Domain Driven Design,这个问题主要基于这篇文章 尽管本文中的逻辑似乎合理,但我还没有找到一个涵盖所有用例的实现解决方案。 这个问题似乎与文章的下一段有关 这里有一个小问题,我们可能会在每个事务中生成几个更新,但我并不担心太多,通过跟踪实体并在当前事务中已更新的情况下不进行更新来解决这个问题相当简单,因此我将留给您 在本文之后,只要更新聚合根目录中的相关实体,就可以强制进行版本更新。但是,在聚合根和相关实体都是“脏”的情况下,这将导致对聚合根进行双重更新。这会导致nhibernate崩溃,因为默认情况下从脏聚合根目录触

这个问题主要基于这篇文章

尽管本文中的逻辑似乎合理,但我还没有找到一个涵盖所有用例的实现解决方案。 这个问题似乎与文章的下一段有关

这里有一个小问题,我们可能会在每个事务中生成几个更新,但我并不担心太多,通过跟踪实体并在当前事务中已更新的情况下不进行更新来解决这个问题相当简单,因此我将留给您

在本文之后,只要更新聚合根目录中的相关实体,就可以强制进行版本更新。但是,在聚合根和相关实体都是“脏”的情况下,这将导致对聚合根进行双重更新。这会导致nhibernate崩溃,因为默认情况下从脏聚合根目录触发的第二个版本更新期望版本与从db加载的版本相同

我尝试在“PreInserteEventListener”和“PreUpdateEventListener”中进行检查,检查更新相关实体时聚合根是否脏。如果是这种情况,则忽略版本的强制更新

public bool OnPreUpdate(PreUpdateEvent updateEvent)
{
    var rootFinder = updateEvent.Entity as ICanFindMyAggregateRoot;
    if (rootFinder == null)
        return false;

    if (!updateEvent.Session.IsAggregateRootDirty(rootFinder.MyRoot))
    {
        updateEvent.Session.Lock(rootFinder.MyRoot, LockMode.Force);
    }

    return false;
}


public static class SessionExtensions
{
        public static bool IsAggregateRootDirty(this ISession session, IAggregateRoot entity)
        {   
            ISessionImplementor sessionImplementation = session.GetSessionImplementation();
            IPersistenceContext persistenceContext = sessionImplementation.PersistenceContext;
            IEntityPersister entityPersister = sessionImplementation.GetEntityPersister(null, entity);

            EntityEntry entityEntry = persistenceContext.GetEntry(entity);

            if ((entityEntry == null) && (entity is INHibernateProxy))
            {
                INHibernateProxy proxy = entity as INHibernateProxy;
                object obj = sessionImplementation.PersistenceContext.Unproxy(proxy);
                entityEntry = sessionImplementation.PersistenceContext.GetEntry(obj);
            }

            object[] oldState = entityEntry.LoadedState;
            object[] currentState = entityPersister.GetPropertyValues(entity, sessionImplementation.EntityMode);

            int[] findDirty = entityEntry.Persister.FindDirty(currentState, oldState, entity, sessionImplementation);
            var hasDirtyCollection = currentState.OfType<IPersistentCollection>().Any(x => x.IsDirty);

            return (findDirty != null) || hasDirtyCollection;
        }
}
这个解决方案似乎确实有效,尽管我仍然需要用几个更多的用例来测试它。然而,我觉得这个解决方案似乎有点过于苛刻,我希望能找到一个更符合本文概述的解决方案。 是否有一种方法可以检测版本是否已在同一事务中更新或将要更新,或者有一种简单的方法可以跟踪事务集中的实体更新其版本


谢谢。

我认为您正在寻找乐观的并发性。请参阅。

注意,在修改子聚合时,您并不总是希望修改根的版本。假设你有一条商业规则,规定一篇文章的评论不应超过10条。添加/删除注释会影响根目录的版本,但修改注释则不会。问题是,我确实希望聚合根目录在更新子实体时滚动其版本。这就是本文的要点,根实体维护整个聚合根的版本。假设一篇文章上的评论不超过10条,那么域本身就会受到限制,因为在这种情况下,评论只能通过根添加。我想说的是,如果您一直使用根版本,即使不需要,也会损害并发性。在上面的示例中,两个用户应该能够同时修改注释,而不会遇到乐观并发异常。因此,修改注释时不应增加根版本。但是,当添加一个时,它应该是。您实现的机制必须是可配置的,以便您可以指定哪些操作应增加根版本,否则您的模型将永远无法扩展。如果要求用户能够独立修改注释,则在这种情况下,注释必须形成自己的聚合根。但这不是要求。还要注意的是,帖子评论域是为这个场合“编造”的。那么你将如何在帖子上强制执行最大评论规则呢。您必须在Post上引入一组值对象来维护该规则,并且必须使用最终一致性来保持注释聚合的同步。您的解决方案在所有情况下都必须改变根目录的版本,这一事实迫使您以不同的方式对域进行建模。然而,这可能值得付出代价,但在我看来,这是一个合理的担忧。虽然这个链接可能回答这个问题,但最好在这里包含答案的基本部分,并提供链接供参考。如果链接页面发生更改,则只有链接的答案可能无效。@StormeHowke这不是只有链接的答案;这与第一个例子中给出的其他答案中包含链接的答案几乎相同。至少这需要一个简单的解释,说明什么是乐观并发性,从链接复制/粘贴。这是一个仅链接的答案,因为如果不遵循链接,答案几乎没有价值。请参阅您链接的问题的第二个答案,以获得澄清和回答。然而,这并没有回答与本文问题中添加的引语相关的问题。示例代码已经按原样使用了乐观并发。