0%

之前寫了一個簡單上傳圖片的功能,碰到用await跟async非同執行的問題,本來在本機Visual Studio偵錯執行Run都好好的,但是放到線上後就永遠得不到回應TimeOut

隱隱約約記得之前黑大寫過類似的文章,當時看過沒啥特別感覺,翻回來看後在實際操作後終於弄懂了(果然寫程式很多地方一知半解會GG的),關於Async、Await這些推薦可以看這兩篇文章

Huan-Lin 學習筆記-async 與 await
黑暗執行緒- await與Task.Result/Task.Wait()的Deadlock問題

兩位前輩大大已經寫的很完整詳盡了,在這邊就不獻醜了,只簡單提供個範例給有興趣的人玩玩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class DeadLockController : ApiController
{
[HttpGet]
[Route("api/DeadLock/Index")]
public HttpResponseMessage Index()
{
Task<int> mytask = Go();
mytask.Wait();
return Request.CreateResponse(mytask.Result);
}

[HttpGet]
[Route("api/DeadLock")]
public HttpResponseMessage DeadLock()
{
var result = Go().Result;
return Request.CreateResponse(result);
}

[HttpGet]
[Route("api/DeadLock/NoDeadLock")]
public async Task<HttpResponseMessage> NoDeadLock()
{
var result = await Go();
return Request.CreateResponse(result);
}

private async Task<int> Go()
{
int res = 0;
await Task.Run(() =>
{
//假裝執行某個耗時程序後取得結果
Task.Delay(1000);
res = 32767;
});
return res;
}
}

這邊只有NoDeadLock那個Action的寫法才不會DeadLock,其餘兩個永遠都不會得到回應,原因簡單的說就是在Web中**(特別強調在Web中,因為Console並不會)**,.Result 與 Wait()的方法都會鎖定目前的Thread等待結果回應再繼續執行,而上面的範例GO()這個Method在Web中會記住是哪個Thread來呼叫他的,以便他執行完後回應該Thread結果繼續執行。

這時候就會發生DeadLock了,因為外面用Wait() or .Result鎖住當前Thread,GO()這個Method又在等待他被釋放好跟他回應結果,互相等待也就DeadLock了。

前面兩篇文章很久以前就看過了,只是當時沒有特別放在心上,直到被這個簡單到只有5行Code的東西困擾了3~4天,才終於想到有這件事情,現在不懂沒關係,等到被鎖一次就懂了(吶喊!!)

以前寫Linq的時候常常碰到一個問題,就是判斷一個條件是否達成來決定Linq的Where條件該如何組合,
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void Main()
{
bool Flag = true;
List<DealDataForGov> SourceData = new List<DealDataForGov>
{
new DealDataForGov
{
Address = "台北市xxxx",
HouseKind = "a1",
Year = 2015
},
new DealDataForGov
{
Address = "高雄市xxxx",
HouseKind = "a1",
Year = 2016
},
new DealDataForGov
{
Address = "台中市xxxx",
HouseKind = "c1",
Year = 2014
}
}

if (Flag)
{
SourceData.Where((x) => x.HouseKind == "a1");
}
else
{
//因為某個條件,新增新的搜尋條件
SourceData.Where((x) => x.HouseKind =="a1" && x.Year >= 2015 );
}
}

public class DealDataForGov
{
public string Address { get; set; }
public string HouseKind { get; set; }
public int Year {get;set;}
}

條件少還算好寫,一旦條件落落長的時候就會貼的到處都是,不僅彈性降低也讓維護增加困難度,還好在黑大那邊看到LINQKIT這個好東西黑暗執行緒-組裝動態LINQ條件的利器-LINQKit

以下簡短介紹一下這個LINQKit的套件,有了他之後感覺Code都活過來了XD
範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void Main()
{
List<TargetModel> SourceData = new List<TargetModel>
{
new TargetModel
{
Address = "台北市xxxx",
HouseKind = "a1",
Year = 2015
},
new TargetModel
{
Address = "高雄市xxxx",
HouseKind = "a1",
Year = 2016
},
new TargetModel
{
Address = "台中市xxxx",
HouseKind = "c1",
Year = 2014
}
};

var expression = GetWhereExpression();

//SourceData.Where((x) => x.HouseKind == "a1");
var e1 = SourceData.Where(expression.Compile());
e1.Dump();

//SourceData.Where((x) => x.HouseKind =="a1" && x.Year > 2015 );
var e2 = SourceData.Where(expression.And(x => x.Year > 2015).Compile());
e2.Dump();

//SourceData.Where((x) => x.HouseKind =="a1" || x.Year >=2014 );
var e3 = SourceData.Where(expression.Or(x => x.Year >= 2014).Compile());
e3.Dump();
}

public Expression<Func<TargetModel, bool>> GetWhereExpression()
{
var pred = PredicateBuilder.True<TargetModel>();
pred = pred.And(x=> x.HouseKind =="a1");
return pred;
}

public class TargetModel
{
public string Address { get; set; }
public string HouseKind { get; set; }
public int Year {get;set;}
}


上面有三個範例分別是替代掉註解的寫法,如果有碰到很多條件會動態產生的結果,用LinqKit可以大幅度省掉很多Code以及維護上的複雜度。算是非常好用的小物,推薦給大家

有張表格如下,Parent欄位代表他的父層的RegionID,所以裡面包含所有縣市、行政區、鄉里等資料,那要如何用SQL遞迴的方式把台北市用階層的方式表列出來呢?
台北市 > 信義區、大安區、中正區…. > 港華里、老泉里…..

用CTE的寫法可以解決,把台北市當做茅點,然後Join自己即可達到遞迴的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
with 遞迴 as(
------茅點 start-------
SELECT RegionID , Name
FROM Region a
WHERE RegionID = 1 --台北市
------茅點 end ---------
union all
select b.RegionID , b.name
from Region b
join 遞迴 on b.Parent = 遞迴.RegionID
)
select * from 遞迴

搜尋出來的結果

ActionAttribute

環境 : WebAPI 2.2 詳細

在WebAPI中,通常都繪有需要客製化ActionFilter的情況發生

執行順序不固定

ActionFilter的執行順序並非在上面的就優先執行,Filter有個FilterScope來表示他屬於何種層級的ActionFilter,執行順序依次是 Global , Controller , Action 。但如果是在同一層級的ActionFilter順序則未必按照固定。

https://2.bp.blogspot.com/-vZ-NniyEom4/VnIx1sGnvcI/AAAAAAAAHro/-ykVRvzvq3Y/s1600/1.png]

測試

https://4.bp.blogspot.com/-LiCgWC_Ob3M/VnIyiMCn0nI/AAAAAAAAHr0/H58PiNpUUvA/s1600/1.png

裡面全部都一樣繼承ActionFilterAttribute即可

1
public class FourAttribute :ActionFilterAttribute{ }

註冊OneAttribute到Global
https://2.bp.blogspot.com/-oCMhVi2M5_w/VnIzKgzvzrI/AAAAAAAAHsA/e9vuB3fiZE4/s1600/1.png

將其它Attribute分別註冊到Controller與Action
https://1.bp.blogspot.com/-VZm61VIoQZY/VnIzc88dwGI/AAAAAAAAHsM/piz8hD4vdXo/s1600/1.png

在Get中補上以下程式然後執行看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[HttpGet]
[Three]
[Four]
public IEnumerable<Tuple<string,string>> Get()
{
IHttpActionSelector actionSelector =
this.Configuration.Services.GetActionSelector();

HttpActionDescriptor actionDescriptor =
actionSelector.SelectAction(this.ControllerContext);

foreach (FilterInfo filterInfo in actionDescriptor.GetFilterPipeline())
{
yield return new Tuple<string, string>(
filterInfo.Instance.GetType().Name,
filterInfo.Scope.ToString()
);
}
}

測試結果

https://2.bp.blogspot.com/-ExqARkhthR8/VnI0IaZEwvI/AAAAAAAAHsY/iSNXKBQFZF0/s1600/1.png

排序依據所對應的層級,雖然這邊排序與我們在程式上的排序相同。但實際上順序同層級的順序未必是由上而下,為了避免這樣的問題,最好的方法就是在ActionFilter加上排序的屬性來解決。

實作Order屬性

定義IOrderAttribute

1
2
3
4
5
6
7
8
9
public interface IOrderAttribute
{
int Order { get; set; }
}

public class FourAttribute : ActionFilterAttribute, IOrderAttribute
{
public int Order { get; set; }
}

實做IComparable介面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class CustomFilterInfo : IComparable
{
public IFilter Instance { get; set; }
public FilterScope Scope { get; set; }

//FilterInfo
public CustomFilterInfo(IFilter instance, FilterScope scope)
{
this.Instance = instance;
this.Scope = scope;
}

public int CompareTo(object obj)
{
if (obj is CustomFilterInfo)
{
var item = obj as CustomFilterInfo;

if (item.Instance is IAttribute)
{
var attr = item.Instance as IAttribute;
return (this.Instance as IAttribute).Order.CompareTo(attr.Order);
}
}

return 0;
}

public FilterInfo ConvertToFilterInfo()
{
return new FilterInfo(this.Instance, this.Scope);
}
}

實做IFilterProvider介面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CustomFilterProvider : IFilterProvider
{
public IEnumerable<FilterInfo> GetFilters(
HttpConfiguration configuration,
HttpActionDescriptor actionDescriptor)
{
IEnumerable<CustomFilterInfo> customActionFilters =
actionDescriptor.GetFilters()
.Select(i => new CustomFilterInfo(i, FilterScope.Controller));

IEnumerable<CustomFilterInfo> customControllerFilters =
actionDescriptor.ControllerDescriptor
.GetFilters()
.Select(i => new CustomFilterInfo(i, FilterScope.Controller));

return customControllerFilters.Concat(customActionFilters)
.OrderBy(i => i)
.Select(i => i.ConvertToFilterInfo());

}
}

Global註冊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();

//註冊Global Attribute
GlobalConfiguration.Configuration.Filters.Add(new OneAttribute());


//新增CustomFilterProvider
GlobalConfiguration.Configuration.Services.Add(
typeof(System.Web.Http.Filters.IFilterProvider), new CustomFilterProvider());

var providers = GlobalConfiguration.Configuration.Services.GetFilterProviders();
var defaultprovider = providers.First(i => i is ActionDescriptorFilterProvider);

//移除DefaultProvider
GlobalConfiguration.Configuration.Services.Remove(
typeof(System.Web.Http.Filters.IFilterProvider),
defaultprovider);
}

測試

將Attibute補上Order再執行看看得到的順序

https://1.bp.blogspot.com/-UucLgmkDBeE/VnJTKlpyjVI/AAAAAAAAHso/5-37Ww458bE/s1600/1.png

可以看到Four排在Three的前面了

https://4.bp.blogspot.com/-YndO7jfF4M0/VnJTkNiXDzI/AAAAAAAAHs0/S6HXWfK1_ks/s1600/1.png

使用套件:
Json.NET 7.0.1 : https://www.nuget.org/packages/Newtonsoft.Json/7.0.1
AutoMapper 4.04 : https://www.nuget.org/packages/AutoMapper/4.0.4

今天碰到一個問題,就是有個API回傳值的欄位是不固定無法掌握的,所以只好在轉型成強型別時以object當做該屬性的類別,但JsonConvert碰到類別為Object的東西就會轉成JObject ,而AutoMapper對應JObject會炸掉。以下是簡單時間的範例Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void Main()
{
Mapper.CreateMap<source, destination>()
.ForMember(d => d.d_name, o => o.MapFrom(s => s.name))
.ForMember(d => d.d_obj, o => o.MapFrom(s => s.obj));

source test = new source
{
name = "test",
obj = new {code = 100},
};

var result = Mapper.Map<destination>(test);
result.Dump();
}

public class source
{
public string name { get; set; }
public object obj { get; set; }
}

public class destination
{
public string d_name { get; set; }
public object d_obj { get; set; }
}

Source與Destination都有個property為object的類別屬性,在Main()裡面也想好兩個類別的對應關係,並且先準備好Source 然後透過AutoMapper轉出Result,在以上的範例執行正確沒問題

換個寫法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void Main()
{
Mapper.CreateMap<source, destination>()
.ForMember(d => d.d_name, o => o.MapFrom(s => s.name))
.ForMember(d => d.d_obj, o => o.MapFrom(s => s.obj));

source test = JsonConvert.DeserializeObject<source>("{\"name\":\"test\",\"obj\" : {\"code\":100}}");
var result = Mapper.Map<destination>(test);
result.Dump();
}

public class source
{
public string name { get; set; }
public object obj { get; set; }
}

public class destination
{
public string d_name { get; set; }
public object d_obj { get; set; }
}

差別只在於原本Source改成透過Json.Net由字串轉回來而已,這時候只要執行到AutoMapper那一行就會爆炸,錯誤訊息如下

AutoMapperMappingException:

Mapping types:
JObject -> JObject
Newtonsoft.Json.Linq.JObject -> Newtonsoft.Json.Linq.JObject

Destination path:
destination.d_obj.d_obj

Source value:
{
“code”: 100
}

JsonConvert碰到目標為Object型別的欄位,會轉成JObject塞進去,AutoMapper用它來對應,
所以如果要解決這個問題需要做一些處理

1
2
3
4
5
6
7
8
9
10
11
12
13
//將Mapper改成如下
Mapper.CreateMap<source, destination>()
.ForMember(d => d.d_name, o => o.MapFrom(s => s.name))
.ForMember(d => d.d_obj, o => o.ResolveUsing(s =>
{
if (s.obj is JObject)
{
var temp = s.obj as JObject;
return temp.ToObject<Dictionary<string, object>>();
}
return s.obj;
}));

這樣就可以正確地取出了

此篇範例程式 : 下載  (此範例在DynamicController之中)

呈上一篇,EditorTemplate (一) 我們可以來看到產生的原始碼如下

Name的部分是由 productList[index].屬性名組成,換句話說,如果今天要由前端動態新增一筆書籍資料,則必須按照這個規則編排下去,後端才能透過ViewModel的方式取得書籍資料

看起來一切美好圓滿,但如果今天的列表是可以新增之外,還要能動態刪除呢?
是不是我的[index]就要一直重新計算,不然送到後端就會不見了

*如果今天有4個textbox但是name分別是
productList[0].id
productList[1].id
productList[2].id
productList[5].id
這樣後端只會拿到0~2的ID,不按照順序編排的就會消失

還好.NET其實提供另外一種方式來繫結ViewModel

1
2
3
<input type="hidden" name="productList.Index" value="0072b890-0e1a-4c93-a5a7-9cafe84b65f8" /> 
<input id="productList_0072b890-0e1a-4c93-a5a7-9cafe84b65f8__id" name="productList[0072b890-0e1a-4c93-a5a7-9cafe84b65f8].id" type="text" >

這樣的話就可以不用管排序,自由的新增刪除List的項目,但EditorTemplate也需要改一改,不然EditTemplate產出的是[0]這種格是,但是前端產出的格是是[Guid],這樣會有問題。

所以EditTemplate改成用這個方法產出
*以下程式碼轉載自:  
搞搞就懂 - 點部落 :[ASP Net MVC] 如何綁定可動態新增或移除之資料集合(EditorTemplate)

  • 新增一組HtmlHelper ```csharp
    public static MvcHtmlString EditorForMany<TModel, TValue>(

          this HtmlHelper<TModel> html,
          Expression<Func<TModel, IEnumerable<TValue>>> expression,
          string htmlFieldName = null) where TModel : class
      {
          var items = expression.Compile()(html.ViewData.Model);
          var sb = new StringBuilder();
          var hasPrefix = false;
    
              if (String.IsNullOrEmpty(htmlFieldName))
          {
              var prefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
              hasPrefix = !String.IsNullOrEmpty(prefix);
              htmlFieldName = (prefix.Length > 0 ? (prefix + ".") : String.Empty) + ExpressionHelper.GetExpressionText(expression);
          }
    
              if (items != null)
          {
              foreach (var item in items)
              {
                  var dummy = new { Item = item };
                  var guid = Guid.NewGuid().ToString();
    
                      var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
                  var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, expression.Parameters);
    
                      sb.Append(String.Format(@"<input type=""hidden"" name=""{0}.Index"" value=""{1}"" />", htmlFieldName, guid));
                  sb.Append(html.EditorFor(singleItemExp, null, String.Format("{0}[{1}]", hasPrefix ? ExpressionHelper.GetExpressionText(expression) : htmlFieldName, guid)));
              }
          }
          return new MvcHtmlString(sb.ToString());
      }
    

    }

1
2
3
4
5

* 原本EditorFor改成自訂的EditorForMany ```html
@Html.EditorFor(x => x.productList)
@Html.EditorForMany(x => x.productList)

這樣產出來的格式就會是帶Guid的方式了,可以正常跟前端結合了

Git範例檔下載位置 : https://github.com/toyo0103/Demo_EditTemplate_1

MVC在View的部分要呈現資料,通常是用ViewModel的方式來傳遞。當碰到List或是Array這種重複多筆的情況時,常常就會用迴圈的方式去跑

例如:

假設有個類別CategoryViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CategoryViewModel
{
public Guid id { get; set; }
public string name { get; set; }
public List<product> productList {get;set;}
}

public class product
{
public Guid id { get; set; }
public string name {get;set;}
}

在View上要呈現CategoryViewModel.productList就可能用跑回圈的方式去組成

1
2
3
4
5
6
7
8
9
10
@model CategoryViewModel
@Html.TextBoxFor(x => x.id)
@Html.TextBoxFor(x => x.name)
//ProductList
@for(int i = 0; i < Model.productList.Count; i++)
{
@Html.TextBoxFor(x => Model.productList[i].id)
@Html.TextBoxFor(x => Model.productList[i].name)
}

這樣寫起View來有種又臭又長的感覺,所以可以使用MVC提供的EditorTemplate來解決,使用方法如下:

  • 在Views > Shared > 新增一個EditorTemplates資料*注意 這邊資料夾名稱一定要對
    [![](http://2.bp.blogspot.com/-rtO8iQJhVEE/VlUQCtIp2YI/AAAAAAAAHo8/kz86QML4Yn8/s1600/1.png)](http://2.bp.blogspot.com/-rtO8iQJhVEE/VlUQCtIp2YI/AAAAAAAAHo8/kz86QML4Yn8/s1600/1.png)
  • 接著在裡面新增一個product.cshtml,這邊一樣檔案名稱必須跟Class名稱相符才行
  • 然後開始編輯product.cshtml
    1
    2
    3
    4
    @model  product
    @Html.TextBoxFor(x => x.id)
    @Html.TextBoxFor(x => x.name)

  • 接著回到CategoryViewModel那邊迴圈的地方,把迴圈改成這一行即可 ```csharp
    @Html.EditorFor(x=>x.productList)

如果是複雜Class要Mapper時

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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. ```csharp
    Category category = new Category()
    {
    id = 1,
    products = new List()
    {
    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(category);
viewModel.Dump();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

2. ```csharp
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;
}
}

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

```csharp 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;}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62


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

* AutoMapper的基本運用

當兩個物件的屬性都一樣時,AutoMapper會自動去對應並且轉出
[![](http://4.bp.blogspot.com/-j6G1kaI6BXw/VimtMro_B9I/AAAAAAAAHgY/qZYVfNriOm0/s1600/1.png)](http://4.bp.blogspot.com/-j6G1kaI6BXw/VimtMro_B9I/AAAAAAAAHgY/qZYVfNriOm0/s1600/1.png)

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

[![](http://1.bp.blogspot.com/-n04XweFHx5w/VimuRLyJtGI/AAAAAAAAHgg/H0pNXYnW11w/s1600/1.png)](http://1.bp.blogspot.com/-n04XweFHx5w/VimuRLyJtGI/AAAAAAAAHgg/H0pNXYnW11w/s1600/1.png)
* 如果兩的類別對應到同一個物件的情況
```csharp
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 : **
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 : **

1
2
3
4
5
6
7
8
9
10
11
public class AutoMapperConfig
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<ControllerMappingProfile>();
});
}
}

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


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

實際案例如下:

  • 先在DB裡建立一個Function ```sql
    create function [dbo].[fb_CreatePWD]
    (
    @Param1 varchar(150)
    )
    RETURNS varbinary (150)
    AS
    BEGIN
    DECLARE @ResultVar varbinary(150)
    select @ResultVar = pwdencrypt(@Param1)

    RETURN @ResultVar
    

    END

1
2
3
4
5
6
7
```sql
</pre>
* 接著在MSSMS上測試是可以正常的[![](http://4.bp.blogspot.com/-9so5HU0Hkr0/Vih51QjzYZI/AAAAAAAAHe8/mIYlcdaDToA/s1600/1.png)](http://4.bp.blogspot.com/-9so5HU0Hkr0/Vih51QjzYZI/AAAAAAAAHe8/mIYlcdaDToA/s1600/1.png)

* 然後移到LinqToSql的DataContext試試看```csharp
var result = this.ExecuteQuery<binary>("select dbo.fn_CreatePWD({0})","ok").FirstOrDefault();
</binary>

得到以下錯誤訊息
型別 ‘System.Data.Linq.Binary’ 必須宣告預設 (無參數) 建構函式,才能在對應時加以建構

  • 網路上找到有人說用byte[]接可以,所以立馬做一下測試 ```csharp
1
2
<pre class="brush:csharp">var result = this.ExecuteQuery<binary>("select dbo.fn_CreatePWD({0})","ok").FirstOrDefault();
</binary>

結果一樣GG, 型別 ‘System.Byte[]’ 必須宣告預設 (無參數) 建構函式,才能在對應時加以建構。

接著改用Entity Framework 6.0來試試看* 一樣不行

  • 換成Byte[]就可以了!!!

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

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

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

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