Rails dynamic finders for .NET 4.0

Ruby on Rails allows you to use ‘dynamic’ finders to query the database. This is actually a feature from ActiveRecord to dynamicly use methods which will represent where clauses on the database.

Some examples:

User.find(:first, :conditions => ["name = ?", name])
User.find_by_name(name)

User.find(:all, :conditions => ["city = ?", city])
User.find_all_by_city(city)

User.find(:all, :conditions => ["street = ? AND city IN (?)", street, cities])
User.find_all_by_street_and_city(street, cities)

With .NET 4.0 we have dynamics of our own so I thought why not recreate this feature…

By creating an extension method for IEnumerable and IQueryable each enumerable source can now use the dynamic finder feature

public static class DynamicExtensions
{
    public static dynamic AsDynamic<T>(this IEnumerable<T> source)
    {
        return new DynamicEnumerable<T>(source);
    }

    public static dynamic AsDynamic<T>(this IQueryable<T> source)
    {
        return new DynamicQueryable<T>(source);
    }
}

The Dynamic classes will inherit from DynamicObject to give basic dynamics support.

sealed class DynamicQueryable<T> : DynamicObject
{
    private readonly IQueryable<T> source;

    public DynamicQueryable(IQueryable<T> source)
    {
        this.source = source;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        var match = methodMatcher.Match(binder.Name);
        if (match.Success)
        {
            var properties = match.Groups[2].Value.Split(new[] { "And" }, StringSplitOptions.RemoveEmptyEntries);
            var predicate = BuildExpression<T>(properties, args);

            if (match.Groups[1].Success)
                result = source.Where(predicate);
            else
                result = source.FirstOrDefault(predicate);

            return true;
        }
        return base.TryInvokeMember(binder, args, out result);
    }
}

With the only missing part the BuildExpression method which will create the expression tree:

private static Expression<Func<T, bool>> BuildExpression<T>(string[] properties, object[] args)
{
    if (properties.Length < 1)
       throw new InvalidOperationException("Need to specify at least one property.");
    if (args.Length != properties.Length)
       throw new InvalidOperationException("Method expects " + properties.Length + " parameters and only got " + args.Length + " values.");

    var param = Expression.Parameter(typeof(T), "p");
    var body = Expression.Equal(Expression.Property(param, properties[0]), Expression.Constant(args[0]));
    for (var i = 1; i < properties.Length; i++)
        body = Expression.AndAlso(body, Expression.Equal(Expression.Property(param, properties[i]), Expression.Constant(args[i])));
    return Expression.Lambda<Func<T, bool>>(body, param);
}

And the only difference with the DynamicEnumerable class is that the expression will be compiled to use for the Where/FirstOrDefault.

Full source at pastebin.

Written Edit