2017年1月22日 星期日

【C#】程式建立LocalDB(.mdf)與刪除


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


這邊記錄一下實作方法,以利之後查找
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了




刪除

首先要先在專案安裝Entity Framerok,並且補上以下的程式
/// <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了
var DatabaseName = "TestCreateDB";
DeleteLocalDb(DatabaseName);


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



刪除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




2017年1月19日 星期四

【Regular Expression】分群與取代


在正規表示法中,可以將比對的資料做分群與命名,而在.Net中的語法是
(?:<群組名稱>)

應用方法如下
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));


也可以用來做取代的用途
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日"));







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

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


以上筆記一下。

2017年1月10日 星期二

【神奇事件】看不見的點


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

"Te6g5R​"

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

保哥有篇文章提到類似的問題 :  魔鬼般的細節:使用 C# 的 String.Trim() 方法刪除空白字元

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

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


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

2017年1月3日 星期二

【Regular Expression】正向環視、反向環視


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


註: 以下案例可以透過 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