• IEnumerable<IEnumerable<string>>结构解析通用解决方案(支持指定属性顺序)


    一、前言

    类似如下字符串

     "ID", "NameValue", "CodeValue", "ExchangeTypeValue", 6, "invalid"

     "ID2", "NameValue2", "CodeValue2", "ExchangeTypeValue2", 6, "invalid"

    .......

    有可能是文件中存在的,或者调用其他程序返回的结构化数据,那么该如何解析?当其他场景中,只是返回顺序(属性顺序)变了,类结构还是一样,又如何应对?当有很多类似场景时,是不是该抽象出一个泛型方法来应对该场景?当然,也不仅仅于上述情况,可能返回的结构是确定,只是形式不一样,这个过程这里暂时省略,因为正则表达式完全能够解析出来。要用以下的方法,必须转换成IEnumerable<IEnumerable<string>>结构,IEnumerable<IEnumerable<string>>结构中IEnumerable<string>为一个对象所有的值,总体是多个对象的值集合。本文中用反射写的(关于IL操作的后续文章提供),相关的类图如下:

    二、ResultTransfer的具体实现

    ResultTransfer主要用于对IEnumerable<IEnumerable<string>>结构的解析,另外还可以指定params string[] propertyNames属性参数列表来确定解析顺序(也即是属性顺序),主要方法如下:

         public static IList<T> Parse<T>(IEnumerable<IEnumerable<string>> entityRows, params string[] propertyNames) where T : new()

         第一个参数entityRows为对象列表值集合。

         第二个参数propertyNames为可选参数,输入该参数后,如果propertyNames中存在相关属性,则按照propertyNames对应的属性顺序进行解析。否则按照提供的T类中属性的DataMemberAttribute来确定属性顺序进行解析。

    实现代码非常简洁和简单,方法具体如下所示:

            public static IList<T> Parse<T>(IEnumerable<IEnumerable<string>> entityRows, params string[] propertyNames) where T : new()
            {
                if (entityRows == null || entityRows.Count() == 0)
                {
                    return new List<T>();
                }
    
                IList<T> entities = new List<T>();
                var members = new DataMemberAttributeCollection(typeof(T), propertyNames);
    
                if (members.Count <= 1)
                {
                    return new List<T>();
                }
    
                FuncProvider funcProvider = new FuncProvider();
    
                foreach (var propertyValues in entityRows)
                {
                    if (propertyValues == null || propertyValues.Count() == 0)
                    {
                        continue;
                    }
    
                    entities.Add(Generate<T>(propertyValues, members, funcProvider));
                }
    
                return entities;
            }
    
            private static T Generate<T>(IEnumerable<string> propertyValues, DataMemberAttributeCollection members,
                FuncProvider funcProvider) where T : new()
            {
                T entity = Activator.CreateInstance<T>();
                int memberCount = members.Count;
                int propertyCount = propertyValues.Count();
    
                if (memberCount == 0 || propertyCount == 0)
                {
                    return entity;
                }
    
                int convertCount = Math.Min(memberCount, propertyCount);
                DataMemberAttribute currAttribute;
                PropertyInfo currPropertyInfo;
    
                int propertyValueIndex = 0;
    
                foreach (string propertyValue in propertyValues)
                {
                    if (propertyValueIndex >= convertCount)
                    {
                        break;
                    }
    
                    propertyValueIndex++;
                    currAttribute = members[propertyValueIndex - 1];
                    currPropertyInfo = currAttribute.PropertyInfo;
    
                    if (propertyValue == null)
                    {
                        currPropertyInfo.SetValue(entity, null, null);
                        continue;
                    }
    
                    if (propertyValue.GetType() == currAttribute.PropertyType)
                    {
                        currPropertyInfo.SetValue(entity, propertyValue, null);
                    }
                    else
                    {
                        object result = funcProvider.DynamicInvoke(currAttribute.PropertyType, (propertyValue ?? string.Empty).ToString());
                        currPropertyInfo.SetValue(entity, result, null);
                    }
                }
    
                return entity;
            }

    三、DataMemberAttributeCollection的具体实现

    DataMemberAttributeCollection集合类主要用于设置解析属性的顺序,同样,该类提供二个参数的构造函数用于生成相应的配置信息public DataMemberAttributeCollection(Type type, params string[] propertyNames)。

    主要代码如下:

            public void GetConfiguration(Type type, params string[] propertyNames)
            {
                if (type == null || type.GetProperties().Length <= 0)
                {
                    return;
                }
    
                if (propertyNames == null || propertyNames.Length == 0)
                {
                    AddAllDataMemberAttributes(type);
                }
                else
                {
                    AddDataMemberAttributes(type, propertyNames);
                }
    
                this._memberAttributes = this._memberAttributes.OrderBy(p => p.Order).ToList();
            }
    
            private void AddDataMemberAttributes(Type type, string[] propertyNames)
            {
                IList<PropertyInfo> validPropertyInfos = new List<PropertyInfo>();
                PropertyInfo tempPropertyInfo;
    
                foreach (string propertyName in propertyNames)
                {
                    if (string.IsNullOrWhiteSpace(propertyName))
                    {
                        continue;
                    }
    
                    tempPropertyInfo = type.GetProperty(propertyName.Trim());
    
                    if (tempPropertyInfo == null)
                    {
                        throw new ArgumentException(string.Format(@"Contains Invalid Property Name Arg : {0}.", propertyName.Trim()));
                    }
    
                    validPropertyInfos.Add(tempPropertyInfo);
                }
    
                if (validPropertyInfos.Count() > 0)
                {
                    foreach (var property in validPropertyInfos)
                    {
                        AddAttributes(new DataMemberAttribute(), property);
                    }
                }
            }
    
            private void AddAllDataMemberAttributes(Type type)
            {
                DataMemberAttribute attr = null;
                foreach (PropertyInfo propertyInfo in type.GetProperties())
                {
                    attr = AttributeUtility.GetCustomAttribute<DataMemberAttribute>(propertyInfo);
    
                    if (attr == null)
                    {
                        continue;
                    }
    
                    if (!attr.IsRequire)
                    {
                        continue;
                    }
    
                    if (this._memberAttributes.Count(p => p.Order == attr.Order) > 0)
                    {
                        throw new ArgumentException(string.Format(@"Contains Same Order {0}.Please Look Up DataMemberAttribute
                                Of The Type {1}", attr.Order, type.Name));
                    }
    
                    AddAttributes(attr, propertyInfo);
                }
            }
    
            private void AddAttributes(DataMemberAttribute attr, PropertyInfo propertyInfo)
            {
                if (string.IsNullOrWhiteSpace(attr.Name))
                {
                    attr.Name = propertyInfo.Name;
                }
    
                attr.PropertyName = propertyInfo.Name;
                attr.PropertyType = propertyInfo.PropertyType;
                attr.PropertyInfo = propertyInfo;
    
                this._memberAttributes.Add(attr);
            }

    该类确保指定Type的类中DataMemberAttribute是否设置正确(是否有相同的Order),确保是否输入了错误的propertyName。

    四、具体应用

    对于具体应用的话,用单元测试来得方便与直接。

    (1)对于只输入一个参数的应用如下:

            [TestMethod()]
            public void ParseTest()
            {
                IList<IList<string>> entityRows = new List<IList<string>>();
                entityRows.Add(new List<string>() { "3", "NameValue", "CodeValue", "ExchangeTypeValue", "6", "invalid" });
    
                var contracts = ResultTransfer.Parse<ContinousContract>(entityRows);
    
                Assert.IsNotNull(contracts);
                Assert.IsTrue(contracts.Count == 1);
                Assert.AreEqual(contracts[0].Code, "CodeValue");
                Assert.AreEqual(contracts[0].Name, "NameValue");
                Assert.AreEqual(contracts[0].ExchangeType, "ExchangeTypeValue");
                Assert.AreEqual(contracts[0].OrgidID, 3);
                Assert.AreEqual(contracts[0].ExchangeTypeValue, 6);
            }

    (2)对于只输入无效参数的应用如下:

            [TestMethod()]
            public void ParseWithInvalidArgTest()
            {
                IList<IList<string>> entityRows = new List<IList<string>>();
                entityRows.Add(new List<string>() { "sss", "NameValue", "CodeValue", "ExchangeTypeValue", "6", "invalid" });
    
                var contracts = ResultTransfer.Parse<ContinousContract>(entityRows);
    
                Assert.IsNotNull(contracts);
                Assert.IsTrue(contracts.Count == 1);
                Assert.AreEqual(contracts[0].Code, "CodeValue");
                Assert.AreEqual(contracts[0].Name, "NameValue");
                Assert.AreEqual(contracts[0].ExchangeType, "ExchangeTypeValue");
                Assert.AreEqual(contracts[0].OrgidID, 0);
                Assert.AreEqual(contracts[0].ExchangeTypeValue, 6);
            }

    输入无效的IEnumerable<IEnumerable<string>>参数,方法内部会进行隐式的转换,比如“sss”转换成0。

    (3)对于二个参数时的应用如下:

            [TestMethod()]
            public void ParseWithArgTest()
            {
                IList<IList<string>> entityRows = new List<IList<string>>();
                entityRows.Add(new List<string>() { "3", "NameValue", "ExchangeTypeValue", "6", "invalid" });
                var propertyNames = new List<string>() { "ExchangeTypeValue", "Name", "", "ExchangeType" };
    
                var contracts = ResultTransfer.Parse<ContinousContract>(entityRows, propertyNames.ToArray());
    
                Assert.IsNotNull(contracts);
                Assert.IsTrue(contracts.Count == 1);
                Assert.AreEqual(contracts[0].Code, null);
                Assert.AreEqual(contracts[0].Name, "NameValue");
                Assert.AreEqual(contracts[0].ExchangeType, "ExchangeTypeValue");
                Assert.AreEqual(contracts[0].OrgidID, 0);
                Assert.AreEqual(contracts[0].ExchangeTypeValue, 3);
            }

    一旦输入二个参数,且propertyNames参数的个数大于0,则以propertyNames对应的属性顺序进行解析。对于输入错误的属性名,方法内部会抛出异常,当然也可以增加一个参数用于控制是否抛出异常,或者写入日志文件中等。

    对于将固定格式的字符串解析成IEnumerable<IEnumerable<string>>,正则表达式解析的话比较简单,此文不做讲解,略过...

  • 相关阅读:
    linux初始密码Mysql
    lamp整个打包
    模拟小球碰撞后返回
    Linux图形界面卡死
    非模态对话框
    菜单
    键盘消息简单示例
    菜单练习
    模态对话框练习
    阶段知识整合(画笔,画刷,字体)
  • 原文地址:https://www.cnblogs.com/jasenkin/p/entity_rows_value_parse.html
Copyright © 2020-2023  润新知