1

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?

Vaccano
  • 78,325
  • 149
  • 468
  • 850

1 Answers1

0

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.

Vaccano
  • 78,325
  • 149
  • 468
  • 850
  • Bit confused. So is the above code having performance issues or this is the final code after removing the bottlenecks. I am trying to do something similar i.e. convert an untyped response to a known type – Dibzmania Feb 14 '21 at 00:35
  • @Dibzmania - This code worked, but had poor performance. I abandoned it for hand written mappers. (At least I think I did by reading my answer. It has been long enough that I am not sure what project this was for.) – Vaccano Feb 16 '21 at 18:15