Search code examples
c#regexrecursionlambda

Create Recursive Regex.IsMatch Expression<Func<T, bool>> for nested property of different Type


I have a method for creating a Regex based Expression which works fine for direct properties of the relevant entity. So the below work fine for anything like entity.property

public static Expression<Func<T, bool>> CreateRegExExpression<T>(string pattern, string property)
{
        var paramObject = Expression.Parameter(typeof(T));
        var paramType = Expression.TypeAs(paramObject, typeof(T));
        var propertyField = Expression.Property(paramType, property);

        var _pattern = Expression.Constant(pattern, typeof(string));
        var paramsEx = new Expression[] { propertyField, _pattern };

        var methodInfo = typeof(Regex).GetMethod("IsMatch", new Type[] { typeof(string), typeof(string) });
        if (!methodInfo.IsStatic || methodInfo == null)
            throw new NotSupportedException();
        
        var lamdaBody = Expression.Call(null, methodInfo, paramsEx);
        return Expression.Lambda<Func<T, bool>>(lamdaBody, paramObject);
}

However, I have not been able to make this work for a nested property, such as entity.sub_entity.property as the call to CreateRegExExpression requires the <T> class to appropriately return the lamba?

Has anyone got any tips on how to construct the lambda for a nested property dynamically as the below works fine, but I can't seem to work out how to make that recursive using the above function.

Expression<Func<entity, bool>> lambda = e => Regex.Ismatch(e.sub_entity.property, pattern)

Solution

  • If I understand your problem correctly you can allow passing the "path" instead of just property name and then split and iterate it:

    static Expression<Func<T, bool>> CreateRegExExpression<T>(string pattern, string property)
    {
        var paramObject = Expression.Parameter(typeof(T));
        var paramType = Expression.TypeAs(paramObject, typeof(T));
        var props = property.Split(".");
        Expression propertyField = Expression.Property(paramType, props[0]);
    
        foreach (var prop in props.Skip(1))
        {
            propertyField = Expression.Property(propertyField, prop);
        }
    
        var _pattern = Expression.Constant(pattern, typeof(string));
        var paramsEx = new Expression[] { propertyField, _pattern };
    
        var methodInfo = typeof(Regex).GetMethod("IsMatch", new Type[] { typeof(string), typeof(string) });
        if (!methodInfo.IsStatic || methodInfo == null)
            throw new NotSupportedException();
            
        var lamdaBody = Expression.Call(null, methodInfo, paramsEx);
        return Expression.Lambda<Func<T, bool>>(lamdaBody, paramObject);
    }
    

    With usage like CreateRegExExpression(pattern, "sub_entity.property")

    Alternatively if you don't have full path, only "previous" property expression you can use reflection to invoke the method:

    var memberReflectedType = previousPropertyField.Member.ReflectedType;
    var propFieldType = previousPropertyField.Type;
    var makeGenericMethod = typeof(CreateRegExExpressionHolder).GetMethod(nameof(CreateRegExExpression), BindingFlags.Public | BindingFlags.Static)
        .MakeGenericMethod(propFieldType);
    var invoke = makeGenericMethod.Invoke(null, new[] { pattern, finalPropName }) as Expression;