在使用 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();

ASP.NET MVC 模型和数据对象映射实践的更多相关文章

  1. ASP.NET MVC 5 - 将数据从控制器传递给视图

    在我们讨论数据库和数据模型之前,让我们先讨论一下如何将数据从控制器传递给视图.控制器类将响应请求来的URL.控制器类是给您写代码来处理传入请求的地方,并从数据库中检索数据,并最终决定什么类型的返回结果 ...

  2. ASP.NET没有魔法——ASP.NET MVC 模型绑定

    在My Blog中已经有了文章管理功能,可以发布和修改文章,但是对于文章内容来说,这里缺少最重要的排版功能,如果没有排版的博客很大程度上是无法阅读的,由于文章是通过浏览器查看的,所以文章的排版其实与网 ...

  3. [转] ASP.NET MVC 模型绑定的功能和问题

    摘要:本文将与你深入探究 ASP.NET MVC 模型绑定子系统的核心部分,展示模型绑定框架的每一层并提供扩展模型绑定逻辑以满足应用程序需求的各种方法. 同时,你还会看到一些经常被忽视的模型绑定技术, ...

  4. [转]ASP.NET MVC 5 - 将数据从控制器传递给视图

    在我们讨论数据库和数据模型之前,让我们先讨论一下如何将数据从控制器传递给视图.控制器类将响应请求来的URL.控制器类是给您写代码来处理传入请求的地方,并从数据库中检索数据,并最终决定什么类型的返回结果 ...

  5. ASP.NET MVC 4 (一)路径映射

    原文:ASP.NET MVC 4 (一)路径映射 正如ASP.NET MVC名字所揭示的一样,是以模型-视图-控制设计模式构建在ASP.NET基础之上的WEB应用程序,我们需要创建相应的程序类来协调处 ...

  6. ASP.NET MVC模型绑定的6个建议(转载)

    ASP.NET MVC模型绑定的6个建议 发表于2011-08-03 10:25| 来源博客园| 31 条评论| 作者冠军 validationasp.netmvc.netasp 摘要:ASP.NET ...

  7. 【ASP.NET MVC系列】数据验证和注解

    [01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google Chrome浏览器(操作篇)(下) [04]浅谈ASP. ...

  8. ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(下篇)

    上一篇<ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(上篇)>文章介绍了ASP.NET MVC模型绑定的相关组件和概念,本章将介绍Controller在执行时是如何通过这 ...

  9. ASP.NET MVC - 模型验证

    ASP.NET MVC - 模型验证(Model verification) 模型验证原理浅析 模型验证用到了模型绑定器.模型验证器(System.Web.Mvc.DataAnnotationsMod ...

随机推荐

  1. Entity Framework 关系约束配置

    前言 简单的说一下自己的理解,大家应该都很明白ADO.NET,也就是原生态的数据库操作,直接通过拼接SQL语句,表与表之间通过链接(inner join  left join  或者子查询),也就是在 ...

  2. wap版百度hi给你飞速的赶脚 赶紧登陆手机百度hi吧

    百度hi自然是百度自己的产品,如果你是做国内市场,这个产品应该要用一下.经常逛百度空间,有时实在受不了它的加载速度,(当然,这个跟你的网速.电脑配置.你所使用的百度空间模板等因素有关),我们看看百度空 ...

  3. cocos2d 如何优化内存使用

    如何优化内存使用 内存优化原理 为优化应用内存使用,开发人员首先应该知道什么最耗应用内存,答案就是纹理! 纹理几乎会占据90%应用内存.所以尽量最小化应用的纹理内存使用,否则应用很有可能会因为低内存而 ...

  4. Linux SUID SGID 讲解

    SUID属性 UNIX的内核是根据什么来确定一个进程对资源的访问权限的呢? 是这个进程的运行用户的(有效)ID,包括user id和group id.用户可以用id命令来查到自己的或其他用户的user ...

  5. vista/win7/win8区别

    1. Vista的内核版本号是:Windows 6.0:         Windows 7的内核是:Windows 6.1:         Windows 8的内核是:Windows 6.2   ...

  6. 【OpenStack】OpenStack系列5之Cinder详解

    源码下载安装 git clone -b stable/icehouse https://github.com/openstack/cinder.git pip install -r requireme ...

  7. 如何在android的mk文件添加依赖已经编译好的库

    用$(MY_LIB)是代表你的库的所在目录,目录结构是这样 MY_LIB |---include |-----xxx.h |-----xxx.h |---lib |----MYLIB.a LOCAL_ ...

  8. 菜单栏展开和收起效果(纯js)

    2014年6月25日 15:36:29 需要关注的是: 1.用cookie保存用户当前点击的菜单项,不打扰后端代码 2.通过数学计算得到要显示和隐藏的div 3.点击事件是动态绑定到a标签上的,因此当 ...

  9. project.json

    概述 项目相关配置,由原来的cocos2d.js中转移到project.json中,该文件需要与index.html同级,一般建议放在根目录下. 字段说明 debugMode 相当于原来的COCOS2 ...

  10. 如何给Eclipse中添加库(jar包)

    折腾Eclipse时,经常会遇到这种情况: 缺少某个库,找到之后,需要将该库,jar包,加入到当前项目,使得代码中的import xxx得以正常导入. 举例: [已解决]Eclipse的java代码出 ...