I'm trying to design a process for doing extract transform load operations. I want to use the ExpandoObject in my pipeline to allow me to easily add columns to my data stream. Basically I read data from some kind of datasource cast it a dynamic and yield return it to a pipeline of transforms that add properties to it either based on existing properties or something else then stream it into a database.
The issue I have is that I need type information for all the properties I add to my expando object even if I add a Nullable type. This is lost if the Nullable type is null because of the boxing of the value. I want the type information so that at the end of my pipeline I can implement a datareader over my enumeration of ExpandoObjects and stream the data into a database.
I had hoped the SetMemberBinder.ReturnType property might help me but it seems to return an object.
here's some sample code:
using System;
using System.Collections.Generic;
using System.Dynamic;
using Xunit
namespace Whanger
{
public class MyExpando : DynamicObject
{
Dictionary<string, object> properties = new Dictionary<string, object>();
Dictionary<string, Type> propertyTypes = new Dictionary<string, Type>();
public Dictionary<string, Type> Types
{
get
{
return propertyTypes;
}
}
public Dictionary<string, object> Values
{
get
{
return properties;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (properties.ContainsKey(binder.Name))
{
result = properties[binder.Name];
return true;
}
else
{
result = null;
return false;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
properties[binder.Name] = value;
propertyTypes[binder.Name] = binder.ReturnType;//always object :(
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
dynamic method = properties[binder.Name];
result = method(args[0].ToString(), args[1].ToString());
return true;
}
}
public class MyExpandoTests
{
[Fact]
public void CanAddDynamicMembers()
{
dynamic obj = new MyExpando();
obj.Name = "Wibble";
obj.Value = 2;
Assert.Equal(obj.Name, "Wibble");
Assert.Equal(obj.Value, 2);
}
[Fact]
public void CanMaintainType()
{
dynamic obj = new MyExpando();
int? nullableInt = null;
obj.NullInt = nullableInt;
obj.Name = "Wibble";
Assert.Equal(obj.Name, "Wibble");
Assert.Null(obj.NullInt);
//fails
Assert.Equal(typeof(int?), ((MyExpando)obj).Types["NullInt"]);
}
}
}
Is there a way to find out the type from TrySetMember? I wonder if there's some way to use some expression tree magic of something?
If anyone has any great ideas I'd love to hear them. It all works well except for the nullable types but they are key for database operations.
Thanks
Yes, it is possible.
I've done some research whether the type information is stored somewhere and I've discovered that during the time of setting the member, there is a Func<System.Runtime.CompilerServices.CallSite,object,int?,object>
object being used. This is probably used to store the binding for later uses.
However, this cache is actually passed to the binder: private CallSiteBinder.Cache
field. It contains an IDictionary<Type,object>
that contains a type of the cache delegate as a key and the delegate itself. So, by examining the generic arguments for the delegate type, you can obtain the type of the expression that was used in the assignment.
Complete method:
private static readonly FieldInfo CallSiteBinder_Cache = typeof(CallSiteBinder).GetField("Cache", BindingFlags.NonPublic | BindingFlags.Instance);
private static Type BindingType(CallSiteBinder binder)
{
IDictionary<Type,object> cache = (IDictionary<Type,object>)CallSiteBinder_Cache.GetValue(binder);
Type ftype = cache.Select(t => t.Key).FirstOrDefault(t => t != null && t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Func<,,,>));
if(ftype == null) return null;
Type[] genargs = ftype.GetGenericArguments();
return genargs[2];
}
This method obtains the secret Cache dictionary and finds a Type key that is has been constructed from Func<T1,T2,T3,TResult>
. Then is just extracts the type argument.