AutoMapper在.Net(asp.net MVC)项目下的应用以及IDataReader转List说明

AutoMapper在.Net(asp.net MVC)项目下如何配置和应用的呢?我们首先说配置初始化

AutoMapper在.Net(asp.net MVC)项目下的应用

首先在应用启动时要注册映射的文件,直接在Global.asax中的Application_Start类中注册即可 这里我们将注册的具体方法写在了 AutoMapperUtil.RegisterAutoMapper工具类中:

 protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
         
            //注册AutoMapper配置
            AutoMapperUtil.LoadConfig();
        }

工具类如下:

/// <summary>
    /// 自动映射工具
    /// </summary>
    public static class AutoMapperUtil
    {
        /// <summary>
        /// 从配置文件加载
        /// </summary>
        /// <param name="sectionName">节点名称</param>
        public static void LoadConfig(string sectionName = "autoMapper")
        {
            var config = ConfigurationManager.GetSection(sectionName) as NameValueCollection;
            if (config != null)
            {
                var assemlies = config.Keys
                    .Cast<string>()
                    .Select(e => config[e])
                    .Where(e => !string.IsNullOrEmpty(e))
                    .ToArray();
                Mapper.Initialize(cfg => {
                    //支持datareader to list
                    cfg.AddDataReaderMapping();
                    cfg.AddProfiles(assemlies);
                });
            }
            Mapper.AssertConfigurationIsValid();
        }
    }

这里要说明**册的方式也有多种,AddProfiles有多个重载
AutoMapper在.Net(asp.net MVC)项目下的应用以及IDataReader转List说明
最简单的方式就是通过

 Mapper.Initialize(cfg =>
 {
          cfg.AddProfile<ModelMapperProfile>();
 });
 public class ModelMapperProfile : Profile
 {
   CreateMap<Book, BookView>()
 }

本人使用的是 void AddProfiles(params Assembly[] assembliesToScan); 这个方式注册程序集下的Profiles,其原理是注册程序集后,程序会自动化扫面程序集下所有继承Profile的类里的映射信息进行注册,代码如下:

 public static void LoadConfig(string sectionName = "autoMapper")
        {
            var config = ConfigurationManager.GetSection(sectionName) as NameValueCollection;
            if (config != null)
            {
                var assemlies = config.Keys
                    .Cast<string>()
                    .Select(e => config[e])
                    .Where(e => !string.IsNullOrEmpty(e))
                    .ToArray();
                Mapper.Initialize(cfg => {
                    cfg.AddProfiles(assemlies);
                });
            }
            Mapper.AssertConfigurationIsValid();
        }

配置文件:

<configSections>
    <section name="autoMapper" type="System.Configuration.NameValueSectionHandler, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </configSections>
 <autoMapper>
    <!--键为程序集简称,可随意命名,不重复即可,值为程序集名称-->
    <add key="ProxyService" value="CSP.RE.Repository" />
  </autoMapper>

这样我的映射类就可以创建在CSP.RE.Repository下任何位置。
到这里注册就完成了,在进行model关系映射时会使用一些通用的帮助类

        ///协变性:派生程度较大类型分配(赋值)给派生程度较小类型
        /// <summary>
        /// 映射到目标类型数据
        /// </summary>
        /// <typeparam name="TMapperType"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static TDestination MapTo<TDestination>(this object value) where TDestination : class
        {
            if (value == null)
                return default(TDestination);
            return Mapper.Map<TDestination>(value);
        }

        /// <summary>
        /// 映射到目标类型数据集合
        /// </summary>
        /// <typeparam name="TDestination">目标类型</typeparam>
        /// <param name="values">源类型集合</param>
        /// <returns></returns>
        public static IEnumerable<TDestination> MapTo<TDestination>(this IEnumerable values) where TDestination : class
        {
            if (values == null) return new List<TDestination>();
            return Mapper.Map<IEnumerable<TDestination>>(values);
        }


        /// <summary>  
        /// 将 DataTable 转为实体对象  
        /// </summary>  
        /// <typeparam name="T"></typeparam>  
        /// <param name="dt"></param>  
        /// <returns></returns>  
        public static IEnumerable<TDestination> MapTo<TDestination>(this DataTable dt) where TDestination : class
        {
            if (dt == null || dt.Rows.Count == 0)
                return new List<TDestination>();
            return Mapper.Map<IEnumerable<TDestination>>(dt.CreateDataReader());
        }

        /// <summary>  
        /// 将 DataSet 转为实体对象  
        /// </summary>  
        /// <typeparam name="T"></typeparam>  
        /// <param name="ds"></param>  
        /// <returns></returns>  
        public static IEnumerable<TDestination> MapTo<TDestination>(this DataSet ds) where TDestination : class
        {
            if (ds == null || ds.Tables.Count == 0 || ds.Tables[0].Rows.Count == 0)
                return new List<TDestination>();
            return Mapper.Map<IEnumerable<TDestination>>(ds.Tables[0].CreateDataReader());
        }

这里就不在多说

IDataReader转List说明

在项目中我需要将datatable以及dataset转成List ,查了下资料 automapper官网有独立的项目支撑其实现AutoMapper.Data,需要在nuget中安装AutoMapper.Data包

然后注册的地方有点变化:

public static void LoadConfig(string sectionName = "autoMapper")
        {
            var config = ConfigurationManager.GetSection(sectionName) as NameValueCollection;
            if (config != null)
            {
                var assemlies = config.Keys
                    .Cast<string>()
                    .Select(e => config[e])
                    .Where(e => !string.IsNullOrEmpty(e))
                    .ToArray();
                Mapper.Initialize(cfg => {
                    cfg.AddProfiles(assemlies);
                });
            }
            Mapper.AssertConfigurationIsValid();
        }

变成:

 public static void LoadConfig(string sectionName = "autoMapper")
        {
            var config = ConfigurationManager.GetSection(sectionName) as NameValueCollection;
            if (config != null)
            {
                var assemlies = config.Keys
                    .Cast<string>()
                    .Select(e => config[e])
                    .Where(e => !string.IsNullOrEmpty(e))
                    .ToArray();
                Mapper.Initialize(cfg => {
                    //支持datareader to list
                    cfg.AddDataReaderMapping();
                    cfg.AddProfiles(assemlies);
                });
            }
            Mapper.AssertConfigurationIsValid();
        }

这里多了一个cfg.AddDataReaderMapping(); 这个就是注册支持datareader to list的
dome和映射关系注册:

 public class DevicewareDto
    {
        public string DeviceNumber{ get; set; }
        public string SIMCCID { get; set; }
    }
   Mapper.Initialize(cfg =>
	  {
	    cfg.AddDataReaderMapping();
	       cfg.CreateMap<IDataReader, DevicewareDto>();
	   });
	
	   DataTable dt = new DataTable();
	   dt.Columns.Add("DeviceNumber", typeof(string));
	   dt.Columns.Add("SIMCCID", typeof(string));
	 
	   for (int i = 0; i < 10; i++)
	   {
	       var newRow = dt.NewRow();
	       newRow["DeviceNumber"] = "DeviceNumber";
	       newRow["SIMCCID"] = "SIMCCID";
	       dt.Rows.Add(newRow);
	   }
       var list = Mapper.Map<IEnumerable<DevicewareDto>>(dt.CreateDataReader());
----------------------------------------------------------------------------------------------------------
  Mapper.Initialize(cfg =>
   {
           cfg.AddDataReaderMapping();
           cfg.CreateMap<IDataReader, DevicewareDto>();
       });
       Mapper.AssertConfigurationIsValid();

       DataSet ds = new DataSet();
       DataTable dt = new DataTable();
       dt.Columns.Add("DeviceNumber", typeof(string));
       dt.Columns.Add("SIMCCID", typeof(string));

       for (int i = 0; i < 10; i++)
       {
           var newRow = dt.NewRow();
           newRow["DeviceNumber"] = "DeviceNumber";
           newRow["SIMCCID"] = "SIMCCID";
           dt.Rows.Add(newRow);
       }
       ds.Tables.Add(dt);
       var list = Mapper.Map<IEnumerable<DevicewareDto>>(ds.CreateDataReader());

但是有两个问题

注册映射关系

在控制台程序中 cfg.CreateMap<IDataReader, DevicewareDto>();加不加这句注册的代码都不会有问题没问题的,但是在mvc中加了是会报错的,错误提示是从IDataReader到DevicewareDto关系映射时没有指定FNumber和SIMCCID的映射关系,去掉之后能完成映射且不报错,这个点感觉很诡异 暂时没有分析出原因,后续会继续分享。

同名与不同名属性之间的映射

目前只能支持类的属性和列名是相同之间映射 不然是映射不出来值的,看了好多方案还是没有找到合适的解决方案 即使使用ForMember去指定映射关系(列名)也是不可的 只能转换出来同名属性的值, 那就换了种思路去解决,使用DataRow来实现datatable转List
注册信息如下:

  CreateMap<DataRow, KnowledgeOverview>()
              .ForMember(x => x.Title, opt => opt.MapFrom(src => src["Title"]))
                 .ForMember(x => x.AbStract, opt => opt.MapFrom(src => src["Summary"]))
                 .ForMember(x => x.Authors, opt => opt.MapFrom(src => src["Creator"]))
                 .ForMember(x => x.KeyWords, opt => opt.MapFrom(src => src["KeyWords"]))
                 .ForMember(x => x.Organizations, opt => opt.MapFrom(src => src["Contributor"]))
                 .ForMember(x => x.Fund, opt => opt.MapFrom(src => src["Fund"]))
                 .ForMember(x => x.Source, opt => opt.MapFrom(src => src["Source"]))
                 .ForMember(x => x.PublishDate, opt => opt.MapFrom(src => src["DATE"]));

这样就可以实现属性名和列名不同的值映射 但是也存在一些问题 就是即使是同名列也要使用ForMember()指定映射关系 不然就出不来值,这个大家要注意 !!!

补充说明关于 同名与不同名属性之间的映射的问题

通过研究官方的issus发现 有网友使用了IDataRecord实现了通过ForMember只要指定不同命属性就可以完成映射,在控制台尝试了一下确实是可以的,代码如下:

 public class DevicewareDto
    {
       public string FNumber { get; set; }
       public string SIMCCID { get; set; }
    }
  Mapper.Initialize(cfg =>
 {
      cfg.AddDataReaderMapping();
      cfg.CreateMap<IDataRecord, DevicewareDto>()
      .ForMember(dest => dest.FNumber, opt => opt.MapFrom(src => (string)src["DeviceNumber"]));
  });
  Mapper.AssertConfigurationIsValid();
 
  DataSet ds = new DataSet();
  DataTable dt = new DataTable();
  dt.Columns.Add("DeviceNumber", typeof(string));
  dt.Columns.Add("SIMCCID", typeof(string));

  for (int i = 0; i < 10; i++)
  {
      var newRow = dt.NewRow();
      newRow["DeviceNumber"] = "DeviceNumber"+ i;
      newRow["SIMCCID"] = "SIMCCID"+ i;
      dt.Rows.Add(newRow);
  }
  ds.Tables.Add(dt);

var list = Mapper.Map<IEnumerable<DevicewareDto>>(ds.CreateDataReader());

  //DataTable dt = new DataTable();
  //dt.Columns.Add("DeviceNumber", typeof(string));
  //dt.Columns.Add("SIMCCID", typeof(string));

  //for (int i = 0; i < 10; i++)
  //{
  //    var newRow = dt.NewRow();
  //    newRow["DeviceNumber"] = "DeviceNumber";
  //    newRow["SIMCCID"] = "SIMCCID";
  //    dt.Rows.Add(newRow);
  //}
  //var list = Mapper.Map<IEnumerable<DevicewareDto>>(dt.CreateDataReader());

以为一切皆大欢喜 结果在MVC中依然无法通过映射关系检查,不过也断有了突破