【问题标题】:How to Map Object / Class Property to Dictionary When Object Property And Dictionary Key Names Are Different?当对象属性和字典键名不同时,如何将对象/类属性映射到字典?
【发布时间】:2013-12-08 01:47:35
【问题描述】:

我正在调用 Bloomberg Server API(用于股票市场数据)并在 Dictionary<string, object> 中取回数据,其中字典中的 Key 是 Bloomberg 一侧的 Field Name,并且该对象包含来自 Bloomberg 的数据值可以是stringdecimalDateTimeboolean

获得彭博数据后,我需要使用返回的值填充我的强类型实体/类。根据我在向bloomberg 发送的请求中发送的字段名称,返回的字典可能具有不同的键值。我遇到的问题是,bloomberg 字段名称和我的 .net 实体的属性名称不匹配,所以我不确定是否可以使用 AutoMapper 或类似库进行此映射。

我还尝试使用Tuple<string,string,object>,其中第一个元组项是Bloomberg 字段名称,第二个元组项是我的实体的属性名称,第三个元组项是Bloomberg 返回的数据值。这也不起作用(到目前为止),所以我想知道是否有一种简单直接的方法来维护这个bloombergfieldEntityProperty 映射并使用相应字段的彭博数据值填充实体的值。泛型(即使用 C# 泛型)解决方案会更好!

我已粘贴以下示例控制台应用程序代码,以便您粘贴并试用。 2 个字典,1 个用于stockdata,另一个用于bonddata 有假数据,但你明白了。我还在下面添加了 cmets 以重申我想要完成的工作。

谢谢!!

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.                                
        }
    }
}

【问题讨论】:

    标签: c# dictionary mapping automapper bloomberg


    【解决方案1】:

    我确信有很多改进是可能的,但这是指定映射和使用该映射的一种方式。

    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);
        }
    }
    

    【讨论】:

    • 这是一些不错的代码。我对其进行了一些更改。
    • 感谢亚历克斯和马克斯。我将在周末测试你的两个答案并告诉你!
    • @Alex 这很好用!谢谢你。我也用 BondData 对其进行了测试。 1 个问题,如果 BloombergDataDictionary 中的字段未映射到实体,我会收到错误消息。所以我改变了 MapFrom 代码如下来检查密钥。这是解决该问题的正确方法吗? foreach(bloombergDict 中的 var 条目){ if (_propertyMappers.ContainsKey(entry.Key)) _propertyMappers[entry.Key](instance, entry.Value); }
    • @Shiva 如果您真的想忽略您收到的字典数据中存在的字段,是的,您可以这样做。我建议您将其设为您检查的明确选项。 IE。向“Mapper”添加一个名为“IgnoreUnmappedFields()”的方法,该方法设置您在“MapFrom”方法中检查的布尔标志。
    • @Alex 谢谢。不确定我是否理解您的建议。也许我的问题很模糊。所以问题是,我将字段作为 List = { "NAME", "PX_LAST", "SETTLE_DT"... etc} 发送到Bloomberg。并且 Bloomberg 返回我转换为 Dictionary 的数据,其中 key 是 List 中的字段名,并且 object 具有值。因此,如果映射器代码没有字段名,我想忽略它。你是说我应该有布尔标志,然后在映射器中。 MapFrom<.. containskey>
    【解决方案2】:

    正如您所说,您需要一个映射表。您可以在您的类型中创建一个静态只读字典,该字典将从 Bloomberg 返回的每个键字段映射到您的强类型类中的属性。

    我会这样做。 PS:我使用 linqpad 进行测试。 PPS:您可以根据需要向字典中添加任意数量的映射器。您还需要fast-member 来运行此代码。

    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;
        }
    }
    

    【讨论】:

    • 我从 Google 代码中添加了对 FastMember 程序集的引用,还尝试了 Nuget 包。在构建项目时仍然在 .Dump() 语句中出现此错误。我错过了什么? “‘T’不包含‘Dump’的定义,并且找不到接受‘T’类型的第一个参数的扩展方法‘Dump’”。
    • 我认为你的代码也可以。在我的代码库中使用 FastMember 可能需要通过大量文书工作才能在我的客户站点获得批准,因此这可能不是理想的解决方案。但是,我也喜欢你的解决方案,所以我赞成它。感谢您编写代码。
    • Dump() 方法是 linqpad 的一部分。我使用 linqpad 做概念证明。您需要删除它,因为它在 vs.net 中不可用。
    • 哦,好的。我注释掉了两个 source.Dump 语句,然后从该 Line => Accessor[result, mapper.PropertyName] = mapper.Parse(entry.Value); 的赋值中删除了 .Dump()现在我从 FastMember 的上面一行中得到一个 CAST 异常的价格值,即当 LastPrice 被分配 181.3 时。稍后会调试它(因为我现在有一个解决方案),但我想我会让你知道。
    • 这是有问题的,因为你得到的值不是浮动的。尝试将映射器中的浮点数更改为对象。或者看看它是什么值类型。
    【解决方案3】:

    怎么样:

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

    【讨论】:

    • 这行不通。我忘了提到彭博词典字段会有所不同,这取决于我告诉彭博的内容。即 1 个数据结果可能具有“名称”和“PX_LAST”,而其他一些请求可能具有“PX_LAST”和“SETTLE_DT”。因此我需要一个通用方法/映射器。
    • 为什么不创建一些条件语句并根据字典中的键设置对象属性呢?您返回的数据中有多少种不同的可能性?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-18
    • 2016-03-04
    相关资源
    最近更新 更多