2015年10月22日 星期四

AutoMapper運用 (二)


如果是複雜Class要Mapper時
public class Category 
{
  public int id { get; set; }
  public List<product>  products {get;set;}
}

public class Product 
{
  public int id { get; set; }
  public string name { get; set; }
  public int qantity {get;set;}
}

Category要Mapping到CategoryViewModel

public class CategoryViewModel 
{
 public int id { get; set; }
 public List<Book>  books {get;set;}
}

public class Book 
{
 public string title {get;set;}

 public int number{get;set;}
}

有兩種做法,結果都會是一樣的



  1.  Category category = new Category()
     {
      id = 1,
      products = new List<Product>()
      {
       new Product()
       {
        id = 1,
        name = "西遊戲",
        qantity =10,
       },
       new Product()
       {
        id = 2,
        name = "三國志",
        qantity =30,
       },
       new Product()
       {
        id = 3,
        name = "鹿鼎記",
        qantity =50,
       }
      }
     };
    
     Mapper.CreateMap<Category, CategoryViewModel>()
      .ForMember(d => d.id, o => o.MapFrom(s => s.id))
      .ForMember(d => d.books, o => o.MapFrom(s => s.products));
    
     Mapper.CreateMap<Product, Book>()
      .ForMember(d => d.title, o => o.MapFrom(s => s.name))
      .ForMember(d => d.number, o => o.MapFrom(s => s.qantity));
    
     
     var viewModel = Mapper.Map<CategoryViewModel>(category);
     viewModel.Dump();
    

  2. void Main()
    {
       Category category = new Category()
       {
         id = 1,
         products = new List<Product>()
         {
           new Product()
           {
             id = 1,
             name = "西遊戲",
             qantity =10,
           },
           new Product()
           {
             id = 2,
             name = "三國志",
             qantity =30,
           },
           new Product()
           {
             id = 3,
             name = "鹿鼎記",
             qantity =50,
           }
        }
     };
    
     Mapper.CreateMap<Category, CategoryViewModel>()
      .ForMember(d => d.id, o => o.MapFrom(s => s.id))
      .ForMember(d => d.books, o => o.ResolveUsing<BookResolve>().FromMember(s => s.products));
    
     var viewModel = Mapper.Map<CategoryViewModel>(category);
     viewModel.Dump();
    }
    
    public class BookResolve : ValueResolver<IList<Product>, IList<Book>> 
    {
     protected override IList<Book> ResolveCore(IList<Product> source) 
     {
        List<Book> books = new List<Book>();
        foreach (var item in source)
        {
          var book = new Book() 
          {
            number = item.qantity,
            title = item.name
          };
          books.Add(book);
        }
        return books;
     }
    }
    

AutoMapper運用


寫網站常常會遇到從ViewModel對應到應用程式的DTO,或是DB的物件問題,以前自己的做法都是

public bool Login(LoginViewModel model)
{
   Account account = new Account()
   {
      Account = model.Account,
      Password = model.Password
   }
   var Result = _AccountService.Loign(account );
   return Result;
}

public class LoginViewModel
{
  public string Account{get;set;}
  public string Password{get;set;}
}


等到內部運作完畢的時候可能又要把對應的DTO處理成ViewModel在拋回頁面,這種反覆一直寫的動作其實真的很煩人,直到最近同事介紹了AutoMapper給我們之後,整個流程得到了大幅度的改善。


  • AutoMapper的基本運用

    當兩個物件的屬性都一樣時,AutoMapper會自動去對應並且轉出




    如果兩個物件的屬性對應略有不同,也可以加上規則

  • 如果兩的類別對應到同一個物件的情況

    void Main()
    {
     AccountViewModel model = new AccountViewModel() 
     {
      Account = "toyo",
      Password = "pwd",
      City = "Taipei",
      District = "大安"
     };
     var Group = new Group() 
     {
      ID = 1,
      Name = "客戶群組"
     };
     
     Mapper.CreateMap<AccountViewModel,AccountDTO>()
      .ForMember(d=> d.Address , o=>o.MapFrom(s=>string.Concat(s.City,s.District)));  
     var account = Mapper.Map<AccountDTO>(model);
    
        //這邊用上面已經轉好的account,來接續Mapper
     Mapper.CreateMap<Group, AccountDTO>()
      .ForMember(d => d.Account, o => o.Ignore())
      .ForMember(d => d.Password, o => o.Ignore())
      .ForMember(d => d.Address, o => o.Ignore())
      .ForMember(d => d.GroupName, o => o.MapFrom(s=>s.Name));
        //注意
     account = Mapper.Map<Group,AccountDTO>(Group,account);
    
     account.Dump();
    }
    
    public class AccountViewModel
    {
     public string Account { get; set; }
     public string Password { get; set; }
     public string City { get; set; }
     public string District {get;set;}
    }
    
    public class AccountDTO
    {
     public string Account { get; set; }
     public string Password { get; set; }
     public string Address { get; set; }
     public string GroupName {get;set;}
    }
    
    public class Group 
    {
     public int ID { get; set; }
     public string Name {get;set;}
    }
    

    結果


最後,在MVC的網站裡面使用AutoMapper可以在把所有規則都先寫在ProFile裡面,然後在Global一次註冊,之後在程式裡面用到就不用每次再寫CreateMap的部分

Profile :
public class ControllerMappingProfile : Profile
{
 public override string ProfileName
 {
  get
  {
   return "ControllerMappingProfile";
  }
 }

 protected override void Configure()
 {
  Mapper.CreateMap<AccountViewModel, AccountDTO>()
   .ForMember(d => d.Address, o => o.MapFrom(s => string.Concat(s.City, s.District)));

  Mapper.CreateMap<Group, AccountDTO>()
      .ForMember(d => d.Account, o => o.Ignore())
      .ForMember(d => d.Password, o => o.Ignore())
      .ForMember(d => d.Address, o => o.Ignore())
      .ForMember(d => d.GroupName, o => o.MapFrom(s => s.Name));
 }
}


AutoMapperConfig :
public class AutoMapperConfig
    {
        public static void Configure()
        {
            Mapper.Initialize(x =>
            {
                x.AddProfile<ControllerMappingProfile>();
            });
        }
    }


最後在Global Application_Start註冊即可
protected void Application_Start()
{
   AutoMapperConfig.Configure();
}

2015年10月21日 星期三

LinqToSql ExcuteQuery回傳Binary的問題


最近發現用LinqToSQL的物件模型,使用ExecuteQuery<Binary>的方式執行SQL會回傳
型別'System.Data.Linq.Binary' 必須宣告預設(無參數)建構涵式,才能在對應時加以建構,
的問題

實際案例如下:

  • 先在DB裡建立一個Function
    create function [dbo].[fb_CreatePWD]
    (
      @Param1 varchar(150)
    )
    RETURNS varbinary (150)
    AS
    BEGIN
      DECLARE @ResultVar varbinary(150)
      
      select @ResultVar = pwdencrypt(@Param1)
    
      RETURN @ResultVar
    END
    
    
    
  • 接著在MSSMS上測試是可以正常的

  • 然後移到LinqToSql的DataContext試試看
    var result = this.ExecuteQuery("select dbo.fn_CreatePWD({0})","ok").FirstOrDefault();
    
    得到以下錯誤訊息
    型別 'System.Data.Linq.Binary' 必須宣告預設 (無參數) 建構函式,才能在對應時加以建構

  • 網路上找到有人說用byte[]接可以,所以立馬做一下測試
    
    
    var result = this.ExecuteQuery("select dbo.fn_CreatePWD({0})","ok").FirstOrDefault();
    
    結果一樣GG, 型別 'System.Byte[]' 必須宣告預設 (無參數) 建構函式,才能在對應時加以建構。


  • 接著改用Entity Framework 6.0來試試看
  • 一樣不行
  • 換成Byte[]就可以了!!!

所以還是趕快放棄LinqToSql這個被放棄掉的產品,投入Entity Framwork的懷抱吧XD

*
這邊特別備註一下,如果今天是直接用物件模型將function拉進來,並且直接對xxxDataContext.fn_CreatePWD()操作是可以運作沒問題.

但因為我今天的狀況是在Repository Pattern的情況下,並不會真的對實體做操作,而是全部都對基底類別DataContext做執行,所以沒有這個Method可以使用

我想以上狀況應該是LinqToSql的Bug吧!!網路上也查了滿久的資料,好像沒有人對這塊有比較好的處理方式,如果有再補充上來~