Модель, которую мы используем для управления сессиями NHibernate с WCF, выглядит следующим образом:
1) У нас есть собственный класс ServiceHost, который наследуется от System.ServiceModel.ServiceHost, который также реализует ICallContextInitializer. Мы добавляем экземпляр узла службы к каждой из операций в нашем сервисе следующим образом:
protected override void InitializeRuntime()
{
base.InitializeRuntime();
foreach (ChannelDispatcher cd in this.ChannelDispatchers)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
foreach (DispatchOperation op in ed.DispatchRuntime.Operations)
{
op.CallContextInitializers.Add(this);
}
}
}
}
public void AfterInvoke(object correlationState)
{
// We don't do anything after the invoke
}
public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
{
OperationContext.Current.Extensions.Add(new SessionOperationContext());
return null;
}
BeforeInvoke просто гарантирует, что OperationContext для каждого вызова WCF имеет свой собственный сеанс. Мы обнаружили проблемы с IDispatchMessageInspector, когда сеанс недоступен во время сериализации ответов - проблема, если вы используете отложенную загрузку.
2) Затем будет вызываться наш SessionOperationContext, чтобы присоединиться, и мы используем событие OperationCompleted, чтобы удалить себя. Таким образом, мы можем быть уверены, что сессия будет доступна для сериализации ответов.
public class SessionOperationContext : IExtension<OperationContext>
{
public ISession Session { get; private set; }
public static SessionOperationContext Current
{
get
{
OperationContext oc = OperationContext.Current;
if (oc == null) throw new InvalidOperationException("Must be in an operation context.");
return oc.Extensions.Find<SessionOperationContext>();
}
}
public void Attach(OperationContext owner)
{
// Create the session and do anything else you required
this.Session = ... // Whatever instantiation method you use
// Hook into the OperationCompleted event which will be raised
// after the operation has completed and the response serialised.
owner.OperationCompleted += new EventHandler(OperationCompleted);
}
void OperationCompleted(object sender, EventArgs e)
{
// Tell WCF this extension is done
((OperationContext)sender).Extensions.Remove(this);
}
public void Detach(OperationContext owner)
{
// Close our session, do any cleanup, even auto commit
// transactions if required.
this.Session.Dispose();
this.Session = null;
}
}
Мы успешно использовали вышеуказанный шаблон в приложениях с высокой нагрузкой, и, похоже, он хорошо работает.
В целом, это похоже на то, что делает новый WcfOperationSessionContext (этого не было, когда мы разобрались с шаблоном выше ;-)), но также преодолевает проблемы, связанные с отложенной загрузкой.
Относительно дополнительных вопросов: Если вы используете модель, изложенную выше, вы просто сделаете следующее:
void SaveMyEntity(MyEntity entity)
{
SessionOperationContext.Current.Session.Save(entity);
}
Вам гарантируется, что сеанс всегда присутствует и что он будет удален после завершения операции WCF. При необходимости вы можете использовать транзакции обычным способом.