MemberExpression作为其类型的对象

时间:2016-05-02 08:00:44

标签: c# linq

我正在构建Linq扩展方法。

很快,我构建了一个扩展方法,以便创建MemberExpression看起来像:

public static Expression Field<T>(this object entity, string field)
{
    Type entityType = entity.GetType();
    PropertyInfo propertyInfo = entityType.GetProperty(field);
    if (propertyInfo == null)
        throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));

    ParameterExpression parameterExpression = Expression.Parameter(entityType, "e");
    return Expression.Property(parameterExpression, propertyInfo);
}

所以,我能够做到这一点:

IEnumerable<C> classes = this.backend.cs.Where(
    c => c.Field<C>("Matter").EndsWith(string.Empty)<<<<<<<< Compilation error.
);

由于MemberExpression没有EndsWith方法,我无法将此MemberExpression扩展为String属性访问权限,如:

IEnumerable<C> classes = this.backend.cs.Where(
    c => c.Matter.EndsWith(string.Empty)
);

有没有办法做到这一点。

正如你能够弄清楚我试图让事情变得更复杂一点,不过,这个例子是为了解释这种情况。

我希望它很清楚。

范围

  • 我的用户界面正在使用后端。
  • 此后端有三个实现。其中每一个都提供了Linq实现(Linq集合,NHibernate,定制的Linq提供程序)。
  • 因此,我的UI能够处理集合,数据库或从我们的服务器获取数据。

  • 我想提供像AnyField()这样的util扩展方法。

  • 所以,在挖掘了一下后,我正在考虑两种方法:
    • AnyField()生成一个表达式树,每个Linq提供程序都可以翻译它(本文的第一个答案)。
    • 为Linq Collections提供Anyfield()的默认实现,然后使用每个Linq提供程序扩展机制来处理它。或者,如果您正在构建Linq提供程序,请在实现时支持它。

5 个答案:

答案 0 :(得分:2)

好的,所以当你构建ExpressionTrees时,你会惹恼C#为你提供的语法糖

Where期望Expression<Func<TObjectType, TReturnType>> 编译的lambda; Func<TObjectType, TReturnType>

您的方法Field目前只返回无类型Expression。这意味着您的查询实际返回Expression<Func<TObjectType, Expression>>。那是不对的!它应该返回Expression<Func<TObjectType, string>>!但是我们怎么做呢?这意味着我们的方法必须返回string,但我们想要构建一个表达式树。

为了让它像你期望的那样工作,它比你想象的要困难得多,但那只是因为我们被语法糖所迷惑了。

我们真正需要做的是编写接受lambda方法的方法,然后返回 lambda方法,每个方法都重写一下。

那么......那是什么样的?

public static Expression<Func<TElementType, object>> Field<TElementType, TReturnType>(this Expression<Func<TElementType, TReturnType>> expr, string field)
{
    Type entityType = expr.Body.Type;
    PropertyInfo propertyInfo = entityType.GetProperty(field);
    if (propertyInfo == null)
        throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));
    ParameterExpression parameterExpression = Expression.Parameter(entityType, "e");

    return Expression.Lambda<Func<TElementType, object>>(
        Expression.Property(parameterExpression, propertyInfo),
        parameterExpression
    );
}

请注意,它与您编写的内容几乎完全相同,但我们用Lambda<Func<TElementType, TReturnType>>包装它。签名也有点不同。

我们希望对lambda表达式进行操作,而不是对对象进行操作。我们还返回一个lambda表达式。

那么我们如何使用它?

var classes = objects.Where(
    ExpressionExtensions.Field<Test, Test>(q => q, "Matter")
);

大!现在我们将Expression<Func<Test, string>>传递给Where,而不是Expression<Func<Test, MemberExpression>>。取得进步。

但那不会编译,这是正确的。我们正在返回一个字符串,但我们正在使用一个过滤方法,这需要一个bool。

现在让我们写一下EndsWith:

public static Expression<Func<T, bool>> EndsWith<T, TReturnType>(this Expression<Func<T, TReturnType>> expr, string str)
{
    var endsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
    var newBody = Expression.Call(expr.Body, endsWithMethod, Expression.Constant(str));
    var result = Expression.Lambda<Func<T, bool>>(newBody, expr.Parameters);
    return result;
}

使用它:

var classes = objects.Where(
    ExpressionExtensions.Field<Test, Test>(q => q, "Matter")
    .EndsWith("A")
);

现在正在编译!表达式树看起来像这样:

UserQuery+Test[].Where(e => e.Matter.EndsWith("A"))

这不是太漂亮,但Field采取了多余的lambda。让我们添加一个帮助方法,让它看起来更漂亮:

public static Expression<Func<TElementType, TElementType>> Query<TElementType>(this Expression<Func<TElementType, TElementType>> expr)
{
    return expr;
}

全部放在一起:

void Main()
{
    var objects = new[] { new Test { Matter = "A" } }.AsQueryable();
    var classes = objects.Where(
        ExpressionExtensions.Query<Test>(q => q)
        .Field("Matter")
        .EndsWith("A")
    );
    classes.Expression.Dump();

}

public class Test
{
    public string Matter { get; set;}
}

public static class ExpressionExtensions
{
    public static Expression<Func<TElementType, TElementType>> Query<TElementType>(this Expression<Func<TElementType, TElementType>> expr)
    {
        return expr;
    }

    public static Expression<Func<TElementType, object>> Field<TElementType, TReturnType>(this Expression<Func<TElementType, TReturnType>> expr, string field)
    {
        Type entityType = expr.Body.Type;
        PropertyInfo propertyInfo = entityType.GetProperty(field);
        if (propertyInfo == null)
            throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));
        ParameterExpression parameterExpression = Expression.Parameter(entityType, "e");

        return Expression.Lambda<Func<TElementType, object>>(
            Expression.Property(parameterExpression, propertyInfo),
            parameterExpression
        );
    }

    public static Expression<Func<T, bool>> EndsWith<T, TReturnType>(this Expression<Func<T, TReturnType>> expr, string str)
    {
        var endsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
        var newBody = Expression.Call(expr.Body, endsWithMethod, Expression.Constant(str));
        var result = Expression.Lambda<Func<T, bool>>(newBody, expr.Parameters);
        return result;
    }

}

答案 1 :(得分:0)

我不知道你是否弄乱了代码以显示一段代码或是否有意,但你有一个通用的扩展,想要A T但你不能使用它

无论如何,如果您想要的是一个返回属性值的方法,为什么不做一个返回T的静态异常?

public static class EntityExtension {
   public static T Field<T>(this object entity, string field) {
       Type entityType = entity.GetType();
       PropertyInfo propertyInfo = entityType.GetProperty(field);
       if (propertyInfo == null) {
           throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));
       }

       return (T)propertyInfo.GetValue(entity);
   }
}

这是一个小提琴,我已经完成了向您展示其用法,非常简单 https://dotnetfiddle.net/PoSfli

发布代码也是为了小提琴丢失:

using System;
using System.Reflection;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {

        YourClass c = new YourClass() {
            PropA = 1,
            PropB = 2,
            PropC = "ciao"
        };


        var propBValue = c.Field<int>("PropB");
        Console.WriteLine("PropB value: {0}", propBValue);

        var propCValue = c.Field<string>("PropC");
        Console.WriteLine("PropC value: {0}", propCValue);
    }
}



public static class EntityExtension {
   public static T Field<T>(this object entity, string field) {
       Type entityType = entity.GetType();
       PropertyInfo propertyInfo = entityType.GetProperty(field);
       if (propertyInfo == null) {
           throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));
       }

       return (T)propertyInfo.GetValue(entity);
   }
}

public class YourClass {
    public int PropA { get; set; }
    public int PropB { get; set; }
    public string PropC { get; set; }
}

nota你可以改进很多,使用类型扩展和属性表达式作为参数而不是字符串

答案 2 :(得分:0)

这样的事情怎么样?实际上,你的通用方法现在还没有用。


public static bool Evaluate<TField>(this object entity, string fieldName, Predicate<TField> condition)
{
    Type entityType = entity.GetType();
    PropertyInfo propertyInfo = entityType.GetProperty(field);
    if (propertyInfo == null)
        throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));

    var value = (TField)propertyInfo.GetValue(entity); //read the value and cast it to designated type, will raise invalid cast exception, if wrong
    return condition.Invoke(value); //invoke the predicate to check the condition
}

然后用法。

.Where(item => item.Evaluate<string>("Matter", prop => prop.EndsWith(string.Empty))

答案 3 :(得分:0)

如果你想使用属性名称,你也可以做一些非常简单的事情:

IEnumerable<C> classes = this.backend.cs.Where(
    c => c.Field<C>("Matter").ToString().EndsWith(string.Empty)

或者,如果您按属性类型进行过滤:

 IEnumerable<C> classes = this.backend.cs.Where(
        c => c.Field<C>("Matter").Type.ToString().EndsWith(string.Empty)

答案 4 :(得分:0)

您可以添加一个新的扩展方法,返回所需的类型。

main.html

在您的陈述中,您只需添加$(function() { $('#menu a').click(function() { $.get('option.html', function(data) { $('#form').replaceWith(data); }); }); });

body {
  padding: 0;
  margin: 0;
  overflow-y: scroll;
  font-family: Arial;
  font-size: 15px;
}
#container {
  background-color: #707070;
}
#menu {
  width: 1250px;
  margin: 0 auto;
  text-align: left;
}
#menu ul {
  list-style-type: none;
  /*Remove bullets*/
  padding: 0;
  margin: 0;
  position: relative;
}
#menu ul li {
  display: inline-block;
}
#menu ul li:hover {
  text-decoration: none;
  color: black;
}
#menu ul li a,
visited {
  color: #CCC;
  /*Color of text*/
  display: block;
  /*Takes up the full width available*/
  padding: 15px;
  text-decoration: none;
}
#menu ul li a:hover {
  color: #CCC;
  text-decoration: none;
}
#menu ul li:hover ul {
  display: block;
}
#menu ul ul li {
  display: block;
}
#menu ul ul {
  display: none;
  position: absolute;
  background-color: #707070;
  min-width: 140px;
  /*Width when hover*/
}
#menu ul ul li a:hover
/*Color of text when hover*/

{
  color: #099;
}