public static class DynamicExtensions
{
#region Fields
private static readonly Regex methodMatcher = new Regex(@"^Find(All)?By(\w*)$", RegexOptions.Singleline | RegexOptions.Compiled);
#endregion
#region Public Methods
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);
}
#endregion
#region Private Methods
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);
}
#endregion
#region Nested Types
private sealed class DynamicEnumerable<T> : DynamicObject
{
#region Fields
private readonly IEnumerable<T> source;
#endregion
#region Constructors
public DynamicEnumerable(IEnumerable<T> source)
{
this.source = source;
}
#endregion
#region Public Methods
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).Compile();
if (match.Groups[1].Success)
result = source.Where(predicate);
else
result = source.FirstOrDefault(predicate);
return true;
}
return base.TryInvokeMember(binder, args, out result);
}
#endregion
}
private sealed class DynamicQueryable<T> : DynamicObject
{
#region Fields
private readonly IQueryable<T> source;
#endregion
#region Constructors
public DynamicQueryable(IQueryable<T> source)
{
this.source = source;
}
#endregion
#region Public Methods
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);
}
#endregion
}
#endregion
}