Practical ASP.NET

Monitor and Respond to Document Store Events in Marten

It's possible to hook into document store events to monitor (and even modify) behavior.

More on This Topic:

It’s possible to hook into document store events to monitor (and even modify) behavior.

There are a number of document store events that can be monitored:

  • Before changes are issued to the database
  • After a change has been committed to the database
  • After a document has been loaded from the database
  • After a document has been explicitly added to a session

There are also asynchronous versions of some of these methods.

Creating a Custom Listener
The primary interface that’s used to create custom listener code is Marten’s IDocumentSessionListener interface, as shown in Listing 1.

Listing 1: IDocumentSessionListener interface
public interface IDocumentSessionListener
{
  void BeforeSaveChanges(IDocumentSession session);

  Task BeforeSaveChangesAsync(IDocumentSession session, CancellationToken token);

  void AfterCommit(IDocumentSession session, IChangeSet commit);

  Task AfterCommitAsync(IDocumentSession session, IChangeSet commit, CancellationToken token);

  void DocumentLoaded(object id, object document);

  void DocumentAddedForStorage(object id, object document);
}

The code in Listing 2 shows a simple console application that adds a new Kingdom document, retrieves it in a different session and then deletes it.

Listing 2: Simple Console Application
using System;
using Marten;

namespace MartenConsoleExample
{
  class Kingdom
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public string History { get; set; }

    public override string ToString()
    {
      return $"{Name} {History}";
    }
  }

  class Program
  {
    public static DocumentStore Store { get; private set; }

    static void Main(string[] args)
    {
      CreateDocumentStore();

      int newKingdomId = InsertAKingdom();

      LoadKingdom(newKingdomId);

      DeleteKingdom(newKingdomId);

      Console.ReadLine();
    }


    private static void CreateDocumentStore()
    {
      Store = DocumentStore.For(configure =>
      {
        configure.Connection(
          "host = localhost; database = RPGDatabase; password = g7qo84nck22i; 
          username = postgres");
      });
    }

    private static int InsertAKingdom()
    {
      using (IDocumentSession session = Store.LightweightSession())
      {
        Kingdom newKingdom = new Kingdom
        {
          Name = "Island of Hrothdo're",
          History = "An island of mystery..."
        };

        session.Store(newKingdom);

        session.SaveChanges();

        return newKingdom.Id;
      }
    }

    private static void LoadKingdom(int id)
    {
      using (IQuerySession session = Store.OpenSession())
      {
        session.Load<Kingdom>(id);
      }
    }

    private static void DeleteKingdom(int id)
    {
      using (IDocumentSession session = Store.LightweightSession())
      {
        session.Delete<Kingdom>(id);

        session.SaveChanges();
      }
    }

  }
}

Rather than implement the IDocumentSessionListener interface, the helper abstract base class DocumentSessionListenerBase can be inherited from instead and selected methods overridden. Listing 3 shows a simple implementation that overrides the BeforeSaveChanges and DocumentLoaded methods.

Listing 3: CustomDocumentSessionListener
using System;
using System.Collections.Generic;
using Marten;
using Marten.Services;

namespace MartenConsoleExample
{
  class CustomDocumentSessionListener : DocumentSessionListenerBase
  {
    public override void BeforeSaveChanges(IDocumentSession session)
    {
      IUnitOfWork pendingChanges = session.PendingChanges;

      IEnumerable<Kingdom> pendingKingdomInserts = pendingChanges.InsertsFor<Kingdom>();
      IEnumerable<Kingdom> pendingKingdomUpdates = pendingChanges.UpdatesFor<Kingdom>();
      IEnumerable<IDeletion> pendingKingdomDeletes = pendingChanges.DeletionsFor<Kingdom>();

      Console.WriteLine("Pending inserts:");
      foreach (Kingdom kingdom in pendingKingdomInserts)
      {
        Console.WriteLine($" - {kingdom}");
      }

      Console.WriteLine("Pending updates:");
      foreach (Kingdom kingdom in pendingKingdomUpdates)
      {
        Console.WriteLine($" - {kingdom}");
      }

      Console.WriteLine("Pending deletes:");
      foreach (IDeletion kingdom in pendingKingdomDeletes)
      {
        Console.WriteLine($" - {kingdom}");
      }
    }

    public override void DocumentLoaded(object id, object document)
    {
      Console.WriteLine($"Document loaded: {document}");
    } 
  }
}

Configuring Document Session Listeners
To actually make use of the CustomDocumentSessionListener it must be configured in the document store. The following code shows a modified CreateDocumentStore method that adds a new instance of the custom listener:

private static void CreateDocumentStore()
{
  Store = DocumentStore.For(configure =>
  {
    configure.Connection(
      "host = localhost; database = RPGDatabase; password = g7qo84nck22i; 
      username = postgres");

    // Add listener to document store - can add multiple listeners if required
        configure.Listeners.Add(new CustomDocumentSessionListener());
  });
}

When the application is executed now, the custom code in the BeforeSaveChanges and DocumentLoaded methods will be executed as the following console output demonstrates:

Pending inserts:
 - Island of Hrothdo're An island of mystery ...
Pending updates:
Pending deletes:
Document loaded: Island of Hrothdo're An island of mystery ...
Pending inserts:
Pending updates:
Pending deletes:
 - Delete MartenConsoleExample.Kingdom with Id 35001: delete from public.mt_doc_kingdom where id = ?

To learn more about Marten, check out the documentation and the series of articles on my Don’t Code Tired blog.

About the Author

Jason Roberts is a Microsoft C# MVP with over 15 years experience. He writes a blog at http://dontcodetired.com, has produced numerous Pluralsight courses, and can be found on Twitter as @robertsjason.

comments powered by Disqus

Featured

Subscribe on YouTube