Пишу небольшой утиль для управления складом, в системе работает порядка 100 пользователей, кладовщиков, продавцов и т.д. Есть WCF сервис (InstanceContextMode = PerSession), который предоставляет функционал для типовых складских операций, внутри использует NHibernate. Возник вопрос, как сделать блокировки данных при изменении одной и той же записи на складе разными пользователями одновременно. Например кейс: есть на складе 100 полотенец, два продавца одновременно оформляют продажу один на 40, второй на 60, при оформлении продажи у сервиса вызывается метод, что-то типа такого: Sell(Sell value, params SellEntry [ ] entries); возникает вопрос, что будет происходить в NHibernate в случае такой реализации метода Sell (см. ниже). Конкретно интересует как заставить NHibernate кидать исключение и отменять транзакцию при попытке продать товара больше чем есть?
[OperationContract]
public Sell Sell(Sell value, params SellEntry [ ] entries)
{
decimal totalCost = 0.0M;
using ( ISession session = DataEngine.Factory.OpenSession ( ) )
{
using ( ITransaction transaction = session.BeginTransaction ( ) )
{
value.Saller = GetCurrentUser ( );
value.Date = DateTime.Now;
StringBuilder builder = new StringBuilder ( 128 );
foreach ( var current in entries )
builder.AppendFormat ( "{0},", current.Id );
string s = builder.ToString ( );
string text = string.Format ( "Select * from Warehouse where Warehouse.Id in (0)", s.Remove ( s.Length - 1, 1 ) );
IEnumerable<WarehouseEntry> whEntries = session.CreateSQLQuery ( text ).List<WarehouseEntry> ( );
foreach ( var current in entries )
{
foreach ( var refreshed in whEntries )
if ( current.WhEntry.Id == refreshed.Id )
{
if ( refreshed.Quantity < current.Quantity )
{
string message = string.Format ( "Невозможно продать {0} в количестве {1} штук. Остаток на складе {2} штук",
current.Product.Name, current.Quantity, refreshed.Quantity );
throw new FaultException<ArgumentException> ( new ArgumentException ( message ) );
}
current.WhEntry = refreshed;
break;
}
}
session.Save ( value );
foreach ( var current in entries )
{
current.Sale = value;
current.WhEntry.Quantity -= current.Quantity;
totalCost += current.Cost;
session.Update ( current.WhEntry );
session.Save ( current );
}
Location refreshed = session.CreateSQLQuery ( "Select * from Locations where Locations.Id = :lId" )
.AddEntity ( typeof ( Location ) )
.SetInt64 ( "lId", value.Location.Id )
.List<Location> ( )
.FirstOrDefault ( );
if ( refreshed == null )
{
throw new FaultException<InvalidOperationException> ( new InvalidOperationException (
string.Format ( "Локация {0} не является действительной", value.Location.Name ) ) );
}
refreshed.Cash += totalCost;
session.Update ( refreshed );
value.Location = refreshed;
transaction.Commit ( );
session.Flush ( );
}
}
return value;
}
[DataContract]
public class Sell
{
[DataMember]
public virtual long Id
{
get;
set;
}
[DataMember]
public virtual DateTime Date
{
get;
set;
}
[DataMember]
public virtual User Saller
{
get;
set;
}
[DataMember]
public virtual Price Price
{
get;
set;
}
[DataMember]
public virtual Location Location
{
get;
set;
}
}
[DataContract]
public class SellEntry
{
[DataMember]
public virtual long Id
{
get;
set;
}
[DataMember]
public virtual Sell Sell
{
get;
set;
}
[DataMember]
public virtual Product Product
{
get;
set;
}
[DataMember]
public virtual int Quantity
{
get;
set;
}
[DataMember]
public virtual decimal Cost
{
get;
set;
}
[DataMember]
public virtual WarehouseEntry WhEntry
{
get;
set;
}
}