Project to a Known Type using Simple.OData.Client Dynamic Syntax

799 views Asked by At

Simple.OData.Client has a typed and dynamic (and basic) syntax.

I like the typed, but I don't want to build out all my types. In the end I really only need two or so types in the results I get.

But my queries need more types to properly filter the results.

So I want to use the dynamic syntax. But I want to cast the results to classes I have.

I can easily do this manually, but I thought I would see if Simple.OData.Client supports this before I go writing up all that conversion code for each query.

Here is some dynamic syntax code that runs without errors:

client.For(x.Client).Top(10).Select(x.ClientId, x.Name).FindEntriesAsync();

Here is an example of what I had hoped would work (selecting into a new Client object)

client.For(x.Client).Top(10).Select(new Client(x.ClientId, x.Name)).FindEntriesAsync();

But that kind of projection is not supported (I get an "has some invalid arguments" error).

Is there a way to support projection into an existing class when using the dynamic syntax of Simple.OData.Client?

1

There are 1 answers

2
Vaccano On

EDIT: The code below works. But it's performance is terrible. I decided to abandon it and write hand written mappers for each type I needed.

This is what I came up with:

dynamic results = oDataClient.For(x.Client).Select(x.ClientId, x.Name).FindEntriesAsync().Result;   
var listOfClients = SimpleODataToList<Client>(results);


public List<T> SimpleODataToList<T>(dynamic sourceObjects) where T : new()
{
    List<T> targetList = new List<T>();

    foreach (var sourceObject in sourceObjects)
    {
        // This is a dictionary with keys (properties) and values.  But no 
        //  matter what sourceObject is passed in, the values will always be
        //  the values of the first entry in the sourceObjects list.
        var sourceProperties = ((System.Collections.Generic.IDictionary<string, object>)sourceObject);  

        var targetProperties = typeof(Client).GetProperties().Where(prop => prop.CanWrite);

        var targetObject = new T();

        foreach (var targetProperty in targetProperties)
        {
            if (sourceProperties.ContainsKey(targetProperty.Name))
            {

                var sourceValue = GetProperty(sourceObject, targetProperty.Name);
                targetProperty.SetValue(targetObject, sourceValue, null);
            }               
        }   
        targetList.Add(targetObject);
    }
    return targetList;
}

public static object GetProperty(object o, string member)
{
    if (o == null) throw new ArgumentNullException("o");
    if (member == null) throw new ArgumentNullException("member");
    Type scope = o.GetType();
    IDynamicMetaObjectProvider provider = o as IDynamicMetaObjectProvider;
    if (provider != null)
    {
        ParameterExpression param = Expression.Parameter(typeof(object));
        DynamicMetaObject mobj = provider.GetMetaObject(param);
        GetMemberBinder binder = (GetMemberBinder)Microsoft.CSharp.RuntimeBinder.Binder.GetMember(0, member, scope, new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null) });
        DynamicMetaObject ret = mobj.BindGetMember(binder);
        BlockExpression final = Expression.Block(
            Expression.Label(CallSiteBinder.UpdateLabel),
            ret.Expression
        );
        LambdaExpression lambda = Expression.Lambda(final, param);
        Delegate del = lambda.Compile();
        return del.DynamicInvoke(o);
    }
    else
    {
        return o.GetType().GetProperty(member, BindingFlags.Public | BindingFlags.Instance).GetValue(o, null);
    }
}

It was made much harder because normal casts and such for the dynamic objects returned would only give the first object in the list over and over. The GetProperty method works around this limitation.