Fork me on GitHub

Extending Marten's Linq Support Edit on GitHub


Marten allows you to add Linq parsing and querying support for your own custom methods. Using the (admittedly contrived) example from Marten's tests, say that you want to reuse a small part of a Where() clause across different queries for "IsBlue()." First, write the method you want to be recognized by Marten's Linq support:


public class IsBlue : IMethodCallParser
{
    private static readonly PropertyInfo _property = ReflectionHelper.GetProperty<ColorTarget>(x => x.Color);

    public bool Matches(MethodCallExpression expression)
    {
        return expression.Method.Name == nameof(CustomExtensions.IsBlue);
    }

    public IWhereFragment Parse(IQueryableDocument mapping, ISerializer serializer, MethodCallExpression expression)
    {
        var locator = mapping.FieldFor(new MemberInfo[] {_property}).SqlLocator;

        return new WhereFragment($"{locator} = 'Blue'");
    }
}

Note a couple things here:

  1. If you're only using the method for Linq queries, it technically doesn't have to be implemented and never actually runs
  2. The methods do not have to be extension methods, but we're guessing that will be the most common usage of this

Now, to create a custom Linq parser for the IsBlue() method, you need to create a custom implementation of the IMethodCallParser interface shown below:


public interface IMethodCallParser
{
    bool Matches(MethodCallExpression expression);

    IWhereFragment Parse(IQueryableDocument mapping, ISerializer serializer, MethodCallExpression expression);
}


The IMethodCallParser interface needs to match on method expressions that it could parse, and be able to turn the Linq expression into part of a Postgresql "where" clause. The custom Linq parser for IsBlue() is shown below:


public static bool IsBlue(this ColorTarget target)
{
    return target.Color == "Blue";
}

Lastly, to plug in our new parser, we can add that to the StoreOptions object that we use to bootstrap a new DocumentStore as shown below:


[Fact]
public void query_with_custom_parser()
{
    using (var store = DocumentStore.For(_ =>
    {
        _.Connection(ConnectionSource.ConnectionString);

        // IsBlue is a custom parser I used for testing this
        _.Linq.MethodCallParsers.Add(new IsBlue());
        _.AutoCreateSchemaObjects = AutoCreate.All;

        // This is just to isolate the test
        _.DatabaseSchemaName = "isblue";
    }))
    {

        store.Advanced.Clean.CompletelyRemoveAll();


        var targets = new List<ColorTarget>();
        for (var i = 0; i < 25; i++)
        {
            targets.Add(new ColorTarget {Color = "Blue"});
            targets.Add(new ColorTarget {Color = "Green"});
            targets.Add(new ColorTarget {Color = "Red"});
        }

        var count = targets.Where(x => x.IsBlue()).Count();

        targets.Each(x => x.Id = Guid.NewGuid());

        store.BulkInsert(targets.ToArray());

        using (var session = store.QuerySession())
        {
            session.Query<ColorTarget>().Count(x => CustomExtensions.IsBlue(x))
                .ShouldBe(count);
        }
    }
}