文章出處

寫在前面

  AutoMapper目錄:

  本篇目錄:

  關于AutoMapper寫到這基本的東西都差不多了,上一篇定義為靈活配置篇,本篇可以定義為擴展應用篇,加一些補充,關于AutoMapper的項目應用,網上找了幾篇英文文章,雖然看不懂,但是代碼是相通的,感覺很不錯,主要是EntityFramework中運用AutoMapper,數據訪問中使用AutoMapper,有支持的,也有反對的,也有提出建議的,自己也正在摸索,希望有機會寫篇文章和大家分享下。

  插一句:寫這些東西,看的人真的很少,還不如像前幾天大家寫篇水文,來討論下C#的好壞增加點人氣,呵呵,但是如果是這種思想來編程真是不可饒恕,寫這種文章的目的不一定是分享給別人,也是對自己學習的另一種修煉,畢竟肚子沒有什么東西,是寫不出來,也是在逼迫自己去學習,當去學習一點東西后,發現其實并不像想象的那么簡單,還有很多的東西要去學習,恨只恨自己晚生了幾年,還需努力。

Mapping Inheritance-映射繼承

  關于映射繼承,其實在“Lists and Array-集合和數組”這一節點有提到,但是只是說明下AutoMapper解決映射繼承所使用的方式,這邊我們說下關于AutoMapper在映射繼承中的一些特性,比如下面轉換示例:

1         public class Order { }
2         public class OnlineOrder : Order { }
3         public class MailOrder : Order { }
4 
5         public class OrderDto { }
6         public class OnlineOrderDto : OrderDto { }
7         public class MailOrderDto : OrderDto { }

  源對象和目標對象存在繼承關系,和“Lists and Array”節點里面的的示例一樣,我們首先要配置AutoMapper,添加類型映射關系和依賴關系,如下:

1             //配置 AutoMapper
2             Mapper.CreateMap<Order, OrderDto>()
3                   .Include<OnlineOrder, OnlineOrderDto>()
4                   .Include<MailOrder, MailOrderDto>();
5             Mapper.CreateMap<OnlineOrder, OnlineOrderDto>();
6             Mapper.CreateMap<MailOrder, MailOrderDto>();

  關于這三段代碼的意義,在“Lists and Array”節點中也有說明,如果我們注釋掉第一段代碼,我們在做派生類映射轉換的時候就會報錯,如果我們把下面兩段代碼去掉,我們在做派生類映射轉換的時候就會映射到基類,說明第一段代碼的意義是,不僅僅包含Order到OrderDto之間的類型映射,還包含Order與OrderDto所有派生類之間的映射,但是只是聲明,如果要對派生類之間進行類型映射轉換,就還得需要創建派生類之間的類型映射關系。

  我們“Lists and Array”節點中這樣執行類型映射轉換:

1     var destinations = Mapper.Map<ParentSource[], ParentDestination[]>(sources);

  上面這段轉換代碼是指定了源泛型類型(Source)和目標泛型類型類型(Dest),所以AutoMapper會根據指定的類型就可以進行轉換了,前提是類型映射關系配置正確,要不然就會報“AutoMapperConfigurationException”異常。如果我們不指定目標數據類型,然后就行轉換會怎樣呢?比如下面轉換:

1     var order = new OnlineOrder();
2     var mapped = Mapper.Map(order, order.GetType(), typeof(OrderDto));
3     Console.WriteLine("mapped Type:" + mapped.GetType());

  轉換效果:

  代碼中我們并沒有指定目標數據類型,只是指定一個派生類的基類,如果按照我們的理解,這段代碼執行的結果應該是:轉換結果mapped對象的類型應該是OrderDto類型,但是確是我們希望想要的OnlineOrderDto類型,雖然我們沒有指定目標類型為OnlineOrderDto,但是這一切AutoMapper都幫你做了,就是說AutoMapper會自動判斷目標類型與源數據類型存在的關系,并找出最合適的派生類類型。

Queryable Extensions (LINQ)-擴展查詢表達式

  注:關于Entity Framework中數據類型映射正在研究,網上找了很多英文文章,還在消化中,這一節點只是簡單的說下AutoMapper查詢表達式的用法,過幾天再整理一篇關于實體框架中運用數據類型映射的文章,功力不夠,還請包涵。

  當我們使用Entity Framework與AutoMapper結合進行查詢對象轉換的時候,使用Mapper.Map方法,就會發現查詢結果對象中的所有屬性和目標屬性對象屬性都會轉換,當然你也可以在查詢結果集中構建一個所需結果的示例,但是這樣做并不是可取的,AutoMapper的作者擴展了QueryableExtensions,使得我們在查詢的時候就可以實現轉換,比如下面示例:

 1         public class OrderLine
 2         {
 3             public int Id { get; set; }
 4             public int OrderId { get; set; }
 5             public Item Item { get; set; }
 6             public decimal Quantity { get; set; }
 7         }
 8         public class Item
 9         {
10             public int Id { get; set; }
11             public string Name { get; set; }
12         }
13 
14         public class OrderLineDTO
15         {
16             public int Id { get; set; }
17             public int OrderId { get; set; }
18             public string Item { get; set; }
19             public decimal Quantity { get; set; }
20         }
21 
22         public List<OrderLineDTO> GetLinesForOrder(int orderId)
23         {
24             Mapper.CreateMap<OrderLine, OrderLineDTO>()
25               .ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name));
26 
27             using (var context = new orderEntities())
28             {
29                 return context.OrderLines.Where(ol => ol.OrderId == orderId)
30                          .Project().To<OrderLineDTO>().ToList();
31             }
32         }

  代碼中的.Project().To就是擴展的查詢表達式,詳細表達式代碼:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Linq.Expressions;
  5 using System.Reflection;
  6 using System.Text.RegularExpressions;
  7 
  8 namespace DTO_AutoMapper使用詳解
  9 {
 10     public static class QueryableExtensions
 11     {
 12         public static ProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source)
 13         {
 14             return new ProjectionExpression<TSource>(source);
 15         }
 16     }
 17 
 18     public class ProjectionExpression<TSource>
 19     {
 20         private static readonly Dictionary<string, Expression> ExpressionCache = new Dictionary<string, Expression>();
 21 
 22         private readonly IQueryable<TSource> _source;
 23 
 24         public ProjectionExpression(IQueryable<TSource> source)
 25         {
 26             _source = source;
 27         }
 28 
 29         public IQueryable<TDest> To<TDest>()
 30         {
 31              var queryExpression = GetCachedExpression<TDest>() ?? BuildExpression<TDest>();
 32 
 33             return _source.Select(queryExpression);
 34         }        
 35 
 36         private static Expression<Func<TSource, TDest>> GetCachedExpression<TDest>()
 37         {
 38             var key = GetCacheKey<TDest>();
 39 
 40             return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression<Func<TSource, TDest>> : null;
 41         }
 42 
 43         private static Expression<Func<TSource, TDest>> BuildExpression<TDest>()
 44         {
 45             var sourceProperties = typeof(TSource).GetProperties();
 46             var destinationProperties = typeof(TDest).GetProperties().Where(dest => dest.CanWrite);
 47             var parameterExpression = Expression.Parameter(typeof(TSource), "src");
 48             
 49             var bindings = destinationProperties
 50                                 .Select(destinationProperty => BuildBinding(parameterExpression, destinationProperty, sourceProperties))
 51                                 .Where(binding => binding != null);
 52 
 53             var expression = Expression.Lambda<Func<TSource, TDest>>(Expression.MemberInit(Expression.New(typeof(TDest)), bindings), parameterExpression);
 54 
 55             var key = GetCacheKey<TDest>();
 56 
 57             ExpressionCache.Add(key, expression);
 58 
 59             return expression;
 60         }        
 61 
 62         private static MemberAssignment BuildBinding(Expression parameterExpression, MemberInfo destinationProperty, IEnumerable<PropertyInfo> sourceProperties)
 63         {
 64             var sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == destinationProperty.Name);
 65 
 66             if (sourceProperty != null)
 67             {
 68                 return Expression.Bind(destinationProperty, Expression.Property(parameterExpression, sourceProperty));
 69             }
 70 
 71             var propertyNames = SplitCamelCase(destinationProperty.Name);
 72 
 73             if (propertyNames.Length == 2)
 74             {
 75                 sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == propertyNames[0]);
 76 
 77                 if (sourceProperty != null)
 78                 {
 79                     var sourceChildProperty = sourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name == propertyNames[1]);
 80 
 81                     if (sourceChildProperty != null)
 82                     {
 83                         return Expression.Bind(destinationProperty, Expression.Property(Expression.Property(parameterExpression, sourceProperty), sourceChildProperty));
 84                     }
 85                 }
 86             }
 87 
 88             return null;
 89         }
 90 
 91         private static string GetCacheKey<TDest>()
 92         {
 93             return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName);
 94         }
 95 
 96         private static string[] SplitCamelCase(string input)
 97         {
 98             return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim().Split(' ');
 99         }
100     }    
101 }
View Code

  我們在前幾節點中說的自定義映射規則,其實也是屬于查詢表達式的一種,結合實體框架可以簡單的應用下,比如我們要映射到DTO中一個Count屬性,來計算查詢結果集中的數量,如下面代碼:

1 Mapper.CreateMap<Customer, CustomerDto>()
2     .ForMember(d => d.FullName, opt => opt.MapFrom(c => c.FirstName + " " + c.LastName))
3     .ForMember(d => d.TotalContacts, opt => opt.MapFrom(c => c.Contacts.Count()));

  LINQ支持聚合查詢,AutoMapper支持LINQ的擴展方法。在自定義映射中,如果我們講屬性名稱TotalContacts改為ContactsCount,AutoMapper將自動匹配到COUNT()擴展方法和LINQ提供程序將轉化計數到相關子查詢匯總子記錄。AutoMapper還可以支持復雜的聚合和嵌套的限制,如果LINQ提供的表達式支持它,例如下面代碼:

1 Mapper.CreateMap<Course, CourseModel>()
2     .ForMember(m => m.EnrollmentsStartingWithA,
3           opt => opt.MapFrom(c => c.Enrollments.Where(e => e.Student.LastName.StartsWith("A")).Count()));

  上面計算的是每門課程,學生名字開頭為“A”的學生數量。

  不是所有的映射選項都支持表達式,因為它必須有LINQ的支持,支持的有:

  • MapFrom
  • Ignore

  不支持的有:

  • Condition
  • DoNotUseDestinationValue
  • SetMappingOrder
  • UseDestinationValue
  • UseValue
  • ResolveUsing
  • Any calculated property on your domain object

Configuration-配置

Profile-修飾

  AutoMapper提供了個性化設置Profile,使得我們轉換后的數據格式可以多變,當然還可以配置全局格式等等,需要繼承自Profile,并重寫Configure方法,然后在AutoMapper初始化的時候,講自定義配置添加到映射配置中,如下面示例:

 1         public class Order
 2         {
 3             public decimal Amount { get; set; }
 4         }
 5         public class OrderListViewModel
 6         {
 7             public string Amount { get; set; }
 8         }
 9         public class OrderEditViewModel
10         {
11             public string Amount { get; set; }
12         }
13         public class MoneyFormatter : ValueFormatter<decimal>
14         {
15             protected override string FormatValueCore(decimal value)
16             {
17                 return value.ToString("c");
18             }
19         }
20         public class ViewModelProfile : Profile
21         {
22             protected override void Configure()
23             {
24                 CreateMap<Order, OrderListViewModel>();
25                 ForSourceType<decimal>().AddFormatter<MoneyFormatter>();
26             }
27         }

  先創建了一個MoneyFormatter字符格式化類,然后創建ViewModelProfile配置類,在Configure方法中,添加類型映射關系,ForSourceType指的是講元數據類型添加格式化,配置使用代碼:

 1         public void Example()
 2         {
 3             var order = new Order { Amount = 50m };
 4             //配置 AutoMapper
 5             Mapper.Initialize(cfg =>
 6             {
 7                 cfg.AddProfile<ViewModelProfile>(); 
 8                 cfg.CreateMap<Order, OrderEditViewModel>();
 9             });
10             //執行 mapping
11             var listViewModel = Mapper.Map<Order, OrderListViewModel>(order);
12             var editViewModel = Mapper.Map<Order, OrderEditViewModel>(order);
13 
14             Console.WriteLine("listViewModel.Amount:" + listViewModel.Amount);
15             Console.WriteLine("editViewModel.Amount:" + editViewModel.Amount);
16         }

  可以看到在Mapper.Initialize初始化的時候,把ViewModelProfile添加到AutoMapper配置中,泛型類型參數必須是Profile的派生類,因為我們在ViewModelProfile的Configure方法中添加了Order到OrderListViewModel類型映射關系,所以我們再初始化的時候就不需要添加了,轉換效果:

Naming Conventions-命名約定

  在“Flattening-復雜到簡單”節點中,我們說到AutoMapper映射轉換遵循PascalCase(帕斯卡命名規則),所以我們在類型名稱命名要按照PascalCase進行命名,除了默認的命名規則,AutoMapper還提供了一種命名規則,如下:

1 Mapper.Initialize(cfg => {
2   cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
3   cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
4 });

  SourceMemberNamingConvention表示源數據類型命名規則,DestinationMemberNamingConvention表示目標數據類型命名規則,LowerUnderscoreNamingConvention和PascalCaseNamingConvention是AutoMapper提供的兩個命名規則,前者命名是小寫并包含下劃線,后者就是帕斯卡命名規則,所以映射轉換的效果是:property_name -> PropertyName。

  當然除了在AutoMapper初始化的時候配置命名規則,也可以在Profile中添加全局配置,如下:

1 public class OrganizationProfile : Profile 
2 {
3   protected override void Configure() 
4   {
5     SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
6     DestinationMemberNamingConvention = new PascalCaseNamingConvention();
7   }
8 }

Conditional Mapping-條件映射

  AutoMapper允許在類型映射之前添加條件,例如下面示例:

 1         public class Foo
 2         {
 3             public int baz { get; set; }
 4         }
 5         public class Bar
 6         {
 7             public uint baz { get; set; }
 8         }
 9         public void Example()
10         {
11             var foo = new Foo { baz = 1 };
12             //配置 AutoMapper
13             Mapper.CreateMap<Foo, Bar>()
14                     .ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0)));
15             //執行 mapping
16             var result = Mapper.Map<Foo, Bar>(foo);
17 
18             Console.WriteLine("result.baz:" + result.baz);
19         }

  上面示例表示當源數據baz大于0的時候,才能執行映射,關鍵字是Condition,Condition方法接受一個Func<TSource, bool>類型參數,注意已經指定返回值為bool類型,方法簽名:

1         //
2         // 摘要:
3         //     Conditionally map this member
4         //
5         // 參數:
6         //   condition:
7         //     Condition to evaluate using the source object
8         void Condition(Func<TSource, bool> condition);

  轉換效果:

AutoMapper版本變化點

  在AutoMapper1.1版本中,如果我們要對類型嵌套映射中加入自定義類型映射,比如下面示例:

1     Mapper.CreateMap<Order, OrderDto>()
2           .Include<OnlineOrder, OnlineOrderDto>()
3           .Include<MailOrder, MailOrderDto>()
4           .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));
5     Mapper.CreateMap<OnlineOrder, OnlineOrderDto>()
6           .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));
7     Mapper.CreateMap<MailOrder, MailOrderDto>()
8           .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));

  可以看出,我們需要在每個類型映射的地方要加:.ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));但是Order、OnlineOrder和MailOrder存在繼承關系,難道我們如果再加一個派生類映射,就得加一段這樣代碼,這樣就會代碼就會變得難以維護。在AutoMapper2.0版本中解決了這一問題,只需要下面這樣配置就可以了:

1     Mapper.CreateMap<Order, OrderDto>()
2           .Include<OnlineOrder, OnlineOrderDto>()
3           .Include<MailOrder, MailOrderDto>()
4           .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));
5     Mapper.CreateMap<OnlineOrder, OnlineOrderDto>();
6     Mapper.CreateMap<MailOrder, MailOrderDto>();

類型映射優先級

  1. Explicit Mapping (using .MapFrom())-顯式映射:優先級最高,我們使用MapFrom方法定義映射規則,比如:.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))
  2. Inherited Explicit Mapping-繼承的顯式映射:就是存在繼承關系的MapFrom定義映射規則映射。
  3. Ignore Property Mapping-忽略屬性映射:使用Ignore方法指定屬性忽略映射,比如:Mapper.CreateMap<Source, Destination>().ForMember(dest => dest.SomeValuefff, opt => opt.Ignore());
  4. Convention Mapping (Properties that are matched via convention)-公約映射:公約映射即符合PascalCase命名規則的映射。如Source類中有Value屬性,Dest類中也有Value屬性,Source和Dest映射關系即是公約映射。
  5. Inherited Ignore Property Mapping-繼承的忽略屬性映射:優先級最低,就是存在繼承關系的忽略屬性映射。

  我們舉個簡單示例來說明下映射優先級:

 1         //Domain Objects
 2         public class Order { }
 3         public class OnlineOrder : Order
 4         {
 5             public string Referrer { get; set; }
 6         }
 7         public class MailOrder : Order { }
 8 
 9         //Dtos
10         public class OrderDto
11         {
12             public string Referrer { get; set; }
13         }
14         public void Example2()
15         {
16             //配置 AutoMapper
17             Mapper.CreateMap<Order, OrderDto>()
18                   .Include<OnlineOrder, OrderDto>()
19                   .Include<MailOrder, OrderDto>()
20                   .ForMember(o => o.Referrer, m => m.Ignore());
21             Mapper.CreateMap<OnlineOrder, OrderDto>();
22             Mapper.CreateMap<MailOrder, OrderDto>();
23 
24             //執行 Mapping
25             var order = new OnlineOrder { Referrer = "google" };
26             var mapped = Mapper.Map(order, order.GetType(), typeof(OrderDto));
27         }

  轉換后mapped對象的Referrer屬性值為“google”,但是你發現我們在配置映射規則的時候,不是把Referrer屬性給Ignore(忽略)了嗎?因為OnlineOrder的ReferrerOrderDto的Referrer屬性符合PascalCase命名規則,即是公約映射,雖然忽略屬性映射的優先級比公約映射高,但是上面示例中Order和OnlineOrder存在繼承關系,即是繼承的忽略屬性映射,所以優先級比公約映射要低。

后記

  示例代碼下載:http://pan.baidu.com/s/1comgI

  如果你覺得本篇文章對你有所幫助,請點擊右下部“推薦”,^_^

  參考資料:


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()