2014-11-03 3 views
2

Я пытаюсь создать процесс для выполнения операций загрузки извлечения. Я хочу использовать ExpandoObject в моем конвейере, чтобы позволить мне легко добавлять столбцы в поток данных. В основном я читаю данные из какого-то источника данных, которые придают ему динамический характер и возвращают его в конвейер преобразований, которые добавляют к нему свойства либо на основе существующих свойств, либо что-то еще, а затем передают его в базу данных.Информация о типе хранилища с DynamicObject

Проблема в том, что мне нужна информация о типе для всех свойств, которые я добавляю к моему объекту expando, даже если я добавляю тип Nullable. Это потеряно, если тип Nullable является нулевым из-за бокса значения. Мне нужна информация о типе, чтобы в конце моего конвейера я мог реализовать datareader над моим перечислением ExpandoObjects и передать данные в базу данных.

Я надеялся, что свойство 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

Хотя это явно не указано, это может представлять интерес: ['System.Dynamic.ExpandObject'] (http://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject%28v=vs. 110% 29.aspx) – heltonbiker

+0

'ExpandoObject' - это просто словарь объектов, также может быть размещена нулевая структура. – IllidanS4

ответ

0

Да, это возможно.

Я изучил, хранится ли информация о типе где-то, и я обнаружил, что во время установки элемента используется объект Func<System.Runtime.CompilerServices.CallSite,object,int?,object>. Вероятно, это используется для хранения привязки для последующего использования.

Однако этот кеш фактически передается на переплет: частный 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]; 
} 

Этот метод получает секретный Cache словарь и находит типа ключ, который был построен из Func<T1,T2,T3,TResult>. Затем просто извлекается аргумент типа.

+0

Удивительно, это работает вкусно! – herps

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.

0

не могли бы вы добавить перегрузку:

public override bool TrySetMember<T>(SetMemberBinder binder, Nullable<T> value) 
{ 
    properties[binder.Name] = value; 
    propertyTypes[binder.Name] = typeof(Nullable<T>); 
    return true; 
} 
+0

Вы не можете переопределить, если не используется метод базового класса. Обратите внимание, что в предоставленном коде нет явных вызовов 'TrySetMember', только те, которые были синтезированы компилятором. –

+0

Я имел в виду перегрузку на MyExpando.TrySetMember (не переопределение на ExpandoObject.TrySetMember) ... –

+0

Ваш комментарий говорит о чем-то отличном от ключевого слова 'override' в вашем коде. –

Смежные вопросы