如何将一个字符串转换为 LINQ 表达式?

9
类似问题:将字符串转换为Linq.Expressions或使用字符串作为选择器? 一个类似的问题:如何将Linq表达式作为字符串传递? 另一个具有相同答案的问题:如何从C#中的字符串创建动态lambda基于Linq表达式? 提出这么多类似问题的原因:
那些类似问题中被接受的答案是不可接受的,因为它们都引用了一个四年前的库(尽管它是由代码大师Scott Gu编写的),而且只提供了一个链接作为答案。
在不包含整个库的情况下,有一种方法可以实现此操作。
以下是一些针对此情况的示例代码:
    public static void getDynamic<T>(int startingId) where T : class
    {
        string classType = typeof(T).ToString();
        string classTypeId = classType + "Id";
        using (var repo = new Repository<T>())
        {
            Build<T>(
             repo.getList(),
             b => b.classTypeId //doesn't compile, this is the heart of the issue
               //How can a string be used in this fashion to access a property in b?
            )
        }
    }

    public void Build<T>(
        List<T> items, 
        Func<T, int> value) where T : class
    {
        var Values = new List<Item>();
        Values = items.Select(f => new Item()
        {
            Id = value(f)
        }).ToList();
    }

    public class Item
    {
     public int Id { get; set; }
    }

请注意,这并不是要将整个字符串转换为表达式,例如


query = "x => x.id == somevalue";

但是它试图仅将字符串作为访问方式使用。
query = x => x.STRING;

@PaulPhillips - 反射将返回传递的类T中属性类型或属性名称。当连接到实体框架存储库时,仅通过引用传递类T作为类型。存储库(repo.getList())返回所有类T的列表。如何使用反射在linq表达式中包含类T的属性? - Travis J
你的示例看起来像是在使用 Linq-to-objects,如果是这样的话,你可以通过反射获取属性的值。但由于你正在另一个上下文中工作,我不确定它是否适用。我发布了我的尝试,以便你可以检查。 - Paul Phillips
3
如果您想在此问题上设置悬赏,请这样做。说“如此回答将被悬赏”是愚蠢的。请明确表达设置悬赏的意愿即可。 - Hogan
1
@Hogan - 抱歉,我还需要等待2天才能发布悬赏。如果可以的话,我会尽快发布的。也许这是你可以在元社区提出的问题。 - Travis J
@Travis - 好吧,我猜这并不重要,因为我已经解决了你的问题 :D 祝你愉快。 - Hogan
显示剩余4条评论
3个回答

15

这是一个表达式树的尝试。我仍然不知道它是否适用于Entity Framework,但我认为值得一试。

Func<T, int> MakeGetter<T>(string propertyName)
{
    ParameterExpression input = Expression.Parameter(typeof(T));

    var expr = Expression.Property(input, typeof(T).GetProperty(propertyName));

    return Expression.Lambda<Func<T, int>>(expr, input).Compile();  
}

这样调用:

Build<T>(repo.getList(), MakeGetter<T>(classTypeId))

如果您可以使用 Expression<Func<T,int>> 替换仅使用的 Func,那么只需删除对 Compile 的调用(并更改 MakeGetter 的签名)。
编辑: 在评论中,TravisJ 问如何像这样使用它:w => "text" + w.classTypeId 有几种方法可以做到这一点,但为了可读性,建议先引入一个本地变量,像这样:
var getId = MakeGetter<T>(classTypeId);

return w => "text" + getId(w); 

重点是getter只是一个函数,您可以像通常一样使用它。将Func<T,int>读作:int DoSomething(T instance)

我已经成功在linqPad中运行了这个代码--以下是代码 https://gist.github.com/3108507 - Hogan
@Paul - 感谢您提供的可工作代码和示例。我也能够让它正常工作。我没有更改签名,因为这会干扰到后面更多的linq。我有一个问题,如果我想以这种方式使用MakeGetterw => "text" + MakeGetter<T>(classTypeId),我需要做什么?类似于w => "text" + w.classTypeId - Travis J
@PaulPhillips - 感谢您的编辑和澄清。虽然我已经阅读了很多MSDN和网络上的文档,但我仍在逐渐熟悉委托、Funcs和表达式树。非常感谢。 - Travis J

2

这里是一个扩展方法,附带我的测试代码(linqPad):

class test
{
   public string sam { get; set; }
   public string notsam {get; set; }
}

void Main()
{
   var z = new test { sam = "sam", notsam = "alex" };

   z.Dump();

   z.GetPropertyByString("notsam").Dump();

   z.SetPropertyByString("sam","john");

   z.Dump();
}

static class Nice
{
  public static void  SetPropertyByString(this object x, string p,object value) 
  {
     x.GetType().GetProperty(p).SetValue(x,value,null);
  }

  public static object GetPropertyByString(this object x,string p)
  {
     return x.GetType().GetProperty(p).GetValue(x,null);
  }
}

结果:

结果


这在你的例子中有效,不过我没有看到与 linq 的整合。请参见 Paul 的回答以了解 linq 的整合方法。 - Travis J
我猜他的工作是基于类型,而我的工作是基于对象...各有千秋。我认为我的工作会更高效,因为不需要模板化。 - Hogan

1
我没有尝试过这个方法,也不确定它是否可行,但你可以尝试使用以下代码: b => b.GetType().GetProperty(classTypeId).GetValue(b, null);

这看起来很有趣,至少可以编译。还需要进行更多的测试。 - Travis J

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接