В WCF службах, как и в любых других приложениях где есть БД и ORM становится вопрос об управлении жизненным циклом объектов и транзакциями. Для собственных служб мне хотелось реализовать схему как в веб приложениях, т.е. :
- ISession живет один запрос.
- Транзация открывается в начале запроса, и закрывается в конце.
- В случае наличия ошибки транзакция откатывается, в обратном подтверждается.
Реализовать это можно при помощи IDispatchMessageInspector. Данный интерфейс позволяет обработать события начала и окончания запроса. Итак реализация:
public class TransactionManager : IDispatchMessageInspector { public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext) { SessionStorage.OpenSession(); SessionStorage.CurrentSession.BeginTransaction(IsolationLevel.ReadCommitted); return null; } public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState) { var session = SessionStorage.CurrentSession; if (session.Transaction.IsActive) { if (reply.IsFault) { session.Transaction.Rollback(); } else { session.Transaction.Commit(); } } } }
Чтобы зарегистрировать TransactionManager нам потребуется еще два вспомогательных класса. 1й добавит TransactionManager к службе:
public class TransactionBehaviour : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { throw new Exception("Behavior not supported on the consumer side!"); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { TransactionManager inspector = new TransactionManager(); endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector); } public void Validate(ServiceEndpoint endpoint) { } }
Второй будет использоваться для регистрации секции в конфигурационном файле:
public class TransactionBehaviorExtensionElement : BehaviorExtensionElement { protected override object CreateBehavior() { return new TransactionBehaviour(); } public override Type BehaviorType { get { return typeof(TransactionBehaviour); } } }
И последний штрих – настройка самого сервиса где все это собирается воедино:
<system.serviceModel> <services> <service name="WcfTransactions.TestService"> <endpoint behaviorConfiguration="transactionsEnabledBehaviour" address="" binding="basicHttpBinding" contract="WcfTransactions.ITestService"/> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> <endpointBehaviors> <behavior name="transactionsEnabledBehaviour"> <transactionBehaviour /> </behavior> </endpointBehaviors> </behaviors> <extensions> <behaviorExtensions> <add name="transactionBehaviour" type="WcfTransactions.Transactions.TransactionBehaviorExtensionElement, WcfTransactions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </behaviorExtensions> </extensions> </system.serviceModel>
Тут есть маленькая особенность, когда мы регистрируем behaviorExtension обязательно указывать полный путь к типу, с указанием версии, культуры и токена сборки.
Как по мне это слишком сложно и слишком много всего приходится делать. В Asp.net mvc есть ActionFilters которые позволяют сделать все тоже самое, но с помощью всего одного атрибута на контроллере. Подобных вещей для WCF я не нашел, буду рад если кто-то подскажет.
Разобраться в этом помогут только исходники :).