Living in the Tech Avalanche Generation

A practitioner’s introspective on technology

NServiceBus - Entity Framework Saga Persister

In my last post I demonstrated a LINQ To SQL Saga Persister for NServiceBus and promised to follow it up with a similar dose of medicine - an Entity Framework Persister, specifically demonstrated for Version 1.0 of the Entity Framework.

Obviously after two previous posts on the LINQ To SQL Persister I am not going to cover the same ground in terms of explaining the various pieces of the puzzle, rather what I would like to do is simply point out some of the differences in the two implementations and note my observations.

As we did previously, let’s start out with the configuration code that uses the Entity Framework Persister.

static void Main()
{
    LogManager.GetLogger("hello").Debug("Order Started.");

    try
    {
        const string dbConnectionString =
          "metadata=..\\..\\Mapping\\OrderSagaDataModel.csdl|..\\..\\Mapping\\"+
          "OrderSagaDataModel.ssdl|..\\..\\Mapping\\OrderSagaDataModel."+
          "msl;provider=System.Data.SqlClient;provider connection "+
          "string=\"Data Source=BOOMER\\BOOM09;Initial Catalog=Sagas;"+
          "Integrated Security=True;MultipleActiveResultSets=True\"";

        var sessionFactory = EntityFrameworkSessionFactory
                             .Configure(dbConnectionString,
                                typeof(OrderSagaData), "OrderSagaDataLines");

        NServiceBus.Configure.With()
            .CastleWindsorBuilder(
                (cfg =>
                {
                    cfg.ConfigureComponent<OrderSagaFinder>
                        (ComponentCallModelEnum.Singlecall)
                        .SessionFactory = sessionFactory;
                }))
            .XmlSerializer()
            .MsmqTransport()
                .IsTransactional(true)
                .PurgeOnStartup(true)
                .IsolationLevel(IsolationLevel.RepeatableRead)
            .DbSubscriptionStorage()
                .Table("Subscriptions")
                .SubscriberEndpointColumnName("SubscriberEndpoint")
                .MessageTypeColumnName("MessageType")
            .Sagas()
            .EntityFrameworkSagaPersister<OrderSagaData>(sessionFactory)
            .UnicastBus()
                .ImpersonateSender(false)
                .LoadMessageHandlers(
                    First<GridInterceptingMessageHandler>
                        .Then<SagaMessageHandler>()
                 )
            .CreateBus()
            .Start();
    }
    catch (Exception e)
    {
        LogManager.GetLogger("hello").Fatal("Exiting", e);
    }
    Console.Read();
}

 

Kissing Cousins?

So what are the main differences between the LINQ To SQL and Entity Framework Persisters?

The Session Factory

  • Manages an ObjectContext vs. DataContext
  • Manages a string for the ObjectQuery<T>.Includes() argument to achieve eager fetching of the entire Saga’s state graph.
  • The Connection string includes the details and path to the mapping files.
  • No TextWriter for logging.
  • Requires to know the Type of Saga Entity to effect reading the mapping files meta data to ascertain the Default Container Name for the ObjectContext.

The Saga Finder

public OrderSagaData FindBy(string purchaseOrderNumber, Guid partnerId)
{
    SessionFactory.GetSession().DefaultContainerName = "SagasEntities";
    var sagaData = SessionFactory.GetSession()
        .CreateQuery<OrderSagaData>("[OrderSagaData]")
        .Where(o => o.PurchaseOrderNumber == purchaseOrderNumber &&
                    o.PartnerId == partnerId);

    foreach(var include in SessionFactory.IncludeInFetching)
    {
        sagaData = ((ObjectQuery<OrderSagaData>) sagaData).Include(include);
    }

    return sagaData.FirstOrDefault();
}

It’s almost identical to the LINQ To SQL finder with the following differences:

  • Eager Fetching via the .Include method.
  • Requires the DefaultContainerName which is available from the MetaDataWorkspace of an EntityConnection – this is specified in the mapping files.

Other Stuff

  • Isolation Level Defaults in the Entity Framework were by default Serializable and LINQ To SQL varied according the the operation in use. Setting the isolation level when configuring the bus settles this for both Persisters.
  • LINQ To SQL Persister requires cascade delete to be set in the database to clean up the entire Saga.
  • Entity Framework Version 1.0 of course does not support POCO and LINQ To SQL for the most part does.
  • I don’t like the way Entity Framework forces me to have persistence code bleed into my Saga Entities – notice how the OrderSagaData class made its way from the OrderService to the OrderService.Persistence project so it could sit alongside the Entity Data Model.

The Last Word

Just as with the LINQ To SQL Persister, this one needs to be merged via a batch file in the build folder and the subsequent single DLL is all you will need to use the this Persister.

image

I have made the entire code for the Entity Framework Saga Persister available from this link. I shall finish off the series by following up shortly by demonstrating how to use the Persister with the next version of the Entity Framework (4.0), which thankfully does support POCO.

Share/Save/Bookmark

7 Comments so far

  1. [...] the LINQ To SQL and Entity Framework Saga Persisters, that were topics of previous posts, are now available on the NServiceBus-contrib. [...]

  2. Rob Crichton February 23rd, 2010 2:18 am

    Thanks for the article. It’s been a great help.

    One question however. I am trying to setup a project where there are two or more sagas that are handled by the same bus/service. Unfortunately nServiceBus is picking up the 1st configured Persister for both sagas which obviously doesn’t function.

    My code has moved a little away from yours so I’m not 100% sure that I haven’t introduced a bug. *Have you tested your code with two or more sagas?*

    I note that there is a difference between the nHibernate version and this EF version in that the SessionFactory in NH does not need to know what aggregate root to expect. This could be what is throwing it.

    I’ll continue trying to track this problem down myself but thought it worthwhile getting the question out there.

    Thanks again, Rob

    [Reply]

  3. Simon Segal February 23rd, 2010 7:14 pm

    Rob

    I didn’t try it with two persisters. What ORM type Persister is the second one? NServiceBus will look for the first saga that handles the type of message that has been received. Also you would not want to have two sagas both handling the same type of message (if that’s what your trying to do - which may not be case).

    [Reply]

  4. Rob Crichton February 23rd, 2010 7:59 pm

    Hi Simon

    I need to have a go at creating a simple example. I am using EF for both sagas and so the config looks something like:

    var sf1 = EntitiesSessionFactory.Configure(
    “xx”,
    typeof(FirstSagaData),
    null);

    var sf2 = EntitiesSessionFactory.Configure(
    “xx”,
    typeof(SecondSagaData),
    null);

    .Sagas()
    .EntityFrameworkSagaPersister(sf1)
    .EntityFrameworkSagaPersister(sf2)

    But when persisting, “SecondSagaData” is sent to the persister that is Typed for FirstSagaData. So you get to the line:

    ctx.AddObject(typeof(T).Name, saga as T);

    … which tries the cast of saga to the wrong type.

    The sagas handle different messages but I haven’t seen how nServiceBus selects the persister based on message (or saga for that).

    Still investigating …

    I am also attempting to rearrange my entities so that they all derive from a single type. Perhaps then I will be able to use a single session factory for all sagas.

    Thanks for your response.
    Rob

    [Reply]

  5. Rob Crichton February 23rd, 2010 8:12 pm

    BTW the comment processor removed the generic types from:

    .EntityFrameworkSagaPersister<FirstSagaData>(sf1)
    .EntityFrameworkSagaPersister<SecondSagaData>(sf2)

    Hopefully they have appeared above.

    [Reply]

  6. Rob Crichton February 24th, 2010 1:22 am

    Having all the Saga entities derive from a single base type and only configuring one SessionFactory is working out for me at the moment.

    It feels slightly dirty in that I may not always want that to be the case. Perhaps I’ll get the chance to look deeper one day and work out a better solution but for now it’ll do.

    [Reply]

  7. Simon Segal February 24th, 2010 9:51 pm

    Rob

    I will also take a look at it over the coming week or so and will let you know how I get on.

    [Reply]

Leave a reply

Creative Commons Attribution-ShareAlike 2.5 Australia
Creative Commons Attribution-ShareAlike 2.5 Australia