我正在尝试设计一个进行提取变换加载操作的过程。我想在我的管道中使用ExpandoObject,以便我轻松地将列添加到我的数据流中。基本上我从某种数据源中读取数据,将其转换为动态并将其返回到转换管道,该转换管道根据现有属性或其他内容向其添加属性,然后将其流式传输到数据库中。
我遇到的问题是,即使添加Nullable类型,我也需要添加到expando对象的所有属性的类型信息。如果Nullable类型由于值的装箱而为空,则会丢失。我想要类型信息,以便在我的管道的末尾,我可以在我的ExpandoObjects枚举上实现一个datareader,并将数据流式传输到数据库中。
我曾希望SetMemberBinder.ReturnType属性可以帮助我,但它似乎会返回一个对象。
这里是一些示例代码:
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"]);
}
}
}
有没有办法从TrySetMember中找出类型?我想知道是否有某种方法可以使用某些表达式神奇的东西?
如果有人有任何好主意,我很乐意听到。除了可以为空的类型之外,它都运行良好,但它们是数据库操作的关键。
由于
答案 0 :(得分:0)
这是一个很好的问题,但没有一般解决方案。 object
参数无法检测传递的值是否先前存储在Nullable<T>
变量中。您似乎必须等到有多条记录,看看是否混合了null
和值。
在这里的特定情况下,我会检查我看到你已经检查过ReturnType
参数上的binder
属性,看看它是否告诉你你需要什么。
您的一般问题是您所拥有的只是值及其类型,而不是表达式的静态类型。例如,您无法区分这些情况:
string s = "hello";
object o = s;
dynamo.P = s; // case 1
dynamo.P = o; // case 2
甚至
dynamo.Use(s);
dynamo.Use(o);
非常与静态类型语言不同,其中表达式的类型用于重载决策。看起来DynamicObject
无法做到这一点。
答案 1 :(得分:0)
无法添加重载:
public override bool TrySetMember<T>(SetMemberBinder binder, Nullable<T> value)
{
properties[binder.Name] = value;
propertyTypes[binder.Name] = typeof(Nullable<T>);
return true;
}
答案 2 :(得分:0)
是的,这是可能的。
我已经做过一些关于类型信息是否存储在某处的研究,并且我发现在设置成员期间,有一个Func<System.Runtime.CompilerServices.CallSite,object,int?,object>
对象被使用。这可能用于存储绑定以供以后使用。
但是,此缓存实际上已传递到binder:private CallSiteBinder.Cache
字段。它包含一个IDictionary<Type,object>
,其中包含一种缓存委托作为键和委托本身。因此,通过检查委托类型的泛型参数,您可以获取赋值中使用的表达式的类型。
完整方法:
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];
}
此方法获取秘密缓存字典,并查找已从Func<T1,T2,T3,TResult>
构造的 Type 键。然后只是提取类型参数。