Search code examples
c#database-schemamarten

Marten: Define schema stuff (like indexes etc) not in the constructor/factory call to create the DocumentStore


I just started testing Marten (2.9), and so far I am loving it. However, I am not sure I am following the DocumentStore.For method. For example, in my "dbhandler" for Marten, I can write:

    public MartenDbHandler()
    {
        store = DocumentStore.For(_ =>
        {
            _.AutoCreateSchemaObjects = AutoCreate.CreateOrUpdate;
            _.Connection("host=localhost;database=marten;password=root;username=postgres");
            _.Schema.For<Customer>().Index(x => x.PopulationRegistryNumber);
        });
    }

but naturally, I do not want to have all that schema code when I initialize the database and supply the connection string.

So I thought, maybe I can pass on the store variable, and do the same, but then the For thing doesn't exist:

enter image description here

... and I haven't really found a way to set the Schema in any other way.

What I really want to do is to have an Interface, that is dynamically loaded and executed (via Reflection) when I start my application, that handles those things, like an IMartenMetaData that looks something like:

public interface IMartenMetaData
{
    SetMetaData(DocumentStore store);
}

and then implement the schema things in that/those classes, but that doesn't work because I can't use the DocumentStore to set the meta.


Solution

  • I managed to do a much nice approach to keep it more dynamic and not all in the construction of DocumentStore.

    Please see code below. The idea is straightforward:

    1. Create the StoreOptions separately

    2. Before creation of the DocumentStore, run method that via Reflection finds all classes of a certain Type that will add table meta data

    3. Create the DocumentStore

       public MartenDbHandler()
       {
           StoreOptions so = new StoreOptions();
           so.Connection("host=localhost;database=marten;password=root;username=postgres");
           so.AutoCreateSchemaObjects = AutoCreate.CreateOrUpdate;
           SetTableMeta(so);
           store = new DocumentStore(so);
       }
      
       private void SetTableMeta(StoreOptions storeOptions)
       {
           // We get the current assembly through the current class
           var currentAssembly = Assembly.GetExecutingAssembly();
           // we filter the defined classes according to the interfaces they implement
           var stuff = currentAssembly.DefinedTypes.Where(type => type.IsSubclassOf(typeof(MartenTableMetaDataBase))).ToList();
      
           foreach (Type type in stuff)
           {
               IMartenTableMetaData temp = (IMartenTableMetaData)Activator.CreateInstance(type);
               temp.SetTableMetaData(storeOptions);
           }
           OnLogEvent?.Invoke(this, $"{stuff.Count} table meta data initialized");
       }
      

    The IMartenTableMetaData is a base class for the IMartenTableMetaData interface. In the example below, the base class isn't used, but I normally find it good to have a base class (I use a similar approach to another ORM, where I actually use the base class). But, the base class can of course be removed if you have no use for it.

    internal abstract class MartenTableMetaDataBase : IMartenTableMetaData
    {
        public void SetTableMetaData(StoreOptions storeOptions)
        {
            SetSpecificTableMetaData(storeOptions);
        }
    
        protected abstract void SetSpecificTableMetaData(StoreOptions storeOptions);
    }
    

    and the interface:

    public interface IMartenTableMetaData
    {
        void SetTableMetaData(StoreOptions storeOptions);
    }
    

    So, I can now create a class for each Type I want to add meta data too, like this:

    internal class MartenTableMetaDataCustomer : MartenTableMetaDataBase
    {
        protected override void SetSpecificTableMetaData(StoreOptions storeOptions)
        {
            storeOptions.Schema.For<Customer>().Index(x => x.Muni);
        }
    }
    

    or

    internal class MartenTableMetaDataDriver : MartenTableMetaDataBase
    {
        protected override void SetSpecificTableMetaData(StoreOptions storeOptions)
        {
            storeOptions.Schema.For<Driver>().Index(x => x.Username);
        }
    }
    

    etc.

    This will keep the Marten DB handler clean and meta data separated into specific classes for readability, clarity and all that stuff =)