Archive for the ‘Active Record’ Category.

ActiveRecord Flush Control

There are a lot of times in your application where you will load a lot of entities in a single session (to display a lot of data on a page, build a report, etc).  Since you are not planning on doing a mutable operation on an entity, there is a big saving that you can get from not flushing the session.  To get an idea on everything that happens when you do a flush, check out the AbstractionFlushingEventListener from the NHibernate source.

If you are using explicit SessionScope’s, not flushing the session is trivial:

   1: using (new SessionScope(FlushAction.Never))
   2: {
   3:     repository.DoSomeReadOperation();
   4: }

This will execute the operation without flushing the session at the end.

Another way to control this behavior is by using an interceptor.  For example, if you are using the Castle’s Automated Transaction Facility, you probably already have a way of determining whether or not your methods do mutable operations by their transaction attributes.  You can then use an interceptor to control flushing:

   1: public class UnitOfWorkInterceptor : IInterceptor
   2: {
   3:     private readonly ITransactionManager transactionManager;
   4:
   5:     /// <summary>
   6:     /// Initializes a new instance of the <see cref="UnitOfWorkInterceptor"/> class.
   7:     /// </summary>
   8:     /// <param name="transactionManager">The transaction manager.</param>
   9:     public UnitOfWorkInterceptor(ITransactionManager transactionManager)
  10:     {
  11:         this.transactionManager = transactionManager;
  12:     }
  13:
  14:     #region Implementation of IInterceptor
  15:
  16:     /// <summary>
  17:     /// Intercepts the specified invocation.
  18:     /// </summary>
  19:     /// <param name="invocation">The invocation.</param>
  20:     public void Intercept(IInvocation invocation)
  21:     {
  22:         ITransaction transaction = transactionManager.CurrentTransaction;
  23:         FlushAction flushAction = transaction == null ? FlushAction.Never : FlushAction.Config;
  24:
  25:         ActiveRecordUnitOfWork.Before(flushAction);
  26:         invocation.Proceed();
  27:         ActiveRecordUnitOfWork.After();
  28:     }
  29:
  30:     #endregion
  31: }

Note that in this example, interceptor ordering is important.  If you register the unit of work interceptor before the transaction interceptor in the ATM facility, then transactionManager.CurrentTransaction will always return null.  (The ActiveRecordUnitOfWork class in the above example is just a facade for controlling the session in a thread local as discussed in this post http://erichauser.net/2008/08/06/activerecord-session-scope-and-wcf-redux/).  If you do not want to tie your flushing to transactions, then you can always create your own metadata and read that metadata at runtime in the interceptor.  The ATM facility code is a great example of how to inspect metadata on initialization and use that metadata in your interceptor.

Hopefully, this little trick will some increased performance for your queries that return a lot of results.

ActiveRecord Session Scope and WCF Redux

Awhile ago, I posted a solution I was using for a project for managing SessionScope with WCF using an ICallContextInitializer. Because of another issue in the codebase (an error in a custom listener), an exception was getting thrown when session.Dispose() was called in the AfterInvoke method.  What was interesting was that it appeared to the client that the WCF call had completed successfully — even though this was not the case.  It turns out that ICallContextInitializer is not the best WCF extension to use to manage the scope. Here’s a quick console application that will show the behavior of throwing an exception in AfterInvoke. Notice how the console window will crash before the ReadKey is hit:  

 

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         using (var host = new ServiceHost(typeof(Operation)))
   6:         {
   7:             var endpointAddress = new EndpointAddress("net.tcp://localhost/optest");
   8:             var endpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(IOperation)),
   9:                                                new NetTcpBinding(),
  10:                                                endpointAddress);
  11:             endpoint.Behaviors.Add(new ThrowBehavior());
  12:             host.Description.Endpoints.Add(endpoint);
  13:             host.Open();
  14:  
  15:             var op = ChannelFactory<IOperation>.CreateChannel(new NetTcpBinding(), endpointAddress);
  16:             op.Op();
  17:         }
  18:  
  19:         Console.ReadKey();
  20:     }
  21: }
  22:  
  23:
 [ServiceContract]
  24: public interface IOperation
  25: {
  26:     [OperationContract]
  27:     void Op();
  28: }
  29:  
  30: public class Operation : IOperation
  31: {
  32:     public void Op()
  33:     {
  34:     } 
  35: }
  36:  
  37: internal class ThrowBehavior : IEndpointBehavior
  38: {
  39:     public void Validate(ServiceEndpoint endpoint)
  40:     {
  41:     }
  42:  
  43:     public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
  44:     {
  45:     }
  46:  
  47:     public void Apply
DispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  48:     {
  49:         foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
  50:         {
  51:             operation.CallContextInitializers.Add(new ThrowCallContextInitializer());
  52:         }
  53:     }
  54:  
  55:     public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
  56:     {
  57:     }
  58: }
  59:  
  60: internal class ThrowCallContextInitializer : ICallContextInitializer
  61: {
  62:     public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
  63:     {
  64:         return null;
  65:     }
  66:  
  67:     public void AfterInvoke(object correlationState)
  68:     {
  69:         throw new InvalidOperationException();

  70:     }
  71: }

 

My revised code uses a DispatchMessageInterceptor instead:

 

   1: public class ARSessionScopeBehavior : IEndpointBehavior
   2: {
   3:     
   4:     /// <summary>
   5:     /// Implement to pass data at runtime to bindings to support custom behavior.
   6:     /// </summary>
   7:     /// <param name="endpoint">The endpoint to modify.</param>
   8:     /// <param name="bindingParameters">The objects that binding elements require to support the behavior.</param>
   9:     public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
  10:     {
  11:     }
  12:  
  13:     /// <summary>
  14:     /// Implements a modification or extension of the service across an endpoint.
  15:     /// </summary>
  16:     /// <param name="endpoint">The endpoint that exposes the contract.</param>
  17:     /// <param name="endpointDispatcher">The endpoint dispatcher to be modified or extended.</param>
  18:     public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  19:     {
  20:         endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new ARDispatcherMessageInspector());
  21:     }
  22:  
  23:     /// <summary>
  24:     /// Implements a modification or extension of the client across an endpoint.
  25:     /// </summary>
  26:     /// <param name="endpoint">The endpoint that is to be customized.</param>
  27:     /// <param name="clientRuntime">The client runtime to be customized.</param>
  28:     public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
  29:     {
  30:     }
  31:  
  32:     /// <summary>
  33:     /// Implement to confirm that the endpoint meets some intended criteria.
  34:     /// </summary>
  35:     /// <param name="endpoint">The endpoint to validate.</param>
  36:     public void Validate(ServiceEndpoint endpoint)
  37:     {
  38:     }
  39:  
  40: }

 

   1: public class ARDispatchMessageInspector : IDispatchMessageInspector
   2:  {
   3:  
   4:      private static readonly ILogger Logger = LogManager.GetLogger(ARDispatchMessageInspector);
   5:  
   6:      private const string ActiveRecordSessionScopeKey = "wcf.ar.sessionscope";
   7:  
   8:      #region Implementation of IDispatchMessageInspector
   9:  
  10:      /// <summary>
  11:      /// Called after an inbound message has been received but before the message is dispatched to the intended operation.
  12:      /// </summary>
  13:      /// <returns>
  14:      /// The object used to correlate state. This object is passed back in the <see cref="M:System.ServiceModel.Dispatcher.IDispatchMessageInspector.BeforeSendReply(System.ServiceModel.Channels.Message@,System.Object)" /> method.
  15:      /// </returns>
  16:      /// <param name="request">The request message.</param>
  17:      /// <param name="channel">The incoming channel.</param>
  18:      /// <param name="instanceContext">The current service instance.</param>
  19:      public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
  20:      {
  21:          var scope = Local.Data[ActiveRecordSessionScopeKey] as SessionScope;
  22:          if (scope == null)
  23:          {
  24:              Logger.Debug("Creating new ActiveRecord SessonScope");
  25:              scope = new SessionScope();
  26:              Local.Data[ActiveRecordSessionScopeKey] = scope;
  27:          }
  28:          return null;
  29:      }
  30:  
  31:      /// <summary>
  32:      /// Called after the operation has returned but before the reply message is sent.
  33:      /// </summary>
  34:      /// <param name="reply">The reply message. This value is null if the operation is one way.</param>
  35:      /// <param name="correlationState">The correlation object returned from the <see cref="M:System.ServiceModel.Dispatcher.IDispatchMessageInspector.AfterReceiveRequest(System.ServiceModel.Channels.Message@,System.ServiceModel.IClientChannel,System.ServiceModel.InstanceContext)" /> method.</param>
  36:      public void BeforeSendReply(ref Message reply, object correlationState)
  37:      {
  38:          var scope = Local.Data[ActiveRecordSessionScopeKey] as SessionScope;
  39:          if (scope != null)
  40:          {
  41:              if (SessionScope.Current == scope)
  42:              {
  43:                  Logger.Debug("Disposing ActiveRecord SessonScope");
  44:                  scope.Dispose();
  45:              }
  46:              else
  47:              {
  48:                  Logger.Warn(
  49:                      "The current Active Record session scope does not equal the correlated session scope from WCF.  This scope will not " +
  50:                      "be disposed, however, this could lead to entities not being properly flushed.");
  51:              }
  52:              Local.Data[ActiveRecordSessionScopeKey] = null;
  53:          }
  54:      }

 

So far, no problems.  Funny enough, when searching for solutions to this problem, I noticed some other posts where people recommended using the ICallContextInitializer to manage unit of work.  I assume they just have not had any issues when calling Dispose() and have not seen this behavior.

Advanced ActiveRecord Models – Part 2

In part 1, I discussed what Castle ActiveRecord does when starting up and ways you can modify the model after is has been generated.  Another useful trick is to add custom attributes to your model that can be inspected while ActiveRecord is inspecting its model.  To this you need to create a class that implements Castle.ActiveRecord.Framework.Internal.IModelBuilderExtension and register it with the ActiveRecordStarter.

 

For my example, let’s assume that we want to implement soft deletes using ActiveRecord.  The NHibernate FAQ has a great blog entry on how to make NHibernate mark a entity as deleted as opposed to deleting it from the database.  Assuming you are using their model of marking your entities as ISoftDeleteable, here are two things we can do to extend this model:

 

  1. If session.Get(id) is called on soft deleted record, null is returned.
  2. If a collection contains a record that has been soft deleted, that record will not show up when the collection is loaded.

 

I am not entirely sure that these two features are something that you want to necessarily do, however, they are good examples for what I am trying to show.  The reason I say that is because I am not sure you want entities showing up sometimes and not other times, i.e. a soft deleted model would show up for a custom query unless someone checks the Deleted flag, how do soft deletes work across entity graphs, should we allow both ends of a bidirectional relationship to be soft deleteable, etc.  So, I have kept this example limited too two easy features, but remember to think about this a little more before actually implementing it.  So on to the example, the first thing we need to is to add a custom model builder to inspect our model:

 

   1: public class SoftDeleteModelBuilderExtension : IModelBuilderExtension
   2: {
   3:  
   4:     public static IDictionary<Type, SoftDeleteModel> SoftDeleteModels = new Dictionary<Type, SoftDeleteModel>();
   5:  
   6:     #region IModelBuilderExtension Members
   7:  
   8:     public void ProcessBelongsTo(System.Reflection.PropertyInfo pi, BelongsToModel belongsToModel, ActiveRecordModel model)
   9:     {
  10:     }
  11:  
  12:     public void ProcessClass(Type type, ActiveRecordModel model)
  13:     {
  14:         GetModel(type, model);
  15:     }
  16:  
  17:     public void ProcessField(System.Reflection.FieldInfo fi, ActiveRecordModel model)
  18:     {
  19:     }
  20:  
  21:     public void ProcessHasAndBelongsToMany(System.Reflection.PropertyInfo pi, HasAndBelongsToManyModel hasAndBelongManyModel, ActiveRecordModel model)
  22:     {
  23:         if (!IsSoftModel(hasAndBelongManyModel.HasManyAtt.MapType)) return;
  24:         var softDeleteModel = GetModel(model.Type, model);
  25:         softDeleteModel.HasAndBelongsToMany.Add(hasAndBelongManyModel);
  26:     }
  27:  
  28:     public void ProcessHasMany(System.Reflection.PropertyInfo pi, HasManyModel hasManyModel, ActiveRecordModel model)
  29:     {
  30:         if (!IsSoftModel(hasManyModel.HasManyAtt.MapType)) return;
  31:         var softDeleteModel = GetModel(model.Type, model);
  32:         softDeleteModel.HasMany.Add(hasManyModel);
  33:     }
  34:  
  35:     public void ProcessHasManyToAny(System.Reflection.PropertyInfo pi, HasManyToAnyModel hasManyModel, ActiveRecordModel model)
  36:     {
  37:         if (!IsSoftModel(hasManyModel.HasManyToAnyAtt.MapType)) return;
  38:         var softDeleteModel = GetModel(model.Type, model);
  39:         softDeleteModel.ManyToAny.Add(hasManyModel);
  40:     }
  41:  
  42:     public void ProcessProperty(System.Reflection.PropertyInfo pi, ActiveRecordModel model)
  43:     {
  44:     }
  45:  
  46:     private static bool IsSoftModel(Type type)
  47:     {
  48:         return (typeof(ISoftDeleteable).IsAssignableFrom(type));
  49:     }
  50:  
  51:     private static SoftDeleteModel GetModel(Type type, ActiveRecordModel model)
  52:     {
  53:         SoftDeleteModel softDeleteModel;
  54:         SoftDeleteModels.TryGetValue(type, out softDeleteModel);
  55:  
  56:         if (softDeleteModel == null)
  57:         {
  58:             softDeleteModel = new SoftDeleteModel {Type = type, ActiveRecordModel = model};
  59:             if (IsSoftModel(type))
  60:                 softDeleteModel.SoftDeleteable = true;
  61:             SoftDeleteModels[type] = softDeleteModel;
  62:         }
  63:         return softDeleteModel;
  64:     }
  65:  
  66:     #endregion
  67: }

 

   1: public class SoftDeleteModel
   2: {
   3:     /// <summary>
   4:     /// Gets or sets the type.
   5:     /// </summary>
   6:     /// <value>The type.</value>
   7:     public Type Type { get; set; }
   8:  
   9:     /// <summary>
  10:     /// Gets or sets a value indicating whether the model is soft deleteable.
  11:     /// </summary>
  12:     /// <value><c>true</c> if soft deleteable; otherwise, <c>false</c>.</value>
  13:     public bool SoftDeleteable { get; set; }
  14:  
  15:     /// <summary>
  16:     /// Gets or sets the active record model for this type.
  17:     /// </summary>
  18:     /// <value>The active record model.</value>
  19:     public ActiveRecordModel ActiveRecordModel { get; set; }
  20:  
  21:     private readonly HashSet<HasManyModel> _hasMany = new HashSet<HasManyModel>();
  22:  
  23:     /// <summary>
  24:     /// Gets the has many relationships to a soft deleted type.
  25:     /// </summary>
  26:     /// <value>The has many.</value>
  27:     public HashSet<HasManyModel> HasMany
  28:     {
  29:         get { return _hasMany; }
  30:     }
  31:  
  32:     private readonly HashSet<HasAndBelongsToManyModel> _hasAndBelongsToMany = new HashSet<HasAndBelongsToManyModel>();
  33:  
  34:     /// <summary>
  35:     /// Gets the has and belongs to many relationships to a soft deleted type.
  36:     /// </summary>
  37:     /// <value>The has and belongs to many.</value>
  38:     public HashSet<HasAndBelongsToManyModel> HasAndBelongsToMany
  39:     {
  40:         get { return _hasAndBelongsToMany; }
  41:     }
  42:  
  43:     private readonly HashSet<HasManyToAnyModel> _manyToAny = new HashSet<HasManyToAnyModel>();
  44:  
  45:     /// <summary>
  46:     /// Gets the many to any relations that belong to a soft deleted type.
  47:     /// </summary>
  48:     /// <value>The many to any.</value>
  49:     public HashSet<HasManyToAnyModel> ManyToAny
  50:     {
  51:         get { return _manyToAny; }
  52:     }
  53: }


As you can see, as the models as being inspected, we are building up a model of class that are soft deleted and classes that have collections of classes that are soft deleted.  To add our custom model inspector to ActiveRecord simply do the following line before calling ActiveRecordStarter.Initialize():

 

   1: ActiveRecordStarter.RegisterExtension(new SoftDeleteModelBuilderExtension());

 

Now, that we have our soft delete model we need to remove any of the collections that are mapped to ISoftDeleteable entities.  We are going to add these collections back later though.  To remove these collections, we need to write a model visitor that I discussed in Part 1:

 

   1: public class SoftRelationshipsRemovalVisitor : AbstractDepthFirstVisitor
   2: {
   3:     private readonly HashSet<ActiveRecordModel> _visited = new HashSet<ActiveRecordModel>();
   4:  
   5:     /// <summary>
   6:     /// Visits the model.
   7:     /// </summary>
   8:     /// <param name="model">The model.</param>
   9:     public override void VisitModel(ActiveRecordModel model)
  10:     {
  11:         if (!_visited.Contains(model))
  12:             _visited.Add(model);
  13:         else
  14:             return;
  15:  
  16:         if (SoftDeleteModelBuilderExtension.SoftDeleteModels.ContainsKey(model.Type))
  17:         {
  18:             var softDeleteModel = SoftDeleteModelBuilderExtension.SoftDeleteModels[model.Type];
  19:  
  20:             foreach (var hasMany in softDeleteModel.HasMany)
  21:                 model.HasMany.Remove(hasMany);
  22:  
  23:             foreach (var hasAndBelongsToManyModel in softDeleteModel.HasAndBelongsToMany)
  24:                 model.HasAndBelongsToMany.Remove(hasAndBelongsToManyModel);
  25:  
  26:             foreach (var manyToAny in softDeleteModel.ManyToAny)
  27:                 model.HasManyToAny.Remove(manyToAny);
  28:         }
  29:     }
  30: }

 

And to register the visitor:

 

   1: ModelsDelegate validated = null;
   2: validated = delegate(ActiveRecordModelCollection models, IConfigurationSource source)
   3: {
   4:     ActiveRecordStarter.ModelsValidated -= validated;
   5:     foreach (ActiveRecordModel model in models)
   6:     {
   7:         model.Accept(new SoftRelationshipsRemovalVisitor());
   8:     }
   9: };
  10: ActiveRecordStarter.ModelsValidated += validated;

 

Now when our entities are generated by ActiveRecord, they will no longer have mapped collections that go to ISoftDeleteable entities.  Now that they are gone what next?  In Part 3,  I will examine how to add back the collections we removed so that they take into account the soft deleted entities. At the end of this series, I will post the full source code for the solution.

Advanced ActiveRecord Models – Part 1

Before you can do anything exciting, it is important to understand what exactly Castle ActiveRecord does when starting up. Specifically, what happens when you can ActiveRecordStarter#Initialize():

 

  1. Inspects supplied types or assemblies looking for ActiveRecord attributes.
  2. Creates an in memory model of the attributes (Castle.ActiveRecord.Framework.Internal.ActiveRecordModelBuilder)
  3. Uses the visitor pattern to connect the model for things like many-to-many relationships (Castle.ActiveRecord.Framework.Internal.GraphConnectorVisitor)
  4. Verifies that the model is valid (Castle.ActiveRecord.Framework.Internal.SemanticVerifierVisitor)
  5. Generates NHibernate XML mappings for the model (Castle.ActiveRecord.Framework.Internal.XmlGenerationVisitor)
  6. Initializes NHibernate with the generated mappings

 

ActiveRecordStarter provides a few events that you can attach to modify the model, ModelsCreated and ModelsValidated (I originally found out about this extension point when reviewing the code for Ayende’s Rhino Security).  What are some of the things you can do with these events?  The project I am working on uses SQL Server schemas to separate different groups of tables. So all of our table mappings look like: [ActiveRecord("`TableName`", "`SchemaName`", Lazy = True)].   We have a number of unit tests for our database classes that we want to run with SQLite so they can run on our build server with no external dependencies. SQLite does not support schemas though, so we can use ActiveRecordStarter to modify our model:

 

   1: AppSettingsSection nhibernateSection = ConfigurationManager.GetSection("nhibernate") as AppSettingsSection;
   2: if (nhibernateSection != null && nhibernateSection.Settings["dialect"].Equals("NHibernate.Dialect.SQLiteDialect"))
   3: {
   4:     ModelsDelegate validated = null;
   5:     validated = delegate(ActiveRecordModelCollection models, IConfigurationSource source)
   6:                     {
   7:                         ActiveRecordStarter.ModelsValidated -= validated;
   8:                         foreach (ActiveRecordModel model in models)
   9:                         {
  10:                             model.Accept(new ChangeSchemaVisitor());
  11:                         }
  12:                     };
  13:     ActiveRecordStarter.ModelsValidated += validated;
  14: }
  15: ActiveRecordStarter.Initialize();
  16:  
  17: ...
  18:  
  19: internal class ChangeSchemaVisitor : AbstractDepthFirstVisitor
  20: {
  21:     public override void VisitModel(ActiveRecordModel model)
  22:     {
  23:         var schema = model.ActiveRecordAtt.Schema;
  24:         model.ActiveRecordAtt.Table = schema + "_" + model.ActiveRecordAtt.Table;
  25:         model.ActiveRecordAtt.Schema = null;
  26:         base.VisitModel(model);
  27:     }
  28:  
  29:     public override void VisitHasAndBelongsToMany(HasAndBelongsToManyModel model)
  30:     {
  31:         var schema = model.HasManyAtt.Schema;
  32:         model.HasManyAtt.Table = schema + "_" + model.HasManyAtt.Table;
  33:         model.HasManyAtt.Schema = null;
  34:     }
  35: }

 

This is just a simple example, but there are a lot of problems you can solve be using the model visitors. Rhino Security uses this pattern to add caching to all the mappings, add schemas to models, and replaces interfaces in the model with actual entities.

Castle ActiveRecord SessionScope and WCF

If you are familiar with NHibernate flushing semantics, then you know you usually want to treat a group of database operations as a single unit of work.  For one reason, flushing is expensive because it requires checking all of your entities for changes to see if they should be persisted to the database.  Second, you often want to treat all of your database operations as an atomic transaction.  Third, any entities that have lazy loaded properties can take advantage of the existing session.  ActiveRecord provides a nice abstraction for treating multiple database operations as a unit of work called SessionScope.  For instance, if all of your database operations are grouped together in one location, you would use SessionScope like:

 

var person = ...
var child = ...
using (SessionScope scope = new SessionScope)
{
  person.Save();
  child.Save();
}

In this case the neither the person or child is saved to the database until the Dispose() is called on the SessionScope.  For  the simple scenarios, this works fine, but in most cases you are doing your database operations in a DAO or Repository class — so you do not know how the database operations are going to be used or what other operations are going to be used with them.

If you are using ActiveRecord with MonoRail or another MVC framework, odds are you are using the built in SessionScopeWebModule to manage the SessionScope for you.  The web module begins a session when an HTTP request begins and ends it when the request has been processed.  Internally, the module uses HttpContext.Current.Items to store the current SessionScope in a thread local while the request is being processed.  If you are hosting your WCF services under IIS, you can take advantage of the web module to manage your SessionScope for you. 

If you are hosting not hosting your WCF services under IIS, then you need another mechanism for managing the SessionScope.  Fortunately, WCF provides a hook for just this type of scenario, ICallContextInitializer.  The WCF examples taking about using this extension point for managing thread context culture or impersonation scenarios.  First you need to implement ICallContextInitializer (I have removed documentation and logging for brevity, but I would recommend putting logging statements in so you can see what is going on at runtime):

    public class ARSessionScopeCallContextlInitializer : ICallContextInitializer
    {

        private const string ActiveRecordSessionScopeKey = "wcf.ar.sessionscope";

        public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
        {
            SessionScope scope = Local.Data[ActiveRecordSessionScopeKey] as SessionScope;
            if (scope == null)
            {
                Logger.Debug("Creating new ActiveRecord SessonScope");
                scope = new SessionScope();
                Local.Data[ActiveRecordSessionScopeKey] = scope;
            }
            return scope;
        }

        public void AfterInvoke(object correlationState)
        {
            SessionScope scope = Local.Data[ActiveRecordSessionScopeKey] as SessionScope;
            if (scope != null)
            {
                scope.Dispose();
            }
        }

    }

 

You can find the Local.Data class in Ayende’s rhino-commons library, but it just a simple wrapper around thread local storage.  Next, you need to implement IEndpointBehavior to add the initializer to your service:

    public class ARSessionScopeBehavior : IEndpointBehavior
    {

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
            {
                 operation.CallContextInitializers.Add(new ARSessionScopeContextCallInitializer());
            }
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }

    }

And finally, an example for configuring your service:

            var uri = new Uri("net.tcp://localhost/TestSessionScope");
            var host = new WindsorServiceHost(typeof(AuthorizationService));
            var endpointAddress = new EndpointAddress(uri);
            var endpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof (ITestService)), new NetTcpBinding(), endpointAddress);
            endpoint.Behaviors.Add(new ARSessionScopeBehavior());
            _host.Description.Endpoints.Add(endpoint);
            _host.Open();

 

And that’s it.  If you want to be more explcit about your flushing behavior — for instance creating a ReadOnlySessionScope that never flushes for read only operations, you can modify the EndpointBehavior to work declaritively with attributes so that you could mark individual WCF methods with configuration information such as [ReadOnly].  I have read some discussions about poor performance do to flushing of read only operations, so this might be worth looking into if you are doing a lot of read only operations on a large object graph.