• ASP.NET MVC 模型和数据对象映射实践


    在使用 MVC 开发项目的过程中遇到了个问题,就是模型和数据实体之间的如何快捷的转换?是不是可以像 Entity Framework 的那样 EntityTypeConfiguration,或者只需要少量的代码就可以把数据实体对象转换成一个 Model 对象(当时还不知道有 AutoMapper 这种东西),所以自己尝试写了一个简单的实现。

    1、初步尝试

    EntityTypeConverter 类主要用把数据实体转换成 Model 

        /// <summary>
        /// 提供一组实体对象模型转换的方法。
        /// </summary>
        /// <typeparam name="TEntityInfo">指定的实体信息对象类型。</typeparam>
        public static class EntityTypeConverter<TEntityInfo>
        {
            /// <summary>
            /// 将指定的实体对象转换成 <see cref="BaseModel"/> 类型的对象模型。
            /// </summary>
            /// <typeparam name="BaseModel">指定类型的模型对象。</typeparam>
            /// <param name="tEntityInfo">指定的实体信息。</param>
            /// <returns><see cref="BaseModel"/> 类型的对象模型。</returns>
            public static BaseModel ConverterToModel<BaseModel>(TEntityInfo tEntityInfo) where BaseModel : new()
            {
                if (tEntityInfo == null)
                    throw new ArgumentNullException("tEntityInfo");
    
                BaseModeltEntity = new BaseModel();
                foreach (var prop in typeof(BaseModel).GetProperties())
                {
    
                    if (!prop.CanRead || !prop.CanWrite)
                        continue;
    
                    if (!CommonHelper.GetCustomTypeConverter(prop.PropertyType).CanConvertFrom(typeof(string)))
                        continue;
                    string fieldName = String.Empty;
             // 验证是否有别名 var customAttribute = prop.CustomAttributes.Where(att => att.AttributeType == typeof(ModelFieldAliasAttribute)).FirstOrDefault(); if (customAttribute != null) { var constructorArgument = customAttribute.ConstructorArguments[0]; fieldName = constructorArgument.Value.ToString(); } else { fieldName = prop.Name; } PropertyInfo property = typeof(TEntityInfo).GetProperty(fieldName); if (property != null) { dynamic value = property.GetValue(tEntityInfo, null); prop.SetValue(tEntity, value, null); } } return tEntity; } /// <summary> /// 将指定的实体对象转换成 <see cref="BaseModel"/> 类型的对象模型列表。 /// </summary> /// <typeparam name="BaseModel">指定类型的模型对象。</typeparam> /// <param name="tEntityList">指定的实体信息列表。</param> /// <returns><see cref="BaseModel"/> 类型的对象模型列表。</returns> public static List<BaseModel> ConverterToList<BaseModel>(IList<TEntityInfo> tEntityList) where BaseModel: new() { List<BaseModel> modelList = new List<BaseModel>(); if (tEntityList != null && tEntityList.Count > 0) { foreach (var item in tEntityList) { modelList.Add(ConverterToModel<BaseModel>(item)); } } return modelList; } }

    辅助类基于 TypeConverter 实现的方法,用于泛型字段的转换

        public class GenericListTypeConverter<T> : TypeConverter
        {
            protected readonly TypeConverter typeConverter;
    
            public GenericListTypeConverter()
            {
                typeConverter = TypeDescriptor.GetConverter(typeof(T));
                if (typeConverter == null)
                    throw new InvalidOperationException("No type converter exists for type " + typeof(T).FullName);
            }
    
            protected virtual string[] GetStringArray(string input)
            {
                if (!String.IsNullOrEmpty(input))
                {
                    string[] result = input.Split(',');
                    Array.ForEach(result, s => s.Trim());
                    return result;
                }
                else
                    return new string[0];
            }
    
            public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
            {
    
                if (sourceType == typeof(string))
                {
                    string[] items = GetStringArray(sourceType.ToString());
                    return items.Any();
                }
    
                return base.CanConvertFrom(context, sourceType);
            }
    
            public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
            {
                if (value is string)
                {
                    string[] items = GetStringArray((string)value);
                    var result = new List<T>();
                    Array.ForEach(items, s =>
                    {
                        object item = typeConverter.ConvertFromInvariantString(s);
                        if (item != null)
                        {
                            result.Add((T)item);
                        }
                    });
    
                    return result;
                }
                return base.ConvertFrom(context, culture, value);
            }
    
            public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
            {
                if (destinationType == typeof(string))
                {
                    string result = string.Empty;
                    if (((IList<T>)value) != null)
                    {
                        //we don't use string.Join() because it doesn't support invariant culture
                        for (int i = 0; i < ((IList<T>)value).Count; i++)
                        {
                            var str1 = Convert.ToString(((IList<T>)value)[i], CultureInfo.InvariantCulture);
                            result += str1;
                            //don't add comma after the last element
                            if (i != ((IList<T>)value).Count - 1)
                                result += ",";
                        }
                    }
                    return result;
                }
    
                return base.ConvertTo(context, culture, value, destinationType);
            }
        }
    

    自定义属性 ModelFieldAlias 类用于标识字段别名。

        /// <summary>
        /// 表示一个模型转换对象字段类型的特性。
        /// </summary>
        [AttributeUsage(ValidTargets, AllowMultiple = false, Inherited = false)]
        public class ModelFieldAliasAttribute : Attribute
        {
            /// <summary>
            /// 指定此属性可以应用特性的应用程序元素。
            /// </summary>
            internal const AttributeTargets ValidTargets = AttributeTargets.Field | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Parameter;
    
            /// <summary>
            /// 模型的字段别名。
            /// </summary>
            private string _fieldAlias;
    
            /// <summary>
            /// 初始化 <see cref="ModelFieldAliasAttribute"/> 类的新实例。
            /// </summary>
            public ModelFieldAliasAttribute()
            {
    
            }
    
            /// <summary>
            /// 使用指定的筛选类型初始化 <see cref="ModelFieldAliasAttribute"/> 类的新实例。
            /// </summary>
            /// <param name="fieldAlias">指定模型的字段别名。</param>
            public ModelFieldAliasAttribute(string fieldAlias)
            {
                _fieldAlias = fieldAlias;
            }
    
            /// <summary>
            /// 获取或设置模型的字段别名。
            /// </summary>
            public string FieldAlias
            {
                get { return _fieldAlias; }
                set { _fieldAlias = value; }
            }
    
        }
    

    使用方法:

            // 集合对象
            List<ChannelNavigationInfo> channelNavigationList = new List<ChannelNavigationInfo>();
            List<ChannelNavigationModel> channelNavigationModelList = new List<ChannelNavigationModel>();
            channelNavigationModelList = EntityTypeConverter<ChannelNavigationInfo>.ConverterToList<ChannelNavigationModel>(channelNavigationList);
    
            // 单独对象
            ChannelNavigationInfo channelNavigation = new ChannelNavigationInfo();
            ChannelNavigationModel channelNavigationModel = new ChannelNavigationModel();
            channelNavigationModel = EntityTypeConverter<ChannelNavigationInfo>.ConverterToModel<ChannelNavigationModel>(channelNavigation);

    由于系统中大多数情况下都是对数据查询的业务,很少有插入和更新,所以没有发现这种实现的弊端,后来开发管理系统的时候,需要大量的数据插入和更新操作,发现这种方法没法做反向映射和转换。

    2、AutoMapper 

    AutoMapper 是用来解决对象之间映射转换的类库。对于我们开发人员来说,写对象之间互相转换的代码是一件极其浪费生命的事情,AutoMapper 能够帮助我们节省不少时间。

    知道这个类库是在研究 nopCommerce 这个项目的时候看到的,使用 AutoMapper 创建映射非常简单。

    Mapper.CreateMap<Order, OrderDto>();//创建映射关系Order –> OrderDto
    OrderDto dto = Mapper.Map<OrderDto>(order);//使用Map方法,直接将order对象装换成OrderDto对象
    

    AutoMapper能够自动识别和匹配大部分对象属性:

    如果源类和目标类的属性名称相同,直接匹配,目标类型的 CustomerName 可以匹配源类型的 Customer.Name,目标类型的 TotalRecords 可以匹配源类型的 GetTotalRecords() 方法。

    AutoMapper 还支持自定义匹配规则:

    Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
        // 属性匹配,匹配源类中 WorkEvent.Date 到 EventDate
        .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.WorkEvent.Date))
        .ForMember(dest => dest.SomeValue, opt => opt.Ignore())//忽略目标类中的属性
        .ForMember(dest => dest.TotalAmount, opt => opt.MapFrom(src => src.TotalAmount ?? 0))//复杂的匹配
        .ForMember(dest => dest.OrderDate, opt => opt.UserValue<DateTime>(DateTime.Now)); //固定值匹配

    为了方便使用,可以把扩展方法统一在一个类中实现。

        /// <summary>
        /// 提供一组对象映射相关的扩展方法。
        /// </summary>
        public static class MappingExtensions
        {
            #region City...
    
            /// <summary>
            /// 执行从 <see cref="City"/> 对象到 <see cref="CityModel"/> 对象的映射。
            /// </summary>
            /// <param name="entity">指定的 <see cref="City"/> 对象。</param>
            /// <returns><see cref="CityModel"/> 对象。</returns>
            public static CityModel ToModel(this City entity)
            {
                return Mapper.Map<City, CityModel>(entity);
            }
    
            /// <summary>
            /// 执行从 <see cref="CityModel"/> 对象到 <see cref="City"/> 对象的映射。
            /// </summary>
            /// <param name="model">指定的 <see cref="CityModel"/> 对象。</param>
            /// <returns><see cref="City"/> 对象。</returns>
            public static City ToEntity(this CityModel model)
            {
                return Mapper.Map<CityModel, City>(model);
            }
    
            /// <summary>
            /// 执行从 <see cref="CityModel"/> 对象到 <see cref="City"/> 对象的映射。
            /// </summary>
            /// <param name="model">指定的 <see cref="CityModel"/> 模型对象。</param>
            /// <param name="destination">指定的 <see cref="City"/> 实体对象。</param>
            /// <returns><see cref="City"/> 对象。</returns>
            public static City ToEntity(this CityModel model, City destination)
            {
                return Mapper.Map(model, destination);
            }
    
            #endregion
        }

    最后,在 Global.cs 文件中程序启动前,调用该方法。

    AutoMapperConfiguration.Configuration();
  • 相关阅读:
    多态性的理解
    类(三)——继承与多态
    类(二)——拷贝控制(浅拷贝,深拷贝,浅赋值,深赋值)
    类 (一) ——基本概念
    STL容器底层数据结构的实现
    异常处理
    C++实现单例模式
    类的成员函数的连续调用与返回值问题
    拷贝构造函数的参数为什么必须使用引用类型?拷贝赋值运算符的参数为什么也是引用类型?
    U盘装机教程
  • 原文地址:https://www.cnblogs.com/weisenz/p/3158314.html
Copyright © 2020-2023  润新知