How to Map Object / Class Property to Dictionary When Object Property And Dictionary Key Names Are Different?

…衆ロ難τιáo~ 提交于 2020-01-14 06:29:09

问题


I am calling Bloomberg Server API (for Stock Market Data) and getting Data back in a Dictionary<string, object> Where the Key to the dictionary is the Field Name on Bloomberg's side, and the object contains the data value from Bloomberg and can be string, decimal, DateTime, boolean etc.

After I get the Bloomberg Data, I need to populate my strong type entities / classes with the values returned. Depending on what Field Names I send in my request to bloomberg, the returned Dictionary could have different key values. The problem I am having is, the bloomberg field name and my .net entity's property names don't match, so I am not sure I can do this mapping using AutoMapper or a similar library.

I also tried using a Tuple<string,string,object> where the 1st tuple item is the bloomberg field name, the 2nd tuple item is my entity's property name and the 3rd tuple item is the data value returned from bloomberg. That is not working out either (so far), so I am wondering if there is a simple straight-forward way of maintaining this bloombergfield<-->EntityProperty mapping and populate the Entity's value using the Bloomberg Data Value for the respective field. A Generic (i.e. using C# Generics) solution would be even better!

I have pasted below sample console app code so you can paste it and try it out. The 2 dictionaries, 1 for stockdata and other for bonddata have fake data, but you get the idea. I have also added comments below to re-iterate what I am trying to accomplish.

Thanks!!

namespace MapBBFieldsToEntityProperties
{
    using System;
    using System.Collections.Generic;

    class Program
    {
        public class StockDataResult
        {
            public string Name { get; set; }
            public decimal LastPrice { get; set; }
            public DateTime SettlementDate { get; set; }
            public decimal EPS { get; set; }

        }

        public class BondDataResult
        {
            public string Name { get; set; }
            public string Issuer { get; set; }
            public decimal Duration { get; set; }
            public DateTime YieldToMaturity { get; set; }
        }


        static void Main(string[] args)
        {

            // Data Coming from Bloomberg. 
            // Dictionary Key is the Bloomberg Data Field Name. 
            // Dictionary Object is the Value returns and can be any .Net primitive Type

            // Sample Data returned for a Stock Query to Bloomberg
            Dictionary<string, object> dctBloombergStockData 
                = new Dictionary<string, object>
                            {
                                { "NAME", "IBM" },
                                {  "PX_LAST", 181.30f },
                                { "SETTLE_DT", "11/25/2013" } // This is Datetime value
                            };

            // Sample Data returned for a Bond Query to Bloomberg
            Dictionary<string, object> dctBloombergBondData = 
                new Dictionary<string, object>
                            {
                                { "NAME", "IBM" },
                                { "ISSUE_ORG","IBM Corp" },
                                {  "DURATION", 4.430f },
                                { "YLD_TO_M", 6.456f }
                            };

            // This is my Stock Entity
            StockDataResult stockData = new StockDataResult();

            // This is my Bond Entity
            BondDataResult bondData = new BondDataResult();

            // PROBLEM STATEMENT:
            //
            // Need to somehow Map the Data returned from Bloomberg into the 
            // Corresponding Strong-typed Entity for that Data Type.
            // i.e. 
            // map dctBloombergStockData to stockData Entity instance as follows
            // 
            // dctBloombergStockData."NAME" Key  <--------> stockData.Name Property so that
            // dctBloombergStockData["NAME"] value of "IBM" can be assigned to stockData.Name
            // 
            // dctBloombergStockData."PX_LAST" Key  <--------> stockData.LastPrice Property so that
            // dctBloombergStockData["PX_LAST"] value 181.30f can be assigned to stockData.LastPrice value.
            // ....
            // .. you get the idea.
            // Similarly,
            // map dctBloombergBondData Data to bondData Entity instance as follows
            // 
            // dctBloombergBondData."NAME" Key  <--------> bondData.Name Property so that
            // dctBloombergBondData["NAME"] value of "IBM" can be assigned to bondData.Name property's value
            // 
            // dctBloombergBondData."ISSUE_ORG" Key  <--------> bondData.Issuer Property so that
            // dctBloombergBondData["ISSUE_ORG"] value 181.30f can be assigned to bondData.Issuer property's value.
            //
            // dctBloombergBondData."YLD_TO_M" Key  <--------> bondData.YieldToMaturity Property so that
            // dctBloombergBondData["YLD_TO_M"] value 181.30f can be assigned to bondData.YieldToMaturity property's value.                                
        }
    }
}

回答1:


I am sure quite a few improvements are possible, but this is one way of specifying a mapping and using that map.

class Program
{
    public class Mapper<TEntity> where TEntity : class
    {
        private readonly Dictionary<string, Action<TEntity, object>> _propertyMappers = new Dictionary<string, Action<TEntity, object>>();
        private Func<TEntity> _entityFactory;

        public Mapper<TEntity> ConstructUsing(Func<TEntity> entityFactory)
        {
            _entityFactory = entityFactory;
            return this;
        }

        public Mapper<TEntity> Map<TProperty>(Expression<Func<TEntity, TProperty>> memberExpression, string bloombergFieldName, Expression<Func<object, TProperty>> converter)
        {
            var converterInput = Expression.Parameter(typeof(object), "converterInput");
            var invokeConverter = Expression.Invoke(converter, converterInput);
            var assign = Expression.Assign(memberExpression.Body, invokeConverter);
            var mapAction = Expression.Lambda<Action<TEntity, object>>(
                assign, memberExpression.Parameters[0], converterInput).Compile();
            _propertyMappers[bloombergFieldName] = mapAction;
            return this;
        }

        public TEntity MapFrom(Dictionary<string, object> bloombergDict)
        {
            var instance = _entityFactory();
            foreach (var entry in bloombergDict)
            {
                _propertyMappers[entry.Key](instance, entry.Value);
            }
            return instance;
        }
    }

    public class StockDataResult
    {
        public string Name { get; set; }
        public decimal LastPrice { get; set; }
        public DateTime SettlementDate { get; set; }
        public decimal EPS { get; set; }
    }

    public static void Main(params string[] args)
    {
        var mapper = new Mapper<StockDataResult>()
            .ConstructUsing(() => new StockDataResult())
            .Map(x => x.Name, "NAME", p => (string)p)
            .Map(x => x.LastPrice, "PX_LAST", p => Convert.ToDecimal((float)p))
            .Map(x => x.SettlementDate, "SETTLE_DT", p => DateTime.ParseExact((string)p, "MM/dd/yyyy", null));


        var dctBloombergStockData = new Dictionary<string, object>
        {
            { "NAME", "IBM" },
            {  "PX_LAST", 181.30f },
            { "SETTLE_DT", "11/25/2013" } // This is Datetime value
        };
        var myStockResult = mapper.MapFrom(dctBloombergStockData);

        Console.WriteLine(myStockResult.Name);
        Console.WriteLine(myStockResult.LastPrice);
        Console.WriteLine(myStockResult.SettlementDate);
    }
}



回答2:


As you have said your self you need a mapping table. You can create a static read only dictionary in you type that would map each key filed returned from Bloomberg to property in your strongly typed class.

Here is how I would do it. PS: I used linqpad to test. PPS: You can add as many mapper(s) to the dictionary as need be. You also need fast-member to run this code.

void Main()
{
  var dctBloombergStockData = new Dictionary<string, object>
        {
            { "NAME", "IBM" },
            {  "PX_LAST", 181.30f },
            { "SETTLE_DT", "11/25/2013" } // This is Datetime value
        };
    StockDataResult.FromBloombergData(dctBloombergStockData);
}

// Define other methods and classes here
interface IMapper
{
    string PropertyName { get; }
    object Parse(object source);
}

class Mapper<T, TResult> : IMapper
{
    private Func<T, TResult> _parser;
    public Mapper(string propertyName, Func<T, TResult> parser)
    {
        PropertyName = propertyName;
        _parser = parser;
    }

    public string PropertyName { get; private set; }

    public TResult Parse(T source)
    {
        source.Dump();
        return _parser(source);
    }

    object IMapper.Parse(object source)
    {
        source.Dump();
        return Parse((T)source);
    }
}

public class StockDataResult
{
    private static TypeAccessor Accessor = TypeAccessor.Create(typeof(StockDataResult));

    private static readonly Dictionary<string, IMapper> Mappers = new Dictionary<string, IMapper>(StringComparer.CurrentCultureIgnoreCase){
            { "NAME", new Mapper<string, string>("Name", a => a) },
            { "PX_LAST", new Mapper<float, decimal>("LastPrice", a => Convert.ToDecimal(a)) },
            { "SETTLE_DT", new Mapper<string, DateTime>("SettlementDate", a => DateTime.ParseExact(a, "MM/dd/yyyy", null)) }
        };

    protected StockDataResult()
    { }

    public string Name { get; set; }
    public float LastPrice { get; set; }
    public DateTime SettlementDate { get; set; }
    public decimal EPS { get; set; }

    public static StockDataResult FromBloombergData(Dictionary<string, object> state)
    {
        var result = new StockDataResult();
        IMapper mapper;
        foreach (var entry in state)
        {
            if(!Mappers.TryGetValue(entry.Key, out mapper))
            { continue; }
            Accessor[result, mapper.PropertyName.Dump()] = mapper.Parse(entry.Value);
        }
        return result;
    }
}



回答3:


How about:

stockData.Name = dctBloombergStockData["NAME"];
stockData.LastPrice = dctBloombergStockData["PX_LAST"]
//and so on...


来源:https://stackoverflow.com/questions/20157421/how-to-map-object-class-property-to-dictionary-when-object-property-and-dictio

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!