0

我正在使用 CQRS 模式使用 NHibernate 从数据库中获取数据。

这是调用 UpdateProductHandler 的 CommittingTransactionCommandHandler

class CommittingTransactionCommandHandler
{
    private readonly ISession session;
    private readonly ICommandHandler<TCommand> _inner; //Generic Command Handlers

    public CommittingTransactionCommandHandler(ISession session)
    {
        this.session = session;
    }

    public async Task Execute(TCommand command)
    {
        using (var txn = session.BeginTransaction(IsolationLevel.Unspecified))
        {
            try
            {
                await _inner.Update(command); // This calls the UpdateProducthandler's Update method
                txn.Commit();
            }
            catch (Exception ex)
            {                   
                throw;
            }
        }
    }
}

这是更新的命令处理程序。

class UpdateProductHandler : ICommand
{
    private readonly ISession session;
    public UpdateProductHandler(ISession session)
    {
        this.session = session;
    }

    public async Task Update(int id, ProductIdentity productIdentity)
    {
        var product = session.Get(id);
        product.Update(productIdentity);
    }
}

这是 Get 的查询处理程序

class GetProductHandler
{
    private readonly ISession session;
    public GetProductHandler(ISession session)
    {
        this.session = session;
    }

    public async Task<Product> Get(int id)
    {
        var product = session.Get(id);
        if (product == null)
            throw new Exception("Entity not found");
        return Task.FromResult(product);
    }
}

这是 Product 实体的代码

class Product
{
    public virtual int Id { get; protected set; }
    public virtual string Name { get; protected set; }
    public virtual string Description { get; protected set; }
    public virtual int? Version { get; protected set; }

    public virtual void Update(ProductIdentity productIdentity)
    {
        Name = productIdentity.Name;
        Description = productIdentity.Description;
    }
}

流量是

CommittingTransactionCommandHandler是一个通用的命令处理程序。这是从 API 调用的,该 API 在内部调用UpdateProductHandler。事务在此打开并在此提交

情景是这样的

  1. 我使用GetProductHandler从数据库获取产品。(在这种情况下,产品的版本号为 10。)

  2. 我正在使用UpdateProductHandler更新产品并提交事务下的会话。(此处产品的版本号为 11)

  3. 在更新产品之后,我立即使用GetProductHandler查询同一个产品,并在 UI 的编辑模式下加载它。(但使用 GetProductHandler 获取的 Product 的版本号是 10 而不是 11。)

  4. 这是问题所在,上面的GetProductHandler不是从数据库获取最新更新,而是获取对象的先前状态。(使用版本号找到)

  5. 现在,如果我尝试更新产品,我会收到过时对象状态异常,因为版本号是 10,这不是产品的最新版本。

我已经尝试过 session.Refresh(product) 但都是徒劳的,因为它会影响其他交易。

我该如何解决这个问题?

4

1 回答 1

1

正如Ayende解释的那样,Get不会总是访问数据库。如果在一级缓存中找不到实体,它只会命中数据库。

您尚未解释如何更新实体;什么是内部product.Update方法。如果您使用不更新底层一级缓存(SQL/HQL/Linq)的 API 进行更新,那么您所说的问题是显而易见的。在这种情况下,

  • Get您使用(版本 10)在会话缓存中加载实体。这会影响数据库。
  • 然后,您调用Update(database hit) 不会更新一级缓存(版本在数据库中更改为 11;不在缓存中)。
  • 然后你Get再打电话。NHibernate 查看实体是否已经加载到会话缓存中。是的; 它是(第 10 版)。它只是返回它。没有数据库命中。

解决方案1:

不知道为什么Update需要这种方法。您可以直接更新实体,如下所示:

public async Task Update(int id, ProductIdentity productIdentity)
{
    var product = session.Get(id);
    product.Version = 11;
    //Update other properties here
    ...
    ...
    //That's it. You are already calling `Flush` somewhere somehow
}

解决方案2:

正如 Ayende 不推荐(我也不推荐),不要使用Get和编写会命中数据库的查询。请改用 Linq/HQL/SQL API。

select customer from s.Linq<Customer>()
where customer.Id = customerId
select customer
).FirstOrDefault();

每次看到这样的东西,我内心都会有些畏缩。原因很简单。这是通过主键进行查询。这里的关键词是查询。

这意味着我们必须访问数据库才能获得此查询的结果。除非您使用查询缓存(默认情况下您不会使用),否则这会强制对数据库进行查询,绕过第一级身份映射和第二级缓存。

Get 和 Load 在这里是有原因的,它们提供了一种通过主键获取实体的方法。这在几个方面很重要,最重要的是,这意味着 NHibernate 可以为此过程应用很多优化。

于 2019-07-23T15:06:44.077 回答