Serialize expression tree

Remote.Linq might be the framework of choice, especially if not only dealing with simple predicates but more advanced queries including projections etc.


I've attempted this before. It will take some work, but you can develop your own protocol to pass predicates across a network.

First, you need to change the type of your p variable to an Expression<TDelegate> so it can be deconstructed:

Expression<Predicate<ICollection<IEntity>>> p = (entities => entities.OfType<Person>().Count() <= 3);

VisitExpression(p);

The C# compiler will see that you are assigning a lambda to an Expression<TDelegate> variable, and it will actually build an expression tree for you.

Now, you can walk the expression tree and serialize it to your custom protocol. I'll use a StringBuilder here, to create a JSON object (for easy deserialization).

StringBuilder sb = new StringBuilder();

void VisitExpression(Expression e)
{
    switch (e.ExpressionType)
    {
    case ExpressionType.And:
        return VisitBinaryExpression(e As BinaryExpression);

    ...
    }
}

void VisitBinaryExpression(BinaryExpression e)
{
    sb.AppendLine("{");
    switch (e.ExpressionType)
    {
    case ExpressionType.And:
        sb.Append("\"Type\": \"And\",");
        break;

    ...
    }
    sb.Append("\"Left\":");
    VisitExpression(e.Left); sb.Append(",");
    sb.Append("\"Right\":");
    VisitExpression(e.Right);
    sb.AppendLine("}");
}

Depending on how your distributed system handles collections and lists, you will need to implement the corresponding logic when walking the expression tree. I would start by using typeof(IEnumerable<>).MakeGenericType(typeof(IEntity)).IsAssignableFrom(typeToTest).

When serializing, you will have to send the full names of the types, methods, and overloads across the network. You'll probably want to make sure that each compute node is referencing all the same libraries, so that you can correctly resolve the types and methods when you deserialize everything.

When you finally deserialize, rebuild the expression tree on the remote host using the classes in the System.Linq.Expressions namespace. Then, compile and run the expression using Lambda.Compile().


My Solution:

After putting the issue to rest for a long time a finally managed to solve my problem using json.net and Aq.ExpressionJsonSerializer (https://github.com/aquilae/expression-json-serializer)

public class JsonNetAdapter : IOconSerializer
{
    private readonly JsonSerializerSettings _settings;

    public JsonNetAdapter(JsonSerializerSettings settings = null)
    {
        var defaultSettings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Objects};
        defaultSettings.Converters.Add(new ExpressionJsonConverter(Assembly.GetAssembly(typeof(IOconSituation))));
        _settings = settings ?? defaultSettings;
    }

    public string Serialize<T>(T obj)
    {
        return JsonConvert.SerializeObject(obj, _settings);
    }

    public T Deserialize<T>(string json)
    {
        return JsonConvert.DeserializeObject<T>(json, _settings);
    }
}

Works like a charm!