Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get original dictionary from dict.AsQueryable()

I have a generic dictionary that pass to a method which only accepts IQueryable as a parameter

Is it possible to cast the queryable back to the original dictionary? And I don't mean creating a new dictionary with .ToDictionary(...)

private static void Main()
{

    var dict = new Dictionary<int, int>();
    dict.Add(1,1);

    SomeMethod(dict.AsQueryable());

}

public static void SomeMethod(IQueryable dataSource)
{
    // dataSource as Dictionary<int, int> --> null
    var dict = dataSource.???
}

I know in this simple example this does not make much sense. But in the big picture I have an interface which requires me to return an IQueryable as a dataSource. On implementation returns a dictionary. On a different place in my code I have classes that process the dataSources.

The processor knows that the dataSource will be an Dictionary but I don't want to have the overhead for creating another Dictionary if I already have one.

like image 934
Jürgen Steinblock Avatar asked Jan 12 '15 12:01

Jürgen Steinblock


2 Answers

The .AsQueryable() extension method returns an instance of the EnumerableQuery<T> wrapper class if it is called on something that was not already an IQueryable<T>.

This wrapper class has an .Enumerable property with internal access that provides access to the original object that .AsQueryable() was called on. So you could do this to get back your original dictionary:

var dict = new Dictionary<int, int>();
dict.Add(1,1);
var q = dict.AsQueryable();



Type tInfo = q.GetType();
PropertyInfo pInfo = tInfo.GetProperties(BindingFlags.NonPublic | 
                                         BindingFlags.Instance)
                          .FirstOrDefault(p => p.Name == "Enumerable");
if (pInfo != null)
{
    object originalDictionary = pInfo.GetValue(q, null);

    Console.WriteLine(dict == originalDictionary);  // true
}

However, this is generally a pretty bad idea. internal members have their access restricted for a reason, and I don't think there's any guarantee that the internal implementation of .AsQueryable() won't change at some point in the future. So your best bet is to either find a way to make the original dictionary accessible, or go ahead and make a new one.


One possible workaround (which is not great) is to make your own wrapper class to carry the dictionary along:
private class DictionaryQueryHolder<TKey, TValue> : IQueryable<KeyValuePair<TKey, TValue>>
{
    public IDictionary<TKey, TValue> Dictionary { get; private set; }
    private IQueryable<KeyValuePair<TKey, TValue>> Queryable { get; set; }

    internal DictionaryQueryHolder(IDictionary<TKey, TValue> dictionary)
    {
        Dictionary = dictionary;
        Queryable = dictionary.AsQueryable();
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return Queryable.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public Expression Expression
    {
        get { return Queryable.Expression; }
    }

    public Type ElementType
    {
        get { return Queryable.ElementType; }
    }

    public IQueryProvider Provider
    {
        get { return Queryable.Provider; }
    }
}

This would both act as a wrapper for the dictionary's IQueryable<T> and provide access to the original dictionary. But on the other hand, anyone trying to retrieve the dictionary would have to know what the generic type parameters were (e.g. <string, string>, <int, string>, etc.) in order to cast it successfully.

like image 116
JLRishe Avatar answered Nov 14 '22 21:11

JLRishe


The main problem here is that IQueryable wraps itself around the Dictionary rather than being like IEnumerable<> over IDictionary<>, where you could cast it back.

You can certainly find out if the type wrapped is a dictionary if you know the types involved:

public bool isDictionary<T>(object obj) {
    return obj.GetType().GenericTypeArguments.Contains(typeof(T));
}

isDictionary<KeyValuePair<string,string>>(dataSource);

If you don't mind reaching into the objects internals, you could use the private Enumerable field on EnumerableQuery to get a version of (possibly) the original dictionary back as an IEnumerable<>

But to actually convert from an EnumerableQuery<KeyValuePair<int,int>> hiding under an IQueryable without doing that I think you'd have to just take the hit and create a new dictionary from it.

like image 36
AssembledGhost Avatar answered Nov 14 '22 22:11

AssembledGhost