0%

常常我會在SSMS上面撰寫SQL語法,等到確定之後才會把他挪到專案內,這時候就會碰到要補上前後雙引號或是跳脫字元等問題,以前都會笨笨的一行一行慢慢改,直到朋友介紹我一款好藥好套件SmartPaster

** **
[![](https://2.bp.blogspot.com/-z9hPIDANTMo/WMX_dl7i0HI/AAAAAAAAIFE/PS6PSdkYy4w1lh-BSUYlR608rsaAfU89wCLcB/s1600/1.png)](https://2.bp.blogspot.com/-z9hPIDANTMo/WMX_dl7i0HI/AAAAAAAAIFE/PS6PSdkYy4w1lh-BSUYlR608rsaAfU89wCLcB/s1600/1.png)
** **

它的功用是做什麼呢? 我想直接看圖就一目了然了

[![](https://4.bp.blogspot.com/-QzoRYhI2WNg/WMYAOrkP_DI/AAAAAAAAIFI/7dsnic6h8wohlRxmfLkli7y7krZfv5URACLcB/s1600/1.png)](https://4.bp.blogspot.com/-QzoRYhI2WNg/WMYAOrkP_DI/AAAAAAAAIFI/7dsnic6h8wohlRxmfLkli7y7krZfv5URACLcB/s1600/1.png)

只要你複製好任何字串,這邊以這串為例

select *
from Employee

到VS裡面的時候點擊右鍵 >> Paste As… >> 選擇你要貼上的格式,登登~~~

[![](https://3.bp.blogspot.com/-Z0tq9CXFV1M/WMYBiGjJ7DI/AAAAAAAAIFU/8sWywyAkPfQRAE3w9FS-AJn7KJAyjH7cACLcB/s1600/1.png)](https://3.bp.blogspot.com/-Z0tq9CXFV1M/WMYBiGjJ7DI/AAAAAAAAIFU/8sWywyAkPfQRAE3w9FS-AJn7KJAyjH7cACLcB/s1600/1.png)

自動就會幫你補上可以執行的程式了,不用再自己前後雙引號、跳脫字元等等,搞到格式大亂啦!!!! YES

寫單元測試測試Controller的時候,有時候程式中會用到Controller.Request Property,但單元測試並不是真的Web連線行為,所以呼叫到Request的時候會是Null , 想要做一個假的塞給他,這個屬性卻是只能讀取不能寫入的

[![](https://2.bp.blogspot.com/-4_O_yEIRJeA/WKJdDhYR0MI/AAAAAAAAIEk/pa8aYvPcJw4jt62eTTSYrp6h-hK98eP1ACLcB/s1600/1.png)](https://2.bp.blogspot.com/-4_O_yEIRJeA/WKJdDhYR0MI/AAAAAAAAIEk/pa8aYvPcJw4jt62eTTSYrp6h-hK98eP1ACLcB/s1600/1.png)

每次寫完沒多久碰到就又會忘記,所以筆記下來方便以後查找,這邊一樣用NSubstitute套件做處理

[![](https://4.bp.blogspot.com/-dMNVp94tO1Y/WKJeLHnEa8I/AAAAAAAAIEs/7yAUVlh29F0wsXsmTjh_UcSZUdoC8MfpgCLcB/s1600/1.png)](https://4.bp.blogspot.com/-dMNVp94tO1Y/WKJeLHnEa8I/AAAAAAAAIEs/7yAUVlh29F0wsXsmTjh_UcSZUdoC8MfpgCLcB/s1600/1.png)
1
2
3
4
5
6
7
8
9
YourController Sut = new YourController();
var request = Substitute.For<HttpRequestBase>();
var context = Substitute.For<HttpContextBase>();
request.HttpMethod.Returns("Get");
context.Request.Returns(request);

Sut.ControllerContext = new ControllerContext(context, new RouteData(), Sut);


之前有寫如何對Repository層做單元測試【Unit Test】針對Repository做單元測試 (一),當時是直接建立LocalDB放在專案之中,測試的時候對它執行,但因為公司導入CICD流程,這些MDF會殘留在佈署的機器上,造成資源的浪費,所以同事教了新的方法,用程式直接建立LocalDB,等到測試完畢後直接砍掉的方法。

這邊記錄一下實作方法,以利之後查找

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
private const string LocalDbMasterConnectionString =
@"Data Source=(LocalDB)\MSSQLLocalDB;Initial Catalog=master;Integrated Security=True";

private const string TestConnectionString =
@"Data Source=(LocalDB)\MSSQLLocalDB;Initial Catalog={0};Integrated Security=True;
MultipleActiveResultSets=True;AttachDBFilename={1}.mdf";

string DatabaseName {get;set;}
void Main()
{
this.DatabaseName = "TestCreateDB";
CreateDB();
}

/// <summary>
/// 建立DB
/// </summary>
void CreateDB()
{
//先看看有沒有相同的DB存在,如果有的話卸離並移除
this.DetachDatabase();

var fileName = this.CleanupDatabase();

using (var connection = new SqlConnection(LocalDbMasterConnectionString))
{
var commandText = new StringBuilder();
//Create DB的語法
commandText.AppendFormat(
"CREATE DATABASE {0} ON (NAME = N'{0}', FILENAME = '{1}.mdf');",
this.DatabaseName,
fileName);

connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = commandText.ToString();
cmd.ExecuteNonQuery();
}
}

/// <summary>
/// Detaches the database.
/// </summary>
private void DetachDatabase()
{
using (var connection = new SqlConnection(LocalDbMasterConnectionString))
{
connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = string.Format("exec sp_detach_db '{0}'", this.DatabaseName);
try
{
cmd.ExecuteNonQuery();
}
catch
{
Console.WriteLine("Could not detach");
}
}
}

/// <summary>
/// Cleanups the database.
/// </summary>
/// <returns>System.String.</returns>
private string CleanupDatabase()
{
var fileName = string.Concat(@"G:\",this.DatabaseName);
try
{
var mdfPath = string.Concat(fileName, ".mdf");
var ldfPath = string.Concat(fileName, "_log.ldf");

var mdfExists = File.Exists(mdfPath);
var ldfExists = File.Exists(ldfPath);

if (mdfExists) File.Delete(mdfPath);
if (ldfExists) File.Delete(ldfPath);
}
catch
{
Console.WriteLine("Could not delete the files (open in Visual Studio?)");
}
return fileName;
}


最後就會在你寫的位置看到產生的DB了

[![](https://2.bp.blogspot.com/-zochbU5phtw/WIWuOeDVZ4I/AAAAAAAAID0/MwBAkQ4YEyQCLFLHbWnBaUiXVbtGrIseACLcB/s1600/1.png)](https://2.bp.blogspot.com/-zochbU5phtw/WIWuOeDVZ4I/AAAAAAAAID0/MwBAkQ4YEyQCLFLHbWnBaUiXVbtGrIseACLcB/s1600/1.png)

刪除

首先要先在專案安裝Entity Framerok,並且補上以下的程式

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
/// <summary>
/// 使用 EntityFramework 的 Database 類別 Delete 方法,確認 LocalDB 存在後再移除.
/// </summary>
public static void DeleteLocalDb(string dbName)
{
using (var connection = new SqlConnection(
string.Format(TestConnectionString,dbName, dbName)))
{
if (Database.Exists(connection))
{
try
{
SqlConnection.ClearAllPools();
Database.Delete(connection);
}
catch (Exception)
{
using (SqlConnection masterConnection = new SqlConnection(LocalDbMasterConnectionString))
{
SqlCommand cmd = masterConnection.CreateCommand();
cmd.CommandText = string.Format(
@"ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [{0}];", connection.Database);

masterConnection.Open();
cmd.ExecuteNonQuery();
}
}
}
}
}

接著呼叫,就可以砍掉剛剛建立出來的MDF了

1
2
3
var DatabaseName = "TestCreateDB";
DeleteLocalDb(DatabaseName);

但有一點必須特別注意,如果你今天的專案結構跟我一樣

[![](https://3.bp.blogspot.com/-x8WxDPWuFrM/WIW661-YM_I/AAAAAAAAIEQ/kV7NxAeClFw5DllZUb6QK2QuKw25ksTKwCLcB/s1600/1.png)](https://3.bp.blogspot.com/-x8WxDPWuFrM/WIW661-YM_I/AAAAAAAAIEQ/kV7NxAeClFw5DllZUb6QK2QuKw25ksTKwCLcB/s1600/1.png)

刪除DB的程式碼寫在ControlLocalDB的專案中,並且在這個專案有安裝Entity Framework,而ConsoleApplication1只是引用ControlLocalDB專案來執行,而沒有安裝Entity Framework,那這時候

Database.Exists(connection)

會永遠回傳False,所以不會去砍掉DB,而且也不會引發Exception的錯誤,不知道算不算是EF的Bug,當時找超久的(崩潰),所以還請特別注意這個地方,有用到這個方法的專案都要記得專EF

參考文章:
1.HOW TO:使用 ADO.NET 與 Visual C# .NET 程式建立 SQL Server 資料庫
2.Repository 測試使用 LocalDB - Part.2

在正規表示法中,可以將比對的資料做分群與命名,而在.Net中的語法是

(?:<群組名稱>)

應用方法如下

1
2
3
4
5
6
7
8
string Date = "2017/1/20";
Regex reg = new Regex(@"^(?<Year>\d{4})/(?<Month>\d{1,2})/(?<Day>\d{1,2})");
var Match = reg.Match(Date);

Console.WriteLine(string.Concat("年 :", Match.Groups["Year"].Value));
Console.WriteLine(string.Concat("月 :",Match.Groups["Month"].Value));
Console.WriteLine(string.Concat("日 :",Match.Groups["Day"].Value));

也可以用來做取代的用途

1
2
3
4
5
string Date = "2017/1/20";
Regex reg = new Regex(@"^(\d{4})/(\d{1,2})/(\d{1,2})");

Console.WriteLine(reg.Replace(Date,@"$1年$2月$3日"));

[![](https://4.bp.blogspot.com/-HQu1GmHFF_M/WIFzWkDPQQI/AAAAAAAAIDo/k5REjSoQ7fMeQBuCYZe7QRSQlXG0SXlFgCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://4.bp.blogspot.com/-HQu1GmHFF_M/WIFzWkDPQQI/AAAAAAAAIDo/k5REjSoQ7fMeQBuCYZe7QRSQlXG0SXlFgCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)

括號()包起來的地方,正規表示法比對到時會把他當作一個群組,而其中Replace的地方寫著 $1代表第一個括號比對的東西放這邊,所以$1會變成2017,後面以此類推,就會變成2017年1月20日這種格式。

所以也可以改成這樣,變成國外表示年份的格式**$2-$3-$1**,就會變成 1-20-2017

以上筆記一下。

最近遇到一個Token怎麼送都錯誤的問題,找遍了程式都找不到原因出在哪,最後沒招只好把傳輸的封包都截下來,看看到底哪邊出了問題,結果發現….

“Te6g5R?”

這串部分Token看起來沒有什麼特別的,但如果你把滑鼠放到最後”的位置開始用鍵盤的往前鍵遍覽整串字,你會發現在R”中間需要按兩次才跳得過去,從封包監視器看到的結果卻是多了一個”點”

[![](https://4.bp.blogspot.com/-XRCi-hr6diU/WHWjX1QA3RI/AAAAAAAAIDI/qTjeVZqBPm0M81EFKCe0Q1b_8UnOHU7dACLcB/s640/1.png)](https://4.bp.blogspot.com/-XRCi-hr6diU/WHWjX1QA3RI/AAAAAAAAIDI/qTjeVZqBPm0M81EFKCe0Q1b_8UnOHU7dACLcB/s1600/1.png)
保哥有篇文章提到類似的問題 :  [魔鬼般的細節:使用 C# 的 String.Trim() 方法刪除空白字元](http://blog.miniasp.com/post/2014/01/15/C-Sharp-String-Trim-ZWSP-Zero-width-space.aspx)

雖然我碰到的字元跟文章中碰到的不同,但應該是類似的問題,用BackSpace把那個看不到的點移除掉後,API就都正常了。

紀錄一下,提醒自己以後可能是因為這種…..奇怪的事情導致的,不要只顧著埋頭找程式Bug

#更新
那個看不見的點應該是 : Unicode Character ‘ZERO WIDTH SPACE’ 

說正向環視、反向環視感覺有點文謅謅的而且很難懂,其實白話文就是,比對這個位置的左(反向環視)右邊(正向環視)是否符合你下的條件,舉個例子

**
**
**註: **以下案例可以透過 https://regex101.com/ 做即時操作與測試

我想將下面中文的空格濾掉

哈 摟 你 好 嗎 ? Hello how are you?

變成這樣

哈摟你好嗎? Hello how are you?

如果要透過正規表示法比對出空格其實很簡單,只要下 **\s **即可,但如果這樣的話會變成這樣的結果。

哈摟你好嗎?Hellohowareyou?

因為所有的空格都被濾掉了,但其實英文跟英文單字之間的空格不能濾掉,這樣就變成無法理解的句子了,所以把我們的規則用中文表達就變成【我想濾掉空格,但該空格的左邊與右邊不能是英文字母

這時候正向環視與反性環視就派上用場了

名稱正規表示法解釋
正向環視(?=)這位置右邊要出現什麼
反向環視(?<=)這位置左邊要出現什麼
正向環視否定(?!)這位置右邊不能出現什麼
反向環視否定(?<!)這位置左邊不能出現什麼
所以剛剛的表達方法應該改成 **(?<![a-zA-Z])\s****(?![a-zA-Z])** ,其中**(?<![a-zA-Z])**表示空格的左邊不能出現英文字母,**(?![a-zA-Z])** 表示空格右邊的不能出現英文字母,只有這樣的空格才符合我們要求的,把他過濾掉,結果就會變成我們要的結果了 最後還有一個讀到的案例覺得也很實用,常常我們會需要在金錢上加上【,】來方便理解位數,例如

123,123,123

所以當我們拿到123456時,該怎麼把逗號加上去? 用中文表達就是,【我希望這個位置的右邊如果有三個數字,就幫我加上逗號】,所以寫出了**(?=\d{3})**,結果就變成了這樣

,1,2,3,456

看起來怪怪的,但其實符合我們下的判斷式,因為1、2、3右邊都可以數到三個數字,所以補上逗號合理,更精確我們的描述成【我希望這個位置的右邊有三個數字為一組,且比對到右邊不是數字為止,幫我加上逗號】,判斷式變成這樣**(?=(\d{3})+(?!\d)),其中三個數字一組的表達式(\d{3})+,且比對到右邊不是數字為止(?!\d)**,結果變成如下

,123,456

的確三個數字一組的補上逗號,但是最一開頭那邊不應該有逗號,所以應該加上一串描述【且左邊要是數字時,才補上逗號】,所以最後結果就變成**(?=(\d{3})+(?!\d))(?<=\d)**,而且切出來也就會剛剛好的

123,456

案情簡介:

手邊有一個中大型專案,最近Web版上線後,時不時會收到資料庫連線異常相關錯誤,不定時不定量,沒有特定會掛在哪支API,完全沒有邏輯可以歸納。

該Database Layer的ORM使用Entity Framwork(後面簡稱 EF)做為與DB溝通的橋樑,DBContext採用Singleton的方式,每個Request進來,不管跟DB溝通幾次都只會建造一次Context實體,Request結束後Dispose掉。

心中的想像:
以前寫ADO.NET自己控制連線,都聽前輩說一定要自己Close連線,不然很可能會把連線的Pool占滿,所以寫EF時習慣也都會加上Using

1
2
3
4
5
6
using (var DbContext = new NorthwindEntities())
{
//Do Something

}

而且自己覺得每次Dispose應該都會關閉該次連線,註1每次New Context都會建立新的連線,一切井然有序好不快樂,直到這個短時間會有大量的Request湧進來的服務上線後,一切都變調,時不時就會接到使用單位來詢問API不定時中斷問題,想破頭還是找不到問題在哪….

驗證:
**
**從外天空找到行天宮,最後發現最可能的原因是出在EF底層的運作,根據許多篇國外的文章指出,EF是自己控制連線,並不會依照你Dispose或是New Instance就關閉跟建立新連線,所以我特地寫一個範例立馬來實驗一下

首先寫一個ADO.NET版本,並且刻意不要寫SqlConnection . Close(),觀察SQL Connection Pool的狀況

1
2
3
4
5
6
7
8
9
10
11
12
void Main()
{
for (int i = 0; i < 10; i++)
{
SqlConnection conn = new SqlConnection(this.Connection.ConnectionString);
conn.Open();
SqlCommand command = new SqlCommand("select top 1 CustomerID from Customers", conn);
var Result = command.ExecuteScalar().ToString();
Result.Dump();
}
}

資料庫那邊用以下語法查詢目前連線DB得使用者與狀態

1
2
select * from sys.sysprocesses where status = 'sleeping' and spid > 50 order by login_time desc

實驗結果發現Pool立馬從4個暴增到14個

[![](https://1.bp.blogspot.com/-oEdNrW43zrg/WFj8I-X22EI/AAAAAAAAICY/hGYoeCGf7F0wxC4D-pLB5-ILQmOBQ7ASgCLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://1.bp.blogspot.com/-oEdNrW43zrg/WFj8I-X22EI/AAAAAAAAICY/hGYoeCGf7F0wxC4D-pLB5-ILQmOBQ7ASgCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)
[![](https://3.bp.blogspot.com/-4UVQhQQHP_I/WFj74WVDqsI/AAAAAAAAICU/N8MWSU17kiIm9CoqwaOoJYGorDof8BBHQCLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://3.bp.blogspot.com/-4UVQhQQHP_I/WFj74WVDqsI/AAAAAAAAICU/N8MWSU17kiIm9CoqwaOoJYGorDof8BBHQCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)

接著寫測是第二個版本,有呼叫Close()看看結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Main()
{

for (int i = 0; i < 10; i++)
{
SqlConnection conn = new SqlConnection(this.Connection.ConnectionString);
conn.Open();
SqlCommand command = new SqlCommand("select top 1 CustomerID from Customers", conn);
var Result = command.ExecuteScalar().ToString();
Result.Dump();
conn.Close(); //有呼叫Close
}
}

Pool裡面的閒置Connection數明顯下降,所以有沒有呼叫Close是有差別的,如果有Close的話,會有放掉Connection進而重用閒置的連線

[![](https://3.bp.blogspot.com/-n5iNtVFfa1A/WFj8v__0zPI/AAAAAAAAICg/pv_Yn0Yh3REK0G3quPsd2Q_TwuOOLmNqACLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://3.bp.blogspot.com/-n5iNtVFfa1A/WFj8v__0zPI/AAAAAAAAICg/pv_Yn0Yh3REK0G3quPsd2Q_TwuOOLmNqACLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)

接著來看看EF的版本,首先測試沒有呼叫Dispose(),且每次Create新的DBContext。(我是用LinqPad測試,所以建立DBContext的地方語法有點不一樣,但不影響結果)

1
2
3
4
5
6
7
8
9
void Main()
{
for (int i = 0; i < 10; i++)
{
var context = new TypedDataContext();
context.Customers.FirstOrDefault().CustomerID.Dump();
}
}

結果看起來連線數也是會自己重用

[![](https://1.bp.blogspot.com/-9dcCNkudzVU/WFj-FcO1zEI/AAAAAAAAICw/u6OqldT3IboAR-5UjsFll0DK4bPdF0FwQCLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://1.bp.blogspot.com/-9dcCNkudzVU/WFj-FcO1zEI/AAAAAAAAICw/u6OqldT3IboAR-5UjsFll0DK4bPdF0FwQCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)

既然EF會自己控制連線,那是否用Using來Dispose與否也沒差,連線並不會無限制增加?
所以我把程式改成這樣再跑一次

1
2
3
4
5
6
7
8
9
10
11
void Main()
{
for (int i = 0; i < 10; i++)
{
using (var context = new TypedDataContext())
{
context.Customers.FirstOrDefault().CustomerID.Dump();
}
}
}

[![](https://3.bp.blogspot.com/-ueHmNj18HlA/WFj-9VzgvXI/AAAAAAAAIC4/W9-U8j0R3IEaHodcAc1lSR3WkA34LstAgCLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://3.bp.blogspot.com/-ueHmNj18HlA/WFj-9VzgvXI/AAAAAAAAIC4/W9-U8j0R3IEaHodcAc1lSR3WkA34LstAgCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)

結果顯示,一模一樣的情況,所以EF的確有自己在控制網路連線這一環,與你Dispose無關,也驗證了這篇文章所說的「 Do I always have to call Dispose() on my DbContext objects? Nope

**
**解決方案 : 

依照上面的時間結果觀察,EF的確有自己在控制連線狀態這件事情,而他什麼時候呼叫Close我並不知道,且還套用了Singleton Pattern,這樣交互影響下不確定是不是導致上述發生的結果,所以我把EF整個拿掉,整個Database Layer用Dapper翻寫,每次連線都自己控制,不再使用EF,上線後至今已經一個禮拜,不再出現任何資料庫連線錯誤。

雖然不敢保證中間推斷的是否百分之百正確,因為如果要更確定,應該要追到EF的底層Code來看他究竟是怎麼寫的,或許觀念大致相同,但是可能會跟我論述的有落差,但的確可以把問題收斂到是EF這個區塊的問題,因為改用Dapper就沒事了。

這個問題整整困擾了將近一年的時間,我想在徹底研究出EF該怎麼解決這個問題之前,大型服務我應該都會暫時放棄這條路了,雖然EF真的很方便阿!!!(吶喊~)

註1:
依照DB Connection Pool的概念,如果有呼叫SqlConnection Close的話,會將Connection釋放到Pool,等到下次有連線時,會優先檢查Pool是否有閒置的可以使用,沒有才會建立新的。
參考: MSDN - SqlConnection.Close 方法 ()

參考文章

1. [在 SQL Server 發現大量在 Sleeping 的連線](https://blogs.msdn.microsoft.com/jchiou/2012/12/10/sql-server-sleeping/)

上線WebAPI專案的時候,發現只要用非Get與Post的動詞呼叫API,就會得到這個405的錯誤,最後上網找了一下資料跟請教同事,紀錄一下狀況。

首先如果IIS的的WebDAV功能有被啟動,那這些Http動詞就會被擋掉。但我同時也懷疑是安裝WebDeploy會開啟這個功能(目前沒有證實),會這樣懷疑是因為這個服務之前是沒問題的,但當天安裝了WebDeploy要建置CI環境時,這服務相關動詞就掛了。
但當天安裝WebDeploy的多台機器中,有些裝完也沒啟動WebDAV,現在不知道到底是需要特定環境或設定會開啟這個功能,還是一開始安裝IIS就勾選啟動了這功能,總之要小心就是了

[![](https://4.bp.blogspot.com/-HKu_UbhGZWg/WDubNA_FOUI/AAAAAAAAIBw/he7Kx_8_nh8GaCzrFKOzUgIn2zOeodTTwCLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://4.bp.blogspot.com/-HKu_UbhGZWg/WDubNA_FOUI/AAAAAAAAIBw/he7Kx_8_nh8GaCzrFKOzUgIn2zOeodTTwCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)

如果發現WebDAV被啟動,同時發生405錯誤時,在該網站的WebConfig加上以下區段來解決。

1
2
3
4
5
6
7
8
9
10
11
12
13
<system.webServer>
<modules>
......
<remove name="WebDAVModule"/>
......
</modules>
<handlers>
.......
<remove name="WebDAV" />
.......
</handlers>
</system.webServer>

很多時候我們的類別會繼承同一個父類別,而父類別的欄位可能都驗證的規則都一致,一直重寫會覺得很煩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Parent 
{
public string Name {get;set;}
}

public class ChildA :Parent
{
public string ChildACustomProperty {get;set;}
}

public class ChildB : Parent
{
public string ChildBCustomProperty { get; set; }
}

這邊有兩個類別分別為ChildA和 ChildB,它們都繼承Parent這個類別,這時候 Parent的Name規則就是不能為空值跟Null,但真正會傳進來使用的是ChildA 與ChildB這兩個子類別,難道只能將驗證Name的欄位兩邊都寫嗎??

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
public class ChildAValidators : AbstractValidator<ChildA>
{
public ChildAValidators()
{
// Name
this.RuleFor(x => x.Name)
.NotEmpty()
.WithErrorCode("400")
.WithMessage("Name 不能為空值")
.NotNull()
.WithErrorCode("400")
.WithMessage("Name 不能為Null");

// ChildACustomProperty
this.RuleFor(x => x.ChildACustomProperty)
.Length(1,6)
.WithErrorCode("400")
.WithMessage("ChildACustomProperty 必須介於1~6個字之間");
}
}

public class ChildBValidators : AbstractValidator<ChildB>
{
public ChildBValidators()
{
// Name
this.RuleFor(x => x.Name)
.NotEmpty()
.WithErrorCode("400")
.WithMessage("Name 不能為空值")
.NotNull()
.WithErrorCode("400")
.WithMessage("Name 不能為Null");

// ChildACustomProperty
this.RuleFor(x => x.ChildBCustomProperty)
.Length(1, 12)
.WithErrorCode("400")
.WithMessage("ChildBCustomProperty 必須介於1~12個字之間");
}
}

這樣對程式設計來說不是很好,如果哪天Name的限制改變,所有繼承Parent的驗證都要翻出來改,其實FluentValidation也可以寫成繼承的方式,可以改成下面的方法

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 ChildAValidators : ParentValidators<ChildA>
{
public ChildAValidators()
{
// ChildACustomProperty
this.RuleFor(x => x.ChildACustomProperty)
.Length(1,6)
.WithErrorCode("400")
.WithMessage("ChildACustomProperty 必須介於1~6個字之間");
}
}

public class ChildBValidators : ParentValidators<ChildB>
{
public ChildBValidators()
{
// ChildACustomProperty
this.RuleFor(x => x.ChildBCustomProperty)
.Length(1, 12)
.WithErrorCode("400")
.WithMessage("ChildBCustomProperty 必須介於1~12個字之間");
}
}

public class ParentValidators<T> : AbstractValidator<T>
where T : Parent
{
public ParentValidators()
{
// Name
this.RuleFor(x => x.Name)
.NotEmpty()
.WithErrorCode("400")
.WithMessage("Name 不能為空值")
.NotNull()
.WithErrorCode("400")
.WithMessage("Name 不能為Null");
}
}

這樣就可以讓程式乾淨許多,也讓它符合物件導向的設計!!!

用Linq的好處是強型別,讓你在寫程式的時後不會因為Key錯字,但也有些衍伸的問題導致程式會寫得很醜,以前最常碰到的例子就是,當排序可能依據【多欄位】升降冪,程式就會又臭又長。

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
void Main()
{
//要用來排序的欄位
OrderByColumnEnum OrderByColumn = OrderByColumnEnum.Pin;
//排序的方法
OrderByEnum OrderBy = OrderByEnum.ASC;

//準備資料
var BuildingList = new List<Building>();
for (int i = 0; i < 5; i++)
{
BuildingList.Add(new Building
{
Age = 1 * (i+1),
Pin = 1 * (i+1),
CaseDate = DateTime.Now.AddDays(i +1)
});
}

//排序欄位
switch (OrderByColumn)
{
case OrderByColumnEnum.Pin:
switch (OrderBy)
{
case OrderByEnum.ASC:
//因為裡面用到的欄位不同,又是強型別,只能寫死...
BuildingList = BuildingList.OrderBy(x => x.Pin).ToList();
break;
case OrderByEnum.DESC:
BuildingList = BuildingList.OrderByDescending(x => x.Pin).ToList();
break;
}
break;
case OrderByColumnEnum.Age:
switch (OrderBy)
{
case OrderByEnum.ASC:
BuildingList = BuildingList.OrderBy(x => x.Age).ToList();
break;
case OrderByEnum.DESC:
BuildingList = BuildingList.OrderByDescending(x =>x.Age).ToList();
break;
}
break;
case OrderByColumnEnum.CaseDate:
switch (OrderBy)
{
case OrderByEnum.ASC:
BuildingList = BuildingList.OrderBy(x => x.CaseDate).ToList();
break;
case OrderByEnum.DESC:
BuildingList = BuildingList.OrderByDescending(x => x.CaseDate).ToList();
break;
}
break;
}
BuildingList.Dump();
}

public class Building
{
public int Pin { get; set; }
public int Age { get; set; }
public DateTime CaseDate { get; set; }

}

/// <summary>
/// 排序欄位
/// </summary>
public enum OrderByColumnEnum
{
/// <summary>
/// 成交日期
/// </summary>
CaseDate,
/// <summary>
/// 建坪
/// </summary>
Pin,
/// <summary>
/// 屋齡
/// </summary>
Age

}

/// <summary>
/// 排序方法
/// </summary>
public enum OrderByEnum
{
ASC = 1,
DESC = 2
}

因為每個欄位不同名稱,又有可能升降冪,所以寫的超級長,如果欄位達到5~6個時,幾乎已經無法維護,但其實只要搭配一點點反射可以把這段改得很漂亮的

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
//要用來排序的欄位
OrderByColumnEnum OrderByColumn = OrderByColumnEnum.Pin;
//排序的方法
OrderByEnum OrderBy = OrderByEnum.ASC;

//準備資料
var BuildingList = new List<Building>();
for (int i = 0; i < 5; i++)
{
BuildingList.Add(new Building
{
Age = 1 * (i+1),
Pin = 1 * (i+1),
CaseDate = DateTime.Now.AddDays(i +1)
});
}

//排序欄位
var param = OrderByColumnEnum.Pin.ToString();

//透過PropertyInfo操作
var propertyInfo = typeof(Building).GetProperty(param);
switch (OrderBy)
{
case OrderByEnum.ASC:
BuildingList = BuildingList.OrderBy(x => propertyInfo.GetValue(x, null)).ToList();
break;
case OrderByEnum.DESC:
BuildingList = BuildingList.OrderByDescending(x => propertyInfo.GetValue(x, null)).ToList();
break;
}

BuildingList.Dump();

這樣程式是不是變得乾淨許多,但這邊要特別注意的是,因為我的Enum跟Building的屬性剛好都可以對應起來,如果我的Enum名稱與Building的屬性不能對應,那要另外寫對應方式,例如寫到擴充的Enum Attribute,或是用最簡單的Switch Case解決。