AutoMapper在.Net(asp.net MVC)專案下的應用以及IDataReader轉List說明

u014180504發表於2019-03-26

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有多個過載
在這裡插入圖片描述
最簡單的方式就是通過

 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中依然無法通過對映關係檢查,不過也斷有了突破

相關文章