Dynamic Linq - Perform a query on an object with members of type "dynamic"
Asked Answered
N

3

7

I am trying to use a dynamic linq query to retrieve an IEnumerable<T> from an object collection (Linq to Object), each of the objects in the collection have an internal collection with another set of objects where the data is stored, these values are accessed through an indexer from the outer collection

The dynamic linq query returns the filtered set as expected when you are working with strongly typed objects but my object stores the data in a member of type dynamic, please see the example below:

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"]);
    }

When I run the above example I get: Operator '==' incompatible with operand types 'Object' and 'Int32'

Are dynamic members incompatible with Dynamic Linq queries?, or is there another way of constructing expressions that would evaluate properly when dealing with members of type dynamic

Thanks a lot for your help.

Neysa answered 12/1, 2012 at 2:4 Comment(0)
C
3

Are dynamic members incompatible with Dynamic Linq queries?, or is there another way of constructing expressions that would evaluate properly when dealing with members of type dynamic?

Both can work together. Just do a conversion to Int32 before doing the comparison like so:

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

Edit 1: Having said that, the use of dynamic binding in connection with Linq is restricted in general as dynamic operations are not allowed in expression trees. Consider the following Linq-To-Objects query:

IEnumerable<DataItem> result = repository.AsQueryable().
                                                  Where(d => d["Age"] == 30);

This code snippet won't compile for the reason mentioned above.

Edit 2: In your case (and in conjunction with Dynamic Linq) there are some ways to hack yourself around the issues mentioned in Edit 1 and in the original question. For example:

// 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...)

or:

// 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");
Cymoid answered 12/1, 2012 at 2:28 Comment(5)
Then they aren't really 'compatible' are they?Renege
Thanks, that solutions works well when we know in advance the runtime type of the value, but how about when the query is built programatically, we won't really know how to convert the values before hand. Is there any other way in which an expression can be built?Neysa
@M.Babcock: Depends on your definiton of 'compatible'.Cymoid
Thanks a lot, both variants seem to work well. Is there any difference in terms of performance between both solutions?Neysa
What if val.ToString() evaluates to a string with " or `\` in it?Clinkscales
F
1

Have you tried it[\"Age\"].Equals(object(30))?

Such that:

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

Edit: Updated to correctly cast 30 to object.

Fimbriation answered 12/1, 2012 at 9:26 Comment(2)
That throws an exception "Expression of type 'System.Int32' cannot be used for parameter of type 'System.Object' of method 'Boolean Equals(System.Object)'". it[\"Age\"].Equals(object(30)) works, though.Studner
Thanks, this solution also works well. Any performance considerations between this solution and the ones given by @CymoidNeysa
W
1

Is the code below useful for you?

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

But for this to work, all your types which can be assigned to the Value member of your Data class needs to have a useful implementation of the ToString method.

Wack answered 12/1, 2012 at 10:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.