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

 

posted @ 2013-06-27 11:03  Charles Zhang  阅读(2780)  评论(1编辑  收藏  举报