ASP.NET MVC 模型和数据对象映射实践
在使用 MVC 开发项目的过程中遇到了个问题,就是模型和数据实体之间的如何快捷的转换?是不是可以像 Entity Framework 的那样 EntityTypeConfiguration,或者只需要少量的代码就可以把数据实体对象转换成一个 Model 对象(当时还不知道有 AutoMapper 这种东西),所以自己尝试写了一个简单的实现。
1、初步尝试
EntityTypeConverter 类主要用把数据实体转换成 Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | /// <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;<br> // 验证是否有别名 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 实现的方法,用于泛型字段的转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | 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 类用于标识字段别名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | /// <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; } } } |
使用方法:
1 2 3 4 5 6 7 8 9 | // 集合对象 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 创建映射非常简单。
1 2 | Mapper.CreateMap<Order, OrderDto>(); //创建映射关系Order –> OrderDto OrderDto dto = Mapper.Map<OrderDto>(order); //使用Map方法,直接将order对象装换成OrderDto对象 |
AutoMapper能够自动识别和匹配大部分对象属性:
如果源类和目标类的属性名称相同,直接匹配,目标类型的 CustomerName 可以匹配源类型的 Customer.Name,目标类型的 TotalRecords 可以匹配源类型的 GetTotalRecords() 方法。
AutoMapper 还支持自定义匹配规则:
1 2 3 4 5 6 | 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)); //固定值匹配 |
为了方便使用,可以把扩展方法统一在一个类中实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /// <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 文件中程序启动前,调用该方法。
1 | AutoMapperConfiguration.Configuration(); |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架