动态 Linq - 在类型为 "dynamic" 的成员对象上执行查询

7
我正在尝试使用动态 Linq 查询从对象集合(Linq to Object)中检索 IEnumerable<T>,集合中的每个对象都有一个内部集合,其中包含另一组存储数据的对象,通过外部集合的索引器访问这些值。
当您使用强类型对象时,动态 Linq 查询会按预期返回过滤后的集合,但是我的对象将数据存储在 dynamic 类型的成员中,请参见以下示例:
public class Data
{
    public Data(string name, dynamic value)
    {
        this.Name = name;
        this.Value = value;
    }

    public string Name { get; set; }
    public dynamic Value { get; set; }
}

public class DataItem : IEnumerable
{
    private List<Data> _collection;

    public DataItem()
    { _collection = new List<Data>(); }

    public dynamic this[string name]
    {
        get
        {
            Data d;
            if ((d = _collection.FirstOrDefault(i => i.Name == name)) == null)
                return (null);

            return (d.Value);
        }
    }

    public void Add(Data data)
    { _collection.Add(data); }

    public IEnumerator GetEnumerator()
    {
        return _collection.GetEnumerator();
    }
}

public class Program
{
    public void Example()
    {
        List<DataItem> repository = new List<DataItem>(){
            new DataItem() {
                new Data("Name", "Mike"),
                new Data("Age", 25),
                new Data("BirthDate", new DateTime(1987, 1, 5))
            },
            new DataItem() {
                new Data("Name", "Steve"),
                new Data("Age", 30),
                new Data("BirthDate", new DateTime(1982, 1, 10))
            }
        };

        IEnumerable<DataItem> result = repository.AsQueryable<DataItem>().Where("it[\"Age\"] == 30");
        if (result.Count() == 1)
            Console.WriteLine(result.Single()["Name"]);
    }

当我运行以上示例时,我收到以下错误信息:运算符“==”与操作数类型“对象”和“Int32”不兼容dynamic成员是否与动态Linq查询不兼容?或者在处理类型为dynamic的成员时,是否有另一种构造表达式的方法可以正确评估?
非常感谢您的帮助。
3个回答

3
动态成员是否与动态Linq查询不兼容?或者在处理类型为dynamic的成员时,是否有其他构建表达式的方法可以正确评估?两者都可以一起工作。只需在比较之前进行Int32转换即可:
IEnumerable<DataItem> result = 
           repository.AsQueryable<DataItem>().Where("Int32(it[\"Age\"]) == 30");
编辑 1: 话虽如此,但是在Linq中使用动态绑定通常受到限制,因为表达式树中不允许使用动态操作。考虑以下Linq-To-Objects查询:
IEnumerable<DataItem> result = repository.AsQueryable().
                                                  Where(d => d["Age"] == 30);

由于上述原因,这个代码片段无法编译。

编辑2:在您的情况下(并结合Dynamic Linq),有一些方法可以绕过编辑1中提到的问题和原始问题中的问题。例如:

// Variant 1: Using strings all the way
public void DynamicQueryExample(string property, dynamic val)
{
   List<DataItem> repository = new List<DataItem>(){
        new DataItem() {
            new Data("Name", "Mike"),
            new Data("Age", 25),
            new Data("BirthDate", new DateTime(1987, 1, 5))
        },
        new DataItem() {
            new Data("Name", "Steve"),
            new Data("Age", 30),
            new Data("BirthDate", new DateTime(1982, 1, 10))
        }
    };

    // Use string comparison all the time        
    string predicate = "it[\"{0}\"].ToString() == \"{1}\"";
    predicate = String.Format(whereClause , property, val.ToString());

    var result = repository.AsQueryable<DataItem>().Where(predicate);
    if (result.Count() == 1)
        Console.WriteLine(result.Single()["Name"]);
}

Program p = new Program();

p.DynamicQueryExample("Age", 30); // Prints "Steve"
p.DynamicQueryExample("BirthDate", new DateTime(1982, 1, 10)); // Prints "Steve"
p.DynamicQueryExample("Name", "Mike"); // Prints "Steve" (nah, just joking...)

或者:

// Variant 2: Detecting the type at runtime.
public void DynamicQueryExample(string property, string val)
{
    List<DataItem> repository = new List<DataItem>(){
        new DataItem() {
            new Data("Name", "Mike"),
            new Data("Age", 25),
            new Data("BirthDate", new DateTime(1987, 1, 5))
        },
        new DataItem() {
            new Data("Name", "Steve"),
            new Data("Age", 30),
            new Data("BirthDate", new DateTime(1982, 1, 10))
        }
    };

    string whereClause = "{0}(it[\"{1}\"]) == {2}";


    // Discover the type at runtime (and convert accordingly)
    Type type = repository.First()[property].GetType();
    string stype = type.ToString();
    stype = stype.Substring(stype.LastIndexOf('.') + 1);

    if (type.Equals(typeof(string))) {
        // Need to surround formatting directive with ""
        whereClause = whereClause.Replace("{2}", "\"{2}\"");
    }
    string predicate = String.Format(whereClause, stype, property, val);

    var result = repository.AsQueryable<DataItem>().Where(predicate);
    if (result.Count() == 1)
        Console.WriteLine(result.Single()["Name"]);
}

var p = new Program();
p.DynamicQueryExample("Age", "30");
p.DynamicQueryExample("BirthDate", "DateTime(1982, 1, 10)");
p.DynamicQueryExample("Name", "Mike");

1
那么它们就不是真正的“兼容”的,对吧? - M.Babcock
谢谢,当我们提前知道值的运行时类型时,该解决方案运行良好,但是当查询是以编程方式构建时,我们不会真正知道如何事先转换这些值。是否有其他方式可以构建表达式? - Darsegura
@M.Babcock:这取决于你对“兼容”的定义。 - afrischke
非常感谢,两种变体似乎都很好用。这两种解决方案在性能方面有什么区别吗? - Darsegura
如果 val.ToString() 的结果是一个包含 "\\ 的字符串,会怎么样? - binki

1

你尝试过 it[\"Age\"].Equals(object(30)) 吗?

这样做:

IEnumerable<DataItem> result = 
    repository.AsQueryable<DataItem>().Where("it[\"Age\"].Equals(object(30))");

编辑:已更新为正确将30转换为对象的代码。


抛出异常:“类型为'System.Int32'的表达式不能用于方法'Boolean Equals(System.Object)'的类型为'System.Object'的参数”。但是,it [\"Age\"].Equals(object(30))可以正常工作。 - svick
谢谢,这个解决方案也很好用。这个解决方案和@afrischke提供的解决方案之间有什么性能上的考虑吗? - Darsegura

1
以下代码对您有用吗?
IEnumerable<DataItem> result = repository.AsQueryable<DataItem>().Where("it[\"Age\"].ToString() == \"30\"");

但是为了使它起作用,所有可分配给您的Data类的Value成员的类型都需要具有ToString方法的有用实现。


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