如何使用 NHibernate 进行增量多用户更新
我使用 nhibernate 作为我的 orm,我试图实现一些看起来简单但已经变得不必要的困难。它涉及如何实现对实体的并发更新,预计该实体将在多用户场景中由多个用户同时更新。该实体是某种具有余额的帐户。
一个示例是库存系统,需要根据进出的移动条目更新库存项目的余额。我在下面包含了以下代码的缩短版本。该代码只是保存条目并依赖 nhibernate 级联保存一直到项目。
该系统预计可以处理多个用户。这些对象是在一段时间内在 UI 中创建的。每个项目的余额将根据在 UI 中创建条目和获取项目时的现有数量进行调整。用户最终保存可能需要几分钟。
如果另一个用户对同一项目进行了其他输入,则第一个用户正在使用的余额现在无效。我知道如果尝试保存,NHibernate 将抛出陈旧数据异常。但这不是我想要的,因为并发更新是必要的。我想要的是根据条目数量的需要增加或减少该项目的当前余额。或者在更新时再次获取该项目,锁定,执行重新计算,然后最终更新,但我不知道在这种情况下如何实现这一点以及代码到底驻留在哪里。我想避免它出现在域对象中,因为控制悲观锁定等是应用程序/持久性问题。看起来这应该是一个常见的场景,但我还没有看到任何讨论这个的内容。
PS 我还担心在条目有很多行的情况下如何使用上面概述的重新获取锁定重新计算更新方法。是否会为每个项目发送单独的锁定请求,或者是否有一种方法可以一次性获取条目行的所有项目的锁定。
任何帮助将不胜感激。
class TestConcurrentUpdates
{
ISession session;
Entry entry;
ItemRepository itemRep;
EntryRepository entryRep;
void testMethod()
{
...
Entry entry = new Entry();
Item item1 = itemRepository.Get(item1ID);
Item item2 = itemRepository.Get(item1ID);
Item item3 = itemRepository.Get(item1ID);
entry.Lines.Add(new EntryLine(this, item1, 10));
entry.Lines.Add(new EntryLine(this, item2, -4));
entry.Lines.Add(new EntryLine(this, item3, 2));
using (ITransaction transaction = session.BeginTransaction())
{
entryRep.Save(entry);
transaction.Commit();
}
}
}
Item
{
string name;
double balance;
IList<EntryLine> entries = new List<EntryLine>();
public double getBalance() { return balance; }
void addEntryLine(EntryLine line)
{
entries.add(line);
balance += line.getQuantity();
}
}
class Entry
{
IList<EntryLine> lines = new List<EntryLine>();
public IList<EntryLine> Lines { get { return lines; } }
}
class EntryLine
{
Entry entry;
double quantity;
Item item;
public EntryLine(Entry entry, Item item, double quantity)
{
this.entry = entry;
this.quantity = quantity;
this.item = item;
item.addEntryLine(this);
}
public double getQuantity()
{
return quantity;
}
}
I use nhibernate as my orm and i am trying to implement something that looks straightforward but has become unneccessarily difficult. It concerns how to implement concurrent updates to an entity where it is expected that the entity will be updated simultaneously by more than one user in a multiuser scenario. The entity is an account of some kind that has a balance.
An example is an inventory system that requires updating a balance of the inventory item based on movement entries in or out. I've included below an shortened version of the code below. The code simply saves the entry and relies on nhibernate to cascade saves all the way to the items.
The system is expected to handle multiple users. The objects are created in the UI over a period of time. Each item's balance will be adjusted from the quantity on hand at the time the entry was created in the UI and the item fetched. It may be several minutes before the user finally saves.
If another user has made other enteries on the same items, the balances that the first user is working with are now invalid. I know that if a save is attempted NHibernate will throw a stale data exception. But this is not what I want since concurrent updates is a neccessity. What I want is that the current balance of the item be incremented or decremented as required by the quantity of the entry. Or that the item be fetched again at the time of update, locked, a recalculation performed and then finally updated, but I don't know how this can be achieved in this scenario and where exactly the code would reside. I would want to avoid it being in the domain object since controlling pessimistic locking etc is an application/persistence concern. It seems this should be a common scenario but I've not seen anything discussing this.
P.S. I have another concern about how the refetch-lock-recalculate-update approach I outlined above in a situation where an entry have many lines. Will seperate lock requests be sent for each item or is there a way to acquire locks for all the items of entry lines in one go.
Any help would be appreciated.
class TestConcurrentUpdates
{
ISession session;
Entry entry;
ItemRepository itemRep;
EntryRepository entryRep;
void testMethod()
{
...
Entry entry = new Entry();
Item item1 = itemRepository.Get(item1ID);
Item item2 = itemRepository.Get(item1ID);
Item item3 = itemRepository.Get(item1ID);
entry.Lines.Add(new EntryLine(this, item1, 10));
entry.Lines.Add(new EntryLine(this, item2, -4));
entry.Lines.Add(new EntryLine(this, item3, 2));
using (ITransaction transaction = session.BeginTransaction())
{
entryRep.Save(entry);
transaction.Commit();
}
}
}
Item
{
string name;
double balance;
IList<EntryLine> entries = new List<EntryLine>();
public double getBalance() { return balance; }
void addEntryLine(EntryLine line)
{
entries.add(line);
balance += line.getQuantity();
}
}
class Entry
{
IList<EntryLine> lines = new List<EntryLine>();
public IList<EntryLine> Lines { get { return lines; } }
}
class EntryLine
{
Entry entry;
double quantity;
Item item;
public EntryLine(Entry entry, Item item, double quantity)
{
this.entry = entry;
this.quantity = quantity;
this.item = item;
item.addEntryLine(this);
}
public double getQuantity()
{
return quantity;
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论