0 Comments

We have the following simple entities, representing a fairly basic property management system.

public class Contact
{
    public int Id { get; set; }
}

public class ContactLink
{
    public int Id { get; set; }
    public int ContactId { get; set; }
    public string Type { get; set; }
    public int LinkedId { get; set; }
}

public class Owner
{
    public int Id { get; set; }
}

public class Tenant
{
    public int Id { get; set; }
    public int? PropertyId { get; set; }
}

public class Property
{
    public int Id { get; set; }
    public int? OwnerId { get; set; }
}

Each Property has zero or one Owners, but an Owner can be linked to multiple Contacts.

A Property can be Tenanted, and each Tenant can be linked to multiple Contacts.

The ContactLink class represents multiple arbitrary links to Contacts, and is how both the Owner and Tenant to Contact relationships are specified.

This model was not created with Entity Framework in mind, which unfortunately is something that you have to deal with when working on a legacy system. The EF model was put in place after many years of the database being the closest thing the software had to a domain model. The models that I’ve listed above are obviously not complete. They contain many more fields than those displayed, and have fields for segmentation because the database is multi-tenant. Anything not specified here is not useful for the purposes of illustrating the point of post.

We need to answer a relatively simple question.

“Show me the Properties that are related to a Contact”

The goal is to answer that question while leaving as much of the work as possible up to the database. So ideally one call and minimal post processing.

Give Me All The Relationships

First things first, a test to ensure that our query works the way we expect it to. Make a database, seed it, run the query, validate the right number of results. Very straightforward.

public void WhenAContactIsRelatedToPropertiesThroughBothOwnerAndTenantRelationships_TheQueryReturnsTheCorrectNumberOfRows() { using (var db = GetDbContext()) { var contactId = RandomId(); // Seed some data into the database. We use Builder objects and extension methods on DataSets to do // seeding, allowing us to fill a previously empty database with all the information we need for a test. // Specifically for this test, seed: // 1.) 1 Contact with the specified Id. // 2.) 1 Owner. // 3.) 2 Properties. One owned by the Owner that was just created.

// 4.) 1 Tenant, for the property with no Owner.

// 5.) 2 ContactLinks, linking the Contact to the Owner and Tenant. var target = new QueryEncapsulation(); var results = target.Execute(db, contactId); results.Count().Should().Be(2); } }

With the test written, all that’s left is to implement the query itself.

public class QueryEncapsulation
{
    public IEnumerable<LinkedProperty> Execute(DbContext db)
    {
        return (from c in db.Contacts
            .Where(c => c.Id == specifiedId)
        from cl in db.ContactLinks
            .Where(cl => cl.ContactId == c.Id)
        from o in db.Owners
            .Where(o => o.Id == cl.LinkedId && cl.Type == "owner")
            .DefaultIfEmpty()
        from t in db.Tenants
            .Where(t => t.Id == cl.LinkedId && cl.Type == "tenant")
            .DefaultIfEmpty()
        from p in db.Properties
            .Where(p => p.OwnerId == o.Id || t.PropertyId == p.Id)
        select new LinkedProperty(o, t, p))
        .ToArray();
    }
}

Not super complicated, in fact it looks fairly similar to the query that you might put together in SQL. Join a bunch of tables together and then filter them as appropriate so only the valid links are included.

The usage of the DefaultIfEmptymethod is the mechanism by which you accomplish Left Outer Joins in Linq. This is important because even though each Owner and Tenant map to a single property, as a result of the way the relationships are structured, and the direction we are starting from (the Contact) each “row” returned by this query will only contain either an Owner or a Tenant, never both. Without the DefaultIfEmpty, the query wouldn’t return anything.

Of course, the test fails.

Failure Is Just A Stepping Stone To Greatness

Instead of getting 2 results (one Property linked via the Owner and one Property linked via the Creditor) the query returns 3 results.

The Property linked to the Tenant is coming back twice. Once for the correct link (to the Tenant) and then again for the Owner, which is nonsense.

As far as I understand, this combination is still included because a completely unspecified Owner can be linked to a null Owner Id on the Property.

Intuitively, p.OwnerId == o.Id feels like it should throw a NullReferenceException when o is null. I’m still not exactly sure I understand why this is a valid result, but I know if I make a similar query in SQL using Left Outer Joins, that it will work the same way. Essentially I’m not being specific enough in my exclusion criteria and null is indeed equal to null.

The fix?

Add a check on the Property condition that checks for Owner being null.

The final query we’re left with is:

public class QueryEncapsulation
{
    public IEnumerable<LinkedProperty> Execute(DbContext db)
    {
        return (from c in db.Contacts
            .Where(c => c.Id == specifiedId)
        from cl in db.ContactLinks
            .Where(cl => cl.ContactId == c.Id)
        from o in db.Owners
            .Where(o => o.Id == cl.LinkedId && cl.Type == "owner")
            .DefaultIfEmpty()
        from t in db.Tenants
            .Where(t => t.Id == cl.LinkedId && cl.Type == "tenant")
            .DefaultIfEmpty()
        from p in db.Properties
            .Where(p => (o != null && p.OwnerId == o.Id) || t.PropertyId == p.Id)
        select new LinkedProperty(o, t, p))
        .ToArray();
    }
}

Summary

Honestly, I didn’t expect this sort of behaviour from EF, but that’s not surprising. EF is an incredibly complicated beast and I’m barely familiar with the shallow end.

To try and understand the results, I had to build up the set by hand and account for all of the potential combinations, combined with whether or not it made sense for them to be included in the final results. Doing this helped to identify the fact that fields on what should be a null object created via DefaultIfEmpty seem to be nullable even though the underlying type on the model is not.

An interesting finding at least and a good example of how sometimes its important to understand your queries conceptually.

Especially if they are giving weird results.

0 Comments

As part of the work we did recently to add search to one of our newer API’s, we leveraged the support that Npgsql has for constructing PostgreSQL Full Text Search queries via Linq. This was a vast improvement over our previous attempts to use the PostgreSQL Full Text Search, because we could combine it with sorting and filtering, and have everything apply right at the database level for maximum efficiency.

Internally, we use a Retriever class for each entity that we offer access to via the API. This class in turn leverages a number of different dependencies to provide for filtering, sorting and searching by modifying an IQueryable. For search, there is an IFullTextSearcher<TEntity> interface, which contains a single method Search, taking an IQueryable and the query and returning a modified IQueryable.

A searcher implementation for an entity called Entity looks like this:

public class EntityFullTextSearcher : IFullTextSearcher<Entity>
{
    private readonly IPostgresQueryBuilder _postgresQueryBuilder;

    public EntityFullTextSearcher(IPostgresQueryBuilder postgresQueryBuilder)
    {
        _postgresQueryBuilder = postgresQueryBuilder;
    }

    public IQueryable<EntityListProjectionModel> Search(IQueryable<EntityListProjectionModel> queryable, SearchQuery searchQuery)
    {
        var query = _postgresQueryBuilder.Build(searchQuery);
        if (string.IsNullOrEmpty(query)) return queryable;

        queryable = queryable.Where(b => NpgsqlTextFunctions.Match
        (
            NpgsqlTextFunctions.QueryOr(
                NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldA), NpgsqlWeightLabel.A),
                NpgsqlTextFunctions.QueryOr(
                    NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldB), NpgsqlWeightLabel.A),
                    NpgsqlTextFunctions.QueryOr(
                        NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldC), NpgsqlWeightLabel.A),
                        NpgsqlTextFunctions.QueryOr(
                            NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldD), NpgsqlWeightLabel.B),
                            NpgsqlTextFunctions.QueryOr(
                                NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldE), NpgsqlWeightLabel.C),
                                NpgsqlTextFunctions.QueryOr(
                                    NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldF), NpgsqlWeightLabel.C),
                                    NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldG), NpgsqlWeightLabel.C)
                                    )
                                )
                            )
                        )
                    )
                )
            ,
            NpgsqlTextFunctions.ToTsQuery(query))
        );

        return queryable;
    }
}

Pretty straightforward, but there is a massive inline lambda function that is somewhat hard to read.

Its A Frowny Face

The essence of that massive chunk of lambda is the definition of the fields that will be indexed during the search, along with the weight at which they will be indexed and how they are combined, which in our case, is just using OR. This Full Text Search definition is then combined with the Match operator. This is the way to get Full Text Search working via Linq, as I wrote about in my previous post on the subject.

If you look at the definition of these functions inside the Npgsql source code, you’ll see that their implementations all throw an InvalidOperationException. How do they even work?

The answer lies in the magic of expression trees and Entity Framework. The Where function on an IQueryable actually takes an Expression<Func<TEntity, bool>>, which this lambda function is supplying.

At no point is this code intended to be executed directly. You could try (i.e. by running this on top of a List or In Memory Database via Effort), but it would fail miserably, throwing an InvalidOperationException as expected.

However, when the IQueryable is passed through the Npgsql EF Provider, it is transformed into actual SQL which is then executed and the results interpreted accordingly. To be honest, I’ve never had to really dig into the whole EF provider concepts too deeply, and I don’t really want to. I’m happy enough that it works, and allows us to combine Full Text Search with arbitrary selection, filtering and sorting, leaving all of the hard work up to the database.

Back to the massive chunk of lambda though, at least its encapsulated inside this tiny class. Right?

Well, it was up until we needed to add sorting by relevance.

Maybe More Of A Grimace?

Have a look at this class and see if anything looks familiar.

public class EntitySearchRelevanceSorter : ISearchRelevanceSorter<EntityListProjectionModel>
{
    private readonly IPostgresQueryBuilder _postgresQueryBuilder;

    public EntitySearchRelevanceSorter(IPostgresQueryBuilder postgresQueryBuilder)
    {
        _postgresQueryBuilder = postgresQueryBuilder;
    }

    public IQueryable<EntityListProjectionModel> Sort(IQueryable<EntityListProjectionModel> queryable, SearchQuery searchQuery)
    {
        if(searchQuery == null || !searchQuery.Terms.Any()) return queryable;
        var query = _postgresQueryBuilder.Build(searchQuery);
        if (string.IsNullOrEmpty(query)) return queryable;

        return queryable.OrderByDescending(b => NpgsqlTextFunctions.TsRank(
               NpgsqlTextFunctions.QueryOr(
                   NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldA), NpgsqlWeightLabel.A),
                   NpgsqlTextFunctions.QueryOr(
                       NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldB), NpgsqlWeightLabel.A),
                       NpgsqlTextFunctions.QueryOr(
                           NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldC), NpgsqlWeightLabel.A),
                           NpgsqlTextFunctions.QueryOr(
                               NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldD), NpgsqlWeightLabel.B),
                               NpgsqlTextFunctions.QueryOr(
                                   NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldE), NpgsqlWeightLabel.C),
                                   NpgsqlTextFunctions.QueryOr(
                                       NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldF), NpgsqlWeightLabel.C),
                                       NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldG), NpgsqlWeightLabel.C)
                                       ))))))
               ,
               NpgsqlTextFunctions.ToTsQuery(query))
               );
    }
}

The lambda expression being used as the input for the TsRank function is exactly the same as the function used inside the searcher class.

It should be the same as well, because the relevance sorting should return the results in an order appropriate for how they were found via the search.

Now we have a problem though, because if the search definition changes, the relevance sorting will no longer make sense. The same is somewhat true for the reverse option (changing the sort order).

My first attempt at fixing this so that there was only a single place where the Full Text Search definition existed, was to simply extract the lambda function to a class of its own, and take a dependency on that class in both the searcher and sorter.

The definition class then looked like this:

public class EntitySearchDefinition
{
    public Expression<Func<Entity, string>> Definition
    {
        get
        {
            return b => NpgsqlTextFunctions.QueryOr( 
                NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldA), NpgsqlWeightLabel.A), 
                NpgsqlTextFunctions.QueryOr( 
                    NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldB), NpgsqlWeightLabel.A), 
                    NpgsqlTextFunctions.QueryOr( 
                        NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldC), NpgsqlWeightLabel.A), 
                        NpgsqlTextFunctions.QueryOr( 
                            NpgsqlTextFunctions.SetWeightt(NpgsqlTextFunctions.ToTsVector(b.FieldD), NpgsqlWeightLabel.B), 
                            NpgsqlTextFunctions.QueryOr(
                                NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldE), NpgsqlWeightLabel.C), 
                                NpgsqlTextFunctions.QueryOr( 
                                    NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldF), NpgsqlWeightLabel.C), 
                                    NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldG), NpgsqlWeightLabel.C) 
                                )
                            )
                        )
                    )
                )
            )
        }
    }
}

And the usage within the searcher looked like this:

public class EntityFullTextSearcher : IFullTextSearcher<Entity>
{
    private readonly IPostgresQueryBuilder _postgresQueryBuilder;
    private readonly EntitySearchDefinition _def;

    public EntityFullTextSearcher(IPostgresQueryBuilder postgresQueryBuilder, EntitySearchDefinition def)
    {
        _postgresQueryBuilder = postgresQueryBuilder;
        _def = def;
    }

    public IQueryable<EntityListProjectionModel> Search(IQueryable<EntityListProjectionModel> queryable, SearchQuery searchQuery)
    {
        var query = _postgresQueryBuilder.Build(searchQuery);
        if (string.IsNullOrEmpty(query)) return queryable;

        queryable = queryable.Where(b => NpgsqlTextFunctions.Match
        (
            def.Definition,
            NpgsqlTextFunctions.ToTsQuery(query))
        );

        return queryable;
    }
}

This won’t compile at all though.

Nope, Definitely Rage

The problem here is that the definition of NpgsqlTextFunctions.Match is bool Match(string, string), so from the compilers point of view it doesn’t take an expression like the one I extracted out to the other class. We can’t just call it here either, because that would defeat the entire point of constructing the Where so that it can be turned into SQL and executed against the database. We have to put together an expression tree containing the logic we want to be pushed to the database.

How can we do that and maintain some common definition of the Full Text Search that will be used by both search and sorting components?

Long story short, my definition class ended up looking like this:

public class EntitySearchDefinition
{
    private Expression<Func<Entity, string>> Definition
    {
        get
        {
            return b => NpgsqlTextFunctions.QueryOr( 
                NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldA), NpgsqlWeightLabel.A), 
                NpgsqlTextFunctions.QueryOr( 
                    NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldB), NpgsqlWeightLabel.A), 
                    NpgsqlTextFunctions.QueryOr( 
                        NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldC), NpgsqlWeightLabel.A), 
                        NpgsqlTextFunctions.QueryOr( 
                            NpgsqlTextFunctions.SetWeightt(NpgsqlTextFunctions.ToTsVector(b.FieldD), NpgsqlWeightLabel.B), 
                            NpgsqlTextFunctions.QueryOr(
                                NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldE), NpgsqlWeightLabel.C), 
                                NpgsqlTextFunctions.QueryOr( 
                                    NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldF), NpgsqlWeightLabel.C), 
                                    NpgsqlTextFunctions.SetWeight(NpgsqlTextFunctions.ToTsVector(b.FieldG), NpgsqlWeightLabel.C) 
                                )
                            )
                        )
                    )
                )
            )
        }
    }

    private static MethodInfo TsRank()
    {
        return MethodInfoFromExpression<Func<string, string, float>>((a, b) => NpgsqlTextFunctions.TsRank(a, b));
    }

    private static MethodInfo ToTsQuery()
    {
        return MethodInfoFromExpression<Func<string, string>>(a => NpgsqlTextFunctions.ToTsQuery(a));
    }

    private MethodInfo Match()
    {
        return MethodInfoFromExpression<Func<string, string, bool>>((a, b) => NpgsqlTextFunctions.Match(a, b));
    }
    
    public Expression<Func<Entity, float>> CreateOrderBy(string query)
    {
        return MakeMethodCallExpression<float>(query, TsRank());
    }

    public Expression<Func<Entity, bool>> CreateWhere(string query)
    {
        return MakeMethodCallExpression<bool>(query, Match());
    }

    private Expression<Func<Entity, TOut>> MakeMethodCallExpression<TOut>(string query, MethodInfo methodInfo)
    {
        var p = Expression.Parameter(typeof(Entity));
        var queryExpression = CreateConstantExpressionForQuery(query);
        var definitionBody = Definition.Body;
        var replacer = new ExpressionReplacer(a => a.Exp == typeof(Entity), a => p);
        var visited = replacer.Visit<TEntity>(definitionBody);
        var expression = Expression.Lambda<Func<TEntity, TOut>>(Expression.Call(methodInfo, visited, queryExpression), new List<ParameterExpression> { p });
        return expression;
    }

    private static MethodCallExpression CreateConstantExpressionForQuery(string query)
    {
        var queryConstant = Expression.Constant(query);
        var queryExpression = Expression.Call(ToTsQuery(), queryConstant);
        return queryExpression;
    }
    
    private class ExpressionReplacer : ExpressionVisitor
    {
        private readonly Func<Expression, bool> _selector;
        private readonly Func<Expression, Expression> _replace;

        public ExpressionReplacer(Func<Expression, bool> selector, Func<Expression, Expression> replace)
        {
            _selector = selector;
            _replace = replace;
        }

        public override Expression Visit(Expression exp)
        {
            if (exp == null) return exp;
            if (_selector(exp)) exp = _replace(exp);
            return base.Visit(exp);
        }
    }
}

Yup, that’s a lot of code, but a lot of it is just utility methods for interacting with expression trees (specifically the parts that get method names without resorting to Reflection or magic strings and the expression visitor that mutates the expression tree for a purpose I’ll get to in a minute).

Using this definition class the body of the search method in EntityFullTextSearcher now looks like this:

public IQueryable<EntityListProjectionModel> Search(IQueryable<EntityListProjectionModel> queryable, SearchQuery searchQuery)
{
    var query = _postgresQueryBuilder.Build(searchQuery);
    if (string.IsNullOrEmpty(query)) return queryable;

    queryable = queryable.Where(b => NpgsqlTextFunctions.Match(def.CreateWhere(query));

    return queryable;
}

With the body of the sort method looking very very similar (calling CreateOrderBy instead).

But what does it all mean?

Expressing Myself

There are two important things to notice about the search definition class above.

The first is that it dynamically constructs the expression tree that would have previously been supplied by the inline lambda expression. You can see this construction inside the MakeMethodCallExpressionmethod. It creates a ParameterExpression (for the incoming Entity from the IQueryable), and then constructs a Call node that has the definition as one parameter, and the query as the other, just like how it was originally specified inline. This method is then used in the construction of the expression trees for Where and OrderBy.

The second important thing is the usage of the ExpressionReplacerclass. The lambda expression that contains the definition for the Full Text Search (the one returned by the Definition property) contains a parameter of type Entity. The lambda expression constructed by the CreateWhere and CreateOrderBy methods also contains a parameter of type Entity.

They are not the same thing.

In order for the inner expression parameter to be correctly bound to the same value as the outer expression parameter, it was necessary to walk the expression tree, find the inner parameter expression and replace it with the outer one. Assuming that it is unlikely there will be multiple parameter expressions throughout the tree of the same type that shouldn’t be bound to the same value, this approach will work just fine to consolidate all of the parameter expressions to the same eventual value.

The end result of the replacement is a new expression tree where all parameter expressions of type Entity are the same reference (there are only two, one inner and one outer).

Conclusion

Looking back at the effort that went into this, I’m not entirely sure it was worth it. I did accomplish my original goal (only one place where the Full Text Search definition was defined), but I’m not sure if the code is more understandable.

It felt good to separate the concerns, which makes it clear that the searcher and sorter classes are dependent on the same thing, and all of the complicated and hard to understand expression tree logic is encapsulated inside a single class, but I worry that it will be almost impenetrable to anyone who comes after me who doesn’t understand expression trees. Keep in mind, at this point, even after this entire adventure, I barely understand them. Me 3 months from now will probably look at the code, get confused, google part of it, find this blog post and then relive the entire affair.

On the upside, this did generate a significant increase in understanding the way that magic of Entity Framework works, and sparked quite a few discussions within the team that let to some interesting insights for all involved. It even spawned some other changes, where we can now use our own custom methods inside the expressions being passed into Entity Framework and then have them transformed appropriately to things that EF knows how to turn into SQL.

That’s a topic for another time though.

0 Comments

A few weeks ago I uploaded a post describing the usage of a simple BNF grammar to describe and parse the configuration for a feature in the software my team maintains. As I mentioned then, that post didn’t cover any of the detail around how the configuration worked, only how it was structured and used to construct an in memory model that could be leveraged to push all of the necessary data to the external service.

This post will instead cover the other half of the story, the content of the configuration file beyond the structure.

Charging a Dynamo

As I said in the post I linked above, we recently integrated with an external service that allowed for the creation of various forms relevant to the Real Estate industry in Australia. Their model was to use an API to programmatically construct the form and to then use their website to confirm that the form was correct (and print it or email it or whatever other usage might be desired).

For the integration, we needed to supply the ability to pick the desired form from the available one, and then supply all of the data that that form required, as sourced from our application.

The form selection was trivial, but filling in the necessary data was somewhat harder. Each form could be printed from various places within the application, so we had to put together a special intermediary domain model based on the current context. Once the model was constructed, we could easily extract the necessary fields/properties from it (depending on what was available) and then put them into a key/value store to be uploaded to the external API.

For a first version, the easiest way to approach the problem was to do it in C#. A domain model, a factory for creating it from different contexts and a simple extractor that built the key/value store.

The limitations of this approach are obvious, but the worst one is that it can’t be changed without releasing a new version of the software. We generally only do a few releases a year (for various reasons I don’t really want to get into), so this meant we would have a very limited ability to change in the face of the external service changing. New keys would be particularly brutal, because they would simply be lacking values until we did a new release, but fixing any errors in the released key mappings would also be difficult.

Our second version needed to be more flexible about how the key mappings were defined and updated, so we switched to a configuration model

Dyno-mite!

Driving the key mappings from configuration added a whole bunch of complexity to the solution.

For one, we could no longer use the infinite flexibility of C# to extract what we needed from the intermediary domain model (and format it in the way we thought was best). Instead we needed to be able to provide some sort of expression evaluation that could be easily defined within text. The intent was that we would maintain the C# code that constructed the domain model based on the context of the operation, and then use a series of key mappings created from the configuration file to extract all of the necessary data to be pushed to the external API.

The second complication was that we would no longer be the only people defining key mappings (as we were when they were written in C#). An expected outcome of the improvements was that anyone with sufficient technical knowledge would be able to edit the mappings (or provide overrides to customers as part of our support process).

At first we thought that we might be able to published a small exploration language which would allow for the definition of a simple expression to get values out of the intermediary domain model. Something similar to C# syntax (i.e. dot notation, like A.B.C). This would be relatively easy to define in text, and would be evaluatable via Reflection.

The more we looked at our original C# key mappings though, the more we realised that the simple property exploration approach would not be enough. We were selecting items from arrays, dynamically concatenating two or more properties together and formatting strings, none of which would be covered by the simple model. We were also somewhat pensive about using Reflection to evaluate the expressions. There were quite a few of them (a couple of hundred) and we knew from previous experience that Reflection could be slow. The last thing we wanted to do was make the feature slower.

Two of those problems could be solved by adding the ability to concatenate two or more expressions in the mapping definition (the <concatenated> term in the grammer defined in the previous post and by offering a format string component for the expression (which just leverages String.Format).

Accessing items out of arrays/lists was something else entirely.

Dynamic Links

Rather than try to write some code to do the value extraction ourselves, we went looking for a library to do it for us.

We located two potential candidates, Flee and System.Linq.Dynamic.

Both of these libraries offered the ability to dynamically execute C# code obtained from a string, which would let us stick with C# syntax for the configuration file. Both were also relatively easy to use and integrate.

In the end, for reasons I can no longer remember, we went with System.Linq.Dynamic. I think this might have been because we were already using it for some functionality elsewhere (dynamic filtering and sorting, before we knew how to manipulate expression trees directly), so it made sense to reuse it.

A single line in the configuration file could now look like this:

Key_Name|DynamicLinq;Owner.Contacts[0].First;”{0} “;”unknown”

This line translates to “For the key named Key_Name, use the DynamicLinq engine, where the expression is to access the first name of zeroth element of the contacts list associated with the current owner. This value should be formatted with a trailing space, and if any errors occur it should default to the string unknown”.

The beauty of this is that we don’t have to handle any of the actual expression evaluation, just the bits around the edges (formatting and error handling).

After the configuration is parsed into an Abstract Syntax Tree by Irony, that tree is then converted into a series of classes that can actually be used to obtain a key/value store from the intermediary domain model. An interface describes the commonality of the right side of the configuration line above (IEvaluationExpression) and there are implementations of this class for each of the types of expression supported by the grammar called DateExpression, LiteralExpression, ConcatenatedExpression and the one I actually want to talk about, DynamicLinqExpression.

This class is relatively simple. Its entire goal is to take a string that can be used to extract some value from an object and run it through the functionality supplied by System.Dynamic.Linq. It does some error handling and formatting as well, but its main purpose is to extract the value.

public class DynamicLinqExpression : IEvaluationExpression
{
    public DynamicLinqExpression(string expression, string default = null, string format = null)
    {
        _expression = expression;
        _default = default;
        _format = format;
    }

    public string Evaluate(Model model)
    {
        IEnumerable query;
        try
        {
            query = (new List<Model> { model }).AsQueryable<Model>().Select(_expression);
        }
        catch (Exception ex)
        {
            return _default;
        }

        List<dynamic> list;
        try
        {
            list = query.Cast<dynamic>().ToList();
        }
        catch (Exception ex)
        {
            return _default;
        }

        try
        {
            return list.Single() == null ? _default : string.Format(_format, list.Single());
        }
        catch (Exception ex)
        {
            return _default;
        }
    }
}

I’ve stripped out all of the logging and some other non-interesting pieces, so you’ll have to excuse the code. In reality we have some detailed logging that occurs at the various failure levels, which is why everything is spread out the way it is.

The important piece is the part where the incoming Model is converted into a Queryable, and System.Dynamic.Linq is used to query that using the supplied expression. A value is then extracted from the resulting queried enumerable, which is them formatted and returned as necessary.

Conclusion

Pushing off the majority of the value extraction let us focus on a nice structure to support all of the things that the configuration file needed to do that were outside of the “just extract a value from this object” scope. It also let us put more effort into the other parts of managing a configuration based approach to a complex problem, like the definition and usage of a grammar (another case of code I would rather not own if I didn’t have to).

The only weird thing left over is the fact that the DynamicLinqExpression has to do a bunch of collection transformations in order to be run on a single object. This leaves a somewhat sour taste in my mouth, but performance testing showed that it was well within the bounds that we needed to accomplish for this particular feature.

In the end, I was mostly just happy that we didn’t have to maintain some convoluted (and likely slow) Reflection based code that extracted fields and properties from an object model in some sort of vastly reduced mockery of C#.

0 Comments

Last year we built a feature that integrated with an external forms provider, extracting data from an internal data model and pushing it out to the supplied API, for use in a variety of ways (its the real estate industry, so there’s a lot of red tape and forms).

It was a fairly simple feature, because the requirements for the API were pretty simple. Select a form from a list, supply a key/value store representing the available data and then display the form and confirm it’s correct. In fact, the hardest part of the whole thing was finding a decent embedded browser to use to display the portal supplied alongside the API (which, annoyingly, is the only place where some of the functionality of the service is available).

At the time, we weren’t sure what the uptake would be for the feature (because the external service required its own subscription), so we built only exactly what was necessary, released it into the wild and waited to see what happened.

I wouldn’t say it was an amazing ground-breaking success, but reception was solid enough that the business decided to return to the feature and solidify it into something better.

Do You Speak the Language?

One of the issues with the external service is that its heavily dependent on the key/value store supplied to it for filling the forms. Unfortunately, it does not programmatically expose a list of the keys required for a form. This information is instead supplied out of band, mostly via emailing spreadsheets around.

Even if the service did expose a list of keys, it would really only be an optimisation (so we know which keys we needed to fill, rather than just all of them). We would still need to know what each key means to us, and where the value can be obtained from.

Our first version was hardcoded. We had a few classes that knew how to create an intermediate domain model specific for this feature based on where you were using it from, and they could then be easily turned into a key/value store. At the time, the limitations of this approach were understood (hard to change, must ship new code in order to deal with new fields), but it was good enough to get us started.

Now that we had clearance to return to the feature and improve it, one of the first things we needed to do was change the way that we obtain the key values. In order to support changing the way the keys were filled without having to do a new deployment we needed to move to a configuration based approach.

The best way to do that? Create a simple language to describe how the configuration should be put together and then use a parser for that language to turn the text into a data structure.

Grammar Nazi

Most of the language is pretty simple, so I’ll just write it down in EBNF notation here:

<config> ::= <line> | <line> <config>
<line> ::= <mapping> | <comment> | <blank>
<blank> ::= <EOL>
<comment> ::= "#" <text> <EOL>
<mapping> ::= <key> "|" <expression> <EOL>
<expression> ::= <dynamic> | <literal> | <date> | <concatenated>
<literal> ::= "Literal;" <text>
<date> ::= "Date;" <format_string>
<concatenated> ::= <expression> "+" <expression>
<dynamic> ::= "DynamicLinq;" <code> [ ";" <format_string> ] [";" <default> ]

Essentially, a config consists of multiple lines, where each line might be a mapping, a blank line or a comment. Each mapping must contain a key, followed by some sort of expression to get the value of the key at runtime. There are a number of different expressions available. In the above specification, any term with no expansion is free text.

With a defined grammar, all we needed was a library to parse it for us. While we could have parsed it ourselves manually, I’d much rather lean on a library that deals specifically with languages to do the work. I don’t want to own custom parsing code.

We chose Irony, which is a nice, neat little language parsing engine available for .NET.

When using Irony, you create a grammar (deriving from the Grammar class) and then fill it out with the appropriate rules. It ends up looking like this:

public class ConfigurationGrammar : Grammar
{
    public ConfigurationGrammar() 
        : base(false)
    {
        var data = new NonTerminal("data");
        var line = new NonTerminal("Line");
        var key = new FreeTextLiteral("key", FreeTextOptions.ConsumeTerminator, "|");
        var concatExpression = new NonTerminal("concatExpression");
        var singleExpression = new NonTerminal("singleExpression");
        var dateExpression = new NonTerminal("dateExpression");
        var literalExpression = new NonTerminal("literalExpression");
        var linqExpression = new NonTerminal("linqExpression");
        var sourceField = new IdentifierTerminal("sourceField", "[].");
        var formatString = new NonTerminal("formatString");
        var defaultValue = new QuotedValueLiteral("defaultValue", "\"", TypeCode.String);
        var unquotedFormatString = new QuotedValueLiteral("unquotedFormatString", "{", "}", TypeCode.String);
        var quotedFormatString = new StringLiteral("quotedFormatString", "\"");


        formatString.Rule = unquotedFormatString | quotedFormatString;
        linqExpression.Rule = ToTerm("DynamicLinq") + ";" + sourceField + ";" + formatString + ";" + defaultValue |
                              ToTerm("DynamicLinq") + ";" + sourceField + ";" + formatString |
                              ToTerm("DynamicLinq") + ";" + sourceField;
        literalExpression.Rule = ToTerm("Literal") + ";" + defaultValue;
        dateExpression.Rule = ToTerm("Date") + ";" + formatString;
        singleExpression.Rule = dateExpression | literalExpression | linqExpression;
        concatExpression.Rule = MakePlusRule(concatExpression, ToTerm("+"), singleExpression);
        line.Rule = key + concatExpression;
        data.Rule = line + Eof;

        this.Root = data;
        this.LanguageFlags |= LanguageFlags.NewLineBeforeEOF;
        this.MarkPunctuation("|", ";");
    }
}

The output from using this grammar is an Abstract Syntax Tree, which can then be converted into an appropriate data structure that does the actual work. I’ll make another post about the details of how that data structure extracts data from our domain model in the future (because this one is already long enough just considering the stuff about the language/grammar). Its relatively interesting though, because we had to move from the infinite flexibility of C# code to a more constrained set of functionality that can be represented with text (and would be usable by non-developers).

Halt! Wir Müssen Reden

Observant readers might notice that the grammar above does not line up exactly with the EBNF specification.

Originally we wrote the grammar class to line up exactly with the specification. Unfortunately, when we started actually writing the configuration file and testing various failure conditions, we discovered that if anything in the file failed, the entire parse would fail. Sure it would tell you where it failed, but we needed a little bit  more robustness than that (its not as important if one line is bad, but it is important that the rest continue to work as expected).

Our first attempt simple removed the problem line on failure and then parsed again, in a loop, until the configuration parsed correctly. This was both inefficient, and caused the line numbers reported in the error messages to be incorrect.

Finally, we decided to handle each line independently, so amended the grammar to only know about a valid line, and then ignored blank and comment lines with our own code.

I think it was a solid compromise. We’re still leveraging the grammar engine to do the heavy lifting, we just make sure it has good input to work with.

Summary

In the interests of full disclosure, I did not actually perform all of the work above. I guided the developer involved towards a language based solution, the selection of a library to do it for us and the review of the code, but that’s about it.

I think that in the end it made for a better, more maintainable solution, with the most important thing being that we don’t own the parsing code. All we own is the code that transforms the resulting syntax tree into appropriate objects. This is especially valuable when it comes to the handling of parsing errors, because parsing a perfect file is easy.

Detecting all the places where that file might be wrong, that’s the hard part, and is generally where home-baked parsing falls apart.

Like the heading puns in this post.

0 Comments

Search is one of those features that most people probably don’t think about all that much. Its ubiquitous across every facet of the internet, and is a core part of what we do every day. Its kind of just…there, and I personally can’t imagine using software without it. Well, I can imagine it, and it doesn’t look good in my head.

Our latest development efforts have been focused around putting together a services platform that we can extend moving forward, providing a semblance of cloud connected functionality to an extremely valuable data set that is currently locked in a series of on-premises databases. The initial construction of this platform is being driven by a relatively simple website for showing a subset of the entities in the system. The intent is that this will be the first step in allowing that data to be accessed outside its current prison, letting us measure interest and use those findings to drive the direction of future development.

To tie everything back in with my first paragraph, we’ve hit a point where we need to provide the ability to search.

Where Are You?

Specifically, we need to provide a nice intuitive search that doesn’t require people to have a fundamental understanding of the underlying data structures. The intent is that it will be used within a webpage initially, to help people narrow down the list of things that they are looking at. Type a few letters/partial words and have the list be automatically reduced to only those things that are relevant, ordered by how relevant they are (i.e. type in greenand 28 Green St, Indooroopilly should come up first, with 39 Smith St, Greenslopes after it, and so on).

From a webpage point of view, search looks like a very small piece, at least as far as the total percentage of the presentation it occupies. Its just a small box that you type things into, how hard could it be?

From an API point of view, search can be a project unto itself, especially when you consider weighting, ranking, what fields are searchable, and so on. That’s not even taking into account cross entity searching.

At this point our API already has partial filtering and sorting built into it, using fairly standard query string parameters (filter={key}::{value}[|{key}::{value}]+ and sort=[{direction}]key[,[{direction}]{key}]+). This allowed us to support complex interactions with lists using GET requests (which are easier to cache due to HTTP semantics), without having to resort to complex POST bodies. Its also much easier to query from the command line, which is nice and is very descriptive from a logging point of view when doing analysis on pure IIS logs.

You may be wondering what the difference is between searching and filtering. To me, its a subtle difference. Both are used to winnow down a full data set to the bits that you are interested in. Filtering is all about directly using field names and doing comparisons like that (so you know you have an Address.Suburb field, so you want to filter to only things in Forest Lake). Searching is more free form, and allows you to enter just about anything and have the service make a decision about what might be relevant. They don’t necessarily need to be separate, but in this case I think the separation of concerns has value.

To keep to our pattern, we want to add a new query string parameter called search. For our purposes, it should be fairly simple (some text, no real language specification) and should be able to be combined with our existing sorting and filtering functionality.

Simple enough conceptually.

Where In Gods Name Are You!

Inside our API we leverage Entity Framework and PostgreSQL for querying. This has worked pretty well so far, as it was simple enough to use DynamicLinq to support filtering and sorting (based on keywords we control, not on fields in the data model being returned) and have everything execute at the database for maximum efficiency.

When it comes to search, PostgreSQL exposes a series of features that allow you to do Full Text Searching, which is pretty much exactly what we want. This deals with things like partial matching, case insensitivity, weighting and ranking, which all combine to make for a nice searching experience for the user.

Combining the Full Text Search functionality with the whole IQueryable/Entity Framework insanity though, that’s where things started to get complicated.

We have used PostgreSQL’s Full Text Search functionality in the past, in a different API. At the time, we were less confident in our ability to create a nice descriptive API following HTTP semantics, so we simply did a /search endpoint that accepted POST requests with a very custom body defining the search to perform.

Under the hood, because we didn’t have any other sorting or filtering, we just constructed the SQL required to do the Full Text Search and then executed it through Entity Framework. It wasn’t the best solution, but it met our immediate needs, at least for that project.

Unfortunately, this made testing search on an In Memory Database impossible, which was annoying, but we did manage to isolate the execution of the search into a series of Search Provider classes that allowed us to abstract out this dependency and test it independently.

When it came time to incorporate search into our latest API, we looked for a better way to do it. A way that didn’t involve constructing SQL ourselves.

A Wild Commercial Library Appears

After a small amount of research, one of my colleagues found a commercial library that appeared to offer the ability to construct Full Text Search queries within Linq statements (and have them be automatically turned into SQL, as you would expect). It was a glorious day, and early experiments seemed to show that it worked just as we expected. We could include normal Where and OrderBy statements along with the Full Text Search match statements, and everything would execute at the database level. Nice and efficient.

However, when it was time to move from prototype to actual implementation, it all fell apart. Replacing our existing PostgreSQL provider was fairly painless (they provided very similar functionality), but we had problems with our database migrations, and the documentation was terrible.

We use the Code First approach for our database, so migrations are a core part of how we manage our schema. Everything worked just fine when running on top of a database that already existed (which is what we were doing in the prototype), but trying to get the new library to create a database correctly from nothing (which we do all the time in our tests) was failing miserably.

We worked through this issue with the help of the vendor (whose solution was to give us two magical lines of code that referred to the deletion strategy for the database, on static classes no less), but the whole interaction had somewhat soured us on the library.

The deal breaker came when we discovered that the licencing for the library would have been a nightmare to include into our build process. We’re so used to using open source tools (or even just tools that are licenced intelligently, with licence files or keys) that we didn’t even think of this at first. As we wanted to include the commercial library inside a Nuget package of our own, we would have needed to identify within the library all of the executables that would have ever used it. The final nail in the coffin was that we would have had to install (install!) the library onto our build agents, which to me, is a massively stupid move that just makes it harder to build software.

It Can’t Be That Hard

Investigating the way in which the library accomplished Full Text Search, we thought that maybe we could implement it ourselves. It didn’t look particularly difficult, just some methods that exist purely to be translated into SQL at a later date.

It turns out, it is actually quite hard.

Luckily, something else came to our rescue.

Old Faithful

It turned out that the library we were originally using for EF compatibility with PostgreSQL (which by the way is Npgsql, an amazing open source library), had very recently received a pull request that did exactly what we wanted, added the Full Text Search functionality into EF 6.

It turns out that Npgsql has offered the core Full Text Search functionality via code since version 3 (through the NpgsqlTsVector and NpgsqlTsQuery classes), it just wasn’t compatible with the EF/Linq way of doing things.

Unfortunately, it wasn’t all good.

The pull request had been merged, but only into the branch for the next hotfix (3.0.6), which was not available through the normal Nuget channels yet. We searched around for an unstable release (on MyGet and similar sites), and found some things, but they were really unstable, so much so that we couldn’t get anything to work properly.

While we waited for the hotfix to be officially released, we downloaded and compiled the source ourselves. After a few hiccups with dependencies and the build process, we got everything working and manually included the Npgsql binaries into our library. Obviously this is a temporary solution, while we wait for the official release, but its enough to get us moving forward for now.

This is one of the great things about open source, if this were a commercial library we would have been at the mercy of that particular organisation, and it would have blocked us from making any progress at all.

Conclusion

In the end we accomplished what we originally set as our ideal. We have incorporated Full Text Searching (with weights and ranking) into our current querying pipeline, allowing us to intelligently combine searching, filtering and sorting together and have it all executed at the database level. There is still a significant amount of work to be done to make sure that what we’ve put together is performant once we get some real traffic on it, but I think it shows promise. I do have ideas about eventually leveraging Elasticsearch to do the search (and exposing the very familiar Lucene query syntax from the API), but that’s a much larger amount of work than just leveraging an existing piece of architecture.

This was one of those pieces of functionality where it felt like we spun our wheels for a while, struggling with technical issues. If we had of compromised and put together a separate /search endpoint we could have probably re-used our old solution (constructing the SQL ourselves using helper methods, or even using the Npgsql functions that we didn’t realise existed at the time) and delivered something more quickly.

In the end though, I think it would have been a worse solution overall, compromising on the design and general cohesiveness of the API in favour of just shipping something.

That sort of thing feels good in the short term, but just builds potential pain into a profession that is already pretty painful.