Retrieving collection values ​​from expression using Lambda -.Contains

Retrieving collection values ​​from expression using Lambda -.Contains

I am trying to get all values ​​from a collection containing a member in an IQueryable. However, it seems that different collections behave differently.

Consider the following list:

 var sampleData = new List<MyClass> { new MyClass { MyMember = "a" }, new MyClass { MyMember = "b" }, new MyClass { MyMember = "c" } };
 IEnumerable<string> samplEnumerable = new List<string> { "b", "a" };
 List<string> sampleList = new List<string> { "b", "a" };
 var resultTest1 = sampleData.AsQueryable().GetCalledMemberAndValidValues(x => (new[] { "b", "a" }).Contains(x.MyMember));
 var resultTest2 = sampleData.AsQueryable().GetCalledMemberAndValidValues(x => sampleList.Contains(x.MyMember));
 var resultTest3 = sampleData.AsQueryable().GetCalledMemberAndValidValues(x => samplEnumerable.Contains(x.MyMember));

      

Extension Method

public static class ExtensionMethods
{
   public static dynamic GetCalledMemberAndValidValues<T>(this IQueryable<T> query, Expression<Func<T, bool>> predicate)
    {
      string parameterName = string.Empty;
      ArrayList values = new ArrayList();
  MethodCallExpression node = (MethodCallExpression)predicate.Body;

  if (predicate.Body.NodeType == ExpressionType.Call)
  {
    if ((node.Method.Name == "Contains"))
    {
      Type valueType = null;
      foreach (var obj in node.Arguments)
      {
        if (obj.NodeType == ExpressionType.MemberAccess && ((MemberExpression)obj).Expression.NodeType == ExpressionType.Parameter)
        {
          parameterName = ((MemberExpression)obj).Member.Name;
        }

        // The below block is valid for array[];
        if (obj.NodeType == ExpressionType.NewArrayInit)
        {
          values.AddRange(((NewArrayExpression)obj).Expressions);
        }

        // The below block is valid for IEnumerable<T>;
        if (obj.NodeType == ExpressionType.MemberAccess && ((MemberExpression)obj).Expression.NodeType == ExpressionType.Constant)
        {
          var value = (((MemberExpression) obj).Member as FieldInfo).GetValue(((obj as MemberExpression).Expression as ConstantExpression).Value);

          values.AddRange((ICollection)value);
        }
      }

      // The below block is valid for List<T>;
      if ((predicate.Body as MethodCallExpression).Object != null)
      {
        var obj = (MemberExpression)(predicate.Body as MethodCallExpression).Object;
        var value = (obj.Member as FieldInfo).GetValue((obj.Expression as ConstantExpression).Value);
        values.AddRange((ICollection)value);
      }
    }
  }

  return new { parameterName, values };
}
}

      

An object

MethodCallExpression

stores the property name MyMember

in the field Arguments

and the value is stored in the field Object

.

MethodCallExpression

the object stores both the property name MyMember

and its value in the field Arguments

.

As you can see, all three examples give the same result.

However, I am not happy that I deal with each collection type differently.

How can I generalize this and get the property name and value from the same field MethodCallExpression

?

+3


source to share


1 answer


This will be done:

public static dynamic GetCalledMemberAndValidValues<T>(this IQueryable<T> query, Expression<Func<T, bool>> predicate)
{
    var methodCall = predicate.Body as MethodCallExpression;
    Expression collectionExpression = null;
    MemberExpression memberExpression = null;
    if (methodCall != null && methodCall.Method.Name == "Contains")
    {
        if (methodCall.Method.DeclaringType == typeof(Enumerable))
        {
            collectionExpression = methodCall.Arguments[0];
            memberExpression = methodCall.Arguments[1] as MemberExpression;
        } else {
            collectionExpression = methodCall.Object;
            memberExpression = methodCall.Arguments[0] as MemberExpression;
        }
    }

    if (collectionExpression != null && memberExpression != null)
    {
        var lambda = Expression.Lambda<Func<object>>(collectionExpression, new ParameterExpression[0]);
        var value = lambda.Compile()();
        return new { parameterName = memberExpression.Member.Name, values = value };
    } 

    return null;
}

      

You cannot make it completely generic. These two methods:
(new[] { "b", "a" }).Contains


samplEnumerable.Contains

They actually call Enumerable.Contains(source, x.Member)

.



sampleList.Contains(

calls sampleList.Contains(x.Member)

.

Pay attention to the number and order of the arguments. So when they look the same when you write your code, you are actually referring to different methods. This will check which method the method contains and then calculates which argument is a collection and which argument is a member expression.

+1


source







All Articles