Dynamic linq: Is there a way to access object data by index?

試著忘記壹切 提交于 2019-12-01 08:10:05

I have some news for you.

It is actually possible... BUT! (Yes, there´s a but).

I assume you are using the dynamic Linq library. You must then use the "it"-keyword to be able to specify the current object to which you wish to apply your indexing operation.

However... The return datatype of your indexer is object, so you will not be able to write [0] > 100 and [1] = "wpf" because of data type mismatch.

Also, even if you derive from DynamicObject and would add the properties at runtime, those properties would not be resolved at runtime by the dynamic linq library in it´s current state. You would just get a field or property does not exist in type xxx.

There are several solutions to this, of which some you may accept as being a solution.

  • One ugly solution, if you have a limited number of datatypes, say n (where n < the number of types in .NET), you could use n indexers with parameter matching and get the datatype you want. For example if you have mostly ints and some strings:

    it[0] > 100 AND it[1, "dummy"] = "wpf" //The dummy parameter allows you to specify return type.
    
  • Another ugly solution, Dynamic Linq has support for using the ToString() and Convert-methods, so, you could for instance write the same query as above:

    Convert.ToDouble(it[0]) > 100 AND it[1].ToString() = "wpf".
    
  • A third ugly solution, you could use a convention where you wrap statements in a way which tells you how to convert the data. For example, you could write:

    it[0] > 100 AND it{1} = "wpf"
    

    And replace "it[" with "Convert.ToDouble(it[" and so on...

If I´m correct, I think that you can´t use a generic indexer with the library either. And Convert.ChangeType does you no good in this case since the return type is still object.

Maybe I or someone else will rewrite the library some time to support these kinds of things, but I have no time to do so in the near future (some weeks).

Well, I´m sorry, but I´ve got to be somewhere in 15 min, so we´ll have to take the nicer solutions later I hope!

teleporting myself to the meeting

UPDATE:

I think I may have found a solution to your (and my) problem!

In the DLINQ library, you could add a member in the IxxxSignatures interface(s) for the type for which you would want to be able to work with.

So, I added (for example) in the IEqualitySignatures:

void F(Object x, Int32 y);

And modified the (in this case) ParseComparison-method in the else block like this.

left = Expression.Convert(left, right.Type);

And, believe it or not, it worked :)

I have not tested all kinds of operations, since I have not added the signatures for the other types and operations, but it should be quite straight forward to do!

UPDATE

Updated some minor stuff above..

I´m experimenting some more on this and while it might not be the prettiest solution either, you could do something like this (DataObject is just a DynamicObject with an indexer):

    [TestMethod]
    public void DynamicTest()
    {
        List<DataObject> dataObjects = new List<DataObject>();

        dynamic firstObject = new DataObject();
        dynamic secondObject = new DataObject();

        firstObject.dblProp = 10.0;
        firstObject.intProp = 8;
        firstObject.strProp = "Hello";

        secondObject.dblProp = 8.0;
        secondObject.intProp = 8;
        secondObject.strProp = "World";

        dataObjects.Add(firstObject);
        dataObjects.Add(secondObject);

        /* Notice the different types */
        string newQuery = FormatQuery("dblProp > 9.0 AND intProp = 8 AND strProp = 'Hello'");

        var result = dataObjects.Where(newQuery);

        Assert.AreEqual(result.Count(), 1);
        Assert.AreEqual(result.First(), firstObject);
    }

And some kind of format method like (pretend I´ve written a complete method):

    public string FormatQuery(string query)
    {
        query = query.Replace('\'', '\"');

        string[] operators = new string[] { "<", ">", "!=", "<=", ">=", "<>", "=" };

        string[] parts = query.Split();

        for (int i = 0; i < parts.Length; i++)
        {
            if (operators.Contains(parts[i]))
            {
                parts[i - 1] = "it[\"" + parts[i - 1] + "\"]";
            }
        }

        return String.Join(" ", parts);
    }

We could of course have an extension-method instead.

Or... We could put the method in the DLINQ lib using something like (not saying it a good idea though):

if (typeof(T).GetInterface("IDynamicMetaObjectProvider", true) != null)
    whereClause = whereClause.FormatQuery();

And, check if the type implements a string-indexer of course, something like (ignoring IndexerName attribute here):

if (t.GetType().GetProperty("Item") != null)

which would enable "normal users" to write:

data.Where("dblProp > 9.0 AND intProp = 8 AND strProp = 'Hello'")

Well, maybe it´s not good to have that stuff in there, but you get the point. You could have! :)

You either use DynamicLinq http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx or you build your expression trees using reflection. A lot of people prefer the Dynamic Linq library for this; I've never used it myself, I've taken the reflection approach.

You just need to prepend your indexer with it which is equivalent in DynamicLinq language to this in C#.

So, you just need it[1] == "wpf".

However, there is some more complication due to the fact your indexer returns object. For class types (including string) you are fine, DLinq will promote everything as needed.

However, for value types like ints, you will have to do Int32(it[0]) > 10.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!