# Simple NHibernate Persistence Usage ## Prerequisites This sample requires an instance of SQL Server and a database named `Samples.NHibernate`. The database must already exist, as the endpoint creates only the tables it needs. The `Server` project connects to `localhost,1433` using SQL Server authentication. To run against SQL Server Express instead, use the `.\SqlExpress` connection string shown in the comment directly above the `connectionString` variable in `Program.cs`. ## Code walk-through This sample shows a simple client/server scenario. * `Client` sends a `StartOrder` message to `Server`. * `Server` starts an `OrderSaga`. * `OrderSaga`: * Sends a `ShipOrder` message to itself, and the handler for that message saves `OrderShipped` business data to the database. * Requests a timeout with `CompleteOrder` data. * When the `CompleteOrder` timeout fires, the `OrderSaga` publishes an `OrderCompleted` event. * `Client` handles the `OrderCompleted` event. ### NHibernate config NHibernate is configured with the right driver, dialect, and connection string. Then, since NHibernate needs a way to map the class to the database table, the configuration code does this using the `ModelMapper` API. Finally, the configuration is passed to the NServiceBus NHibernate persistence. ```cs var endpointConfiguration = new EndpointConfiguration("Samples.NHibernate.Server"); var persistence = endpointConfiguration.UsePersistence(); // for SqlExpress use Data Source=.\SqlExpress;Initial Catalog=Samples.NHibernate;Integrated Security=True;Max Pool Size=100;Encrypt=false var connectionString = @"Server=localhost,1433;Initial Catalog=Samples.NHibernate;User Id=SA;Password=yourStrong(!)Password;Max Pool Size=100;Encrypt=false"; var hibernateConfig = new Configuration(); hibernateConfig.DataBaseIntegration(x => { x.ConnectionString = connectionString; x.Dialect(); x.Driver(); }); AddMappings(hibernateConfig); persistence.UseConfiguration(hibernateConfig); ``` ### Order saga data Note that to use NHibernate's lazy-loading feature, all properties on the saga data class must be `virtual`. ```cs public class OrderSagaData : ContainSagaData { public virtual Guid OrderId { get; set; } public virtual string OrderDescription { get; set; } } ``` ### Order saga ```cs public class OrderSaga : Saga, IAmStartedByMessages, IHandleTimeouts { static ILog log = LogManager.GetLogger(); protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { mapper.ConfigureMapping(message => message.OrderId) .ToSaga(sagaData => sagaData.OrderId); } public Task Handle(StartOrder message, IMessageHandlerContext context) { Data.OrderId = message.OrderId; var orderDescription = $"The saga for order {message.OrderId}"; Data.OrderDescription = orderDescription; log.Info($"Received StartOrder message {Data.OrderId}. Starting Saga"); var shipOrder = new ShipOrder { OrderId = message.OrderId }; log.Info("Order will complete in 5 seconds"); var timeoutData = new CompleteOrder { OrderDescription = orderDescription }; return Task.WhenAll( context.SendLocal(shipOrder), RequestTimeout(context, TimeSpan.FromSeconds(5), timeoutData) ); } public Task Timeout(CompleteOrder state, IMessageHandlerContext context) { log.Info($"Saga with OrderId {Data.OrderId} completed"); var orderCompleted = new OrderCompleted { OrderId = Data.OrderId }; MarkAsComplete(); return context.Publish(orderCompleted); } } ``` ### Handler using ISession The handler uses the `ISession` instance to store business data. ```cs public class ShipOrderHandler : IHandleMessages { public Task Handle(ShipOrder message, IMessageHandlerContext context) { var session = context.SynchronizedStorageSession.Session(); var orderShipped = new OrderShipped { Id = message.OrderId, ShippingDate = DateTime.UtcNow, }; session.Save(orderShipped); return Task.CompletedTask; } } ``` ## The database Data in the database is stored in two different tables. ### The saga data * `IContainSagaData.Id` maps to the `OrderSagaData` primary key and unique identifier column `Id`. * `IContainSagaData.Originator` and `IContainSagaData.OriginalMessageId` map to columns of the same name with type `varchar(255)`. * Custom properties on `OrderSagaData`, in this case `OrderDescription` and `OrderId`, are also mapped to columns with the same name and the respective types. ![Query results for the OrderSagaData table](sagadata.png) ### The handler stored data ![Query results for the OrderShipped table](handlerdoc.png)