厚積薄發,豐富的公用類庫積累,助你高效進行系統開發(3)----數據庫相關操作

在前面隨筆《厚積薄發,豐富的公用類庫積累,助你高效進行系統開發(1)》和《厚積薄發,豐富的公用類庫積累,助你高效進行系統開發(2)》,介紹了公用類庫的包含的內容以及相關使用說明,本文將繼續把在整理幫助文檔成CHM過程中,完成的類庫使用說明逐步放送,一是使得大家對類庫的功能及使用有一個大致的了解,并能夠在實際中應用,或者能夠和大家在這方面繼續探討,逐步改進和完善。
1、 Access數據庫文件操作輔助類JetAccessUtil
**實現效果 **
1)本輔助類主要是用來方便實現Access數據庫文件操作,包括新建Access數據庫(可含密碼)、壓縮數據庫、設置數據庫密碼、列出數據庫表、列出數據庫表字段等常用的Access數據庫文件操作的實現。

實現代碼
1)輔助類庫JetAccessUtil的相關方法定義

/// <summary>    
/// 新建帶密碼的空Access 2000 數據庫    
/// </summary>    
/// <param name="mdbFilePath">數據庫文件路徑</param>    
/// <param name="password">數據庫密碼</param>    
/// <returns>字符0為操作成功,否則為失敗異常消息。</returns>    
public static string CreateMDB(string mdbFilePath, string password)    
   
/// <summary>    
/// 新建空的Access數據庫    
/// </summary>    
/// <param name="mdbFilePath">數據庫文件路徑</param>    
/// <returns>字符0為操作成功,否則為失敗異常消息。</returns>    
public static string CreateMDB(string mdbFilePath)    
   
/// <summary>    
/// 壓縮帶密碼Access數據庫    
/// </summary>    
/// <param name="mdbFilePath">數據庫文件路徑</param>    
/// <param name="password">數據庫密碼</param>    
/// <returns>字符0為操作成功,否則為失敗異常消息。</returns>    
public static string CompactMDB(string mdbFilePath, string password)    
   
/// <summary>    
/// 壓縮沒有帶密碼Access數據庫    
/// </summary>    
/// <param name="mdbFilePath">數據庫文件路徑</param>    
/// <returns>字符0為操作成功,否則為失敗異常消息。</returns>    
public static string CompactMDB(string mdbFilePath)    
   
/// <summary>    
/// 設置Access數據庫的訪問密碼    
/// </summary>    
/// <param name="mdbFilePath">數據庫文件路徑</param>    
/// <param name="oldPwd">舊密碼</param>    
/// <param name="newPwd">新密碼</param>    
/// <returns>字符0為操作成功,否則為失敗異常消息。</returns>    
public static string SetMDBPassword(string mdbFilePath, string oldPwd, string newPwd)    
   
/// <summary>    
/// 列出Access 2000 數據庫的表名稱    
/// </summary>    
/// <param name="mdbFilePath">數據庫文件路徑</param>    
/// <param name="password">數據庫密碼</param>    
/// <returns></returns>    
public static List<string> ListTables(string mdbFilePath, string password)    
   
/// <summary>    
/// 列出Access2000數據庫的表字段    
/// </summary>    
/// <param name="mdbFilePath">數據庫文件路徑</param>    
/// <param name="password">數據庫密碼</param>    
/// <param name="tableName">表名稱</param>    
/// <returns>返回字段名稱和對應類型的字典數據</returns>    
public static Dictionary<string, string> ListColumns(string mdbFilePath, string password, string tableName) 

2)輔助類庫的使用例子。

string fileNoPass = Path.Combine(Path.GetTempPath(), "EmptyNoPass.mdb");    
string filePass = Path.Combine(Path.GetTempPath(), "EmptyWithPass.mdb");    
   
//創建不帶密碼的空數據庫    
JetAccessUtil.CreateMDB(fileNoPass);    
//創建帶密碼的空數據庫    
JetAccessUtil.CreateMDB(filePass, "wuhuacong@163.com");    
   
//壓縮不帶密碼的數據庫    
JetAccessUtil.CompactMDB(fileNoPass);    
//壓縮帶密碼的數據庫    
JetAccessUtil.CompactMDB(filePass, "wuhuacong@163.com");    
   
//重新設置數據庫的密碼    
JetAccessUtil.SetMDBPassword(filePass, "wuhuacong@163.com", "6966254");    
//列出數據庫的表名稱    
List<string> tableNameList = JetAccessUtil.ListTables(filePass, "6966254");    
string strNameList = "";    
foreach (string name in tableNameList)    
{    
    strNameList += string.Format(",{0}", name);    
}    
if (!string.IsNullOrEmpty(strNameList))    
{    
    MessageUtil.ShowTips(strNameList);    
}    
  
Process.Start(Path.GetTempPath());  

** 2、常用的Access數據庫Sql操作輔助類庫 OleDbHelper**
** 實現效果**
1)本輔助類主要是用來方便實現對Access數據庫文件的Sql訪問,包括測試連接、執行Sql、獲取返回數據集等操作。
2)輔助類庫構造對象的時候,只需要傳入Access數據庫文件,即可對其進行相關的Sql操作,簡化對Access數據庫執行腳本的操作。

**實現代碼 **
1)輔助類OleDbHelper提供的函數列表如下所示。

/// <summary>    
/// 常用的Access數據庫Sql操作輔助類庫    
/// </summary>    
public class OleDbHelper    
{    
    /// <summary>    
    /// 構造函數    
    /// </summary>    
    /// <param name="accessFilePath"></param>    
    public OleDbHelper(string accessFilePath)    
   
    /// <summary>    
    /// 測試數據庫是否正常連接    
    /// </summary>    
    /// <returns></returns>    
    public bool TestConnection()    
   
    /// <summary>    
    /// 執行Sql,并返回成功的數量    
    /// </summary>    
    /// <param name="sqlList">待執行的Sql列表</param>    
    /// <returns></returns>    
    public int ExecuteNonQuery(List<string> sqlList)    
   
    /// <summary>    
    /// 執行無返回值的語句,成功返回True,否則False    
    /// </summary>    
    /// <param name="sql">待執行的Sql</param>    
    /// <returns></returns>    
    public bool ExecuteNoQuery(string sql)    
   
    /// <summary>    
    /// 執行單返回值的語句    
    /// </summary>    
    /// <param name="sql">待執行的Sql</param>    
    /// <returns></returns>    
    public object ExecuteScalar(string sql)    
   
    /// <summary>    
    /// 執行Sql,并返回IDataReader對象。    
    /// </summary>    
    /// <param name="sql">待執行的Sql</param>    
    /// <returns></returns>    
    public IDataReader ExecuteReader(string sql)    
   
    /// <summary>    
    /// 執行Sql并返回DataSet集合    
    /// </summary>    
    /// <param name="sql">待執行的Sql</param>    
    /// <returns></returns>    
    public DataSet ExecuteDataSet(string sql)    
}      

2)實現操作例子如下所示。

string access = @"C:\Orderwater.mdb";    
List<string> tableNameList = JetAccessUtil.ListTables(access, "");    
OleDbHelper helper = new OleDbHelper(access);    
   
foreach(string tableName in tableNameList)    
{    
    string sql = string.Format("Select * from {0} ", tableName);    
    DataSet ds = helper.ExecuteDataSet(sql);    
    if (ds.Tables[0].Rows.Count > 0)    
    {    
        MessageUtil.ShowTips(string.Format("tableName:{0} RowCount:{1}", tableName, ds.Tables[0].Rows.Count));    
    }   
}    

**3、根據各種不同數據庫生成不同分頁語句的輔助類 PagerHelper **
實現效果
1)本輔助類主要是用來方便根據各種條件,生成不同的分頁語句,且支持Oracle、SqlServer、Access、MySql數據庫分頁語句的生成。

2)輔助類可以根據表名、查詢字段列表、排序字段、分頁數量、分頁頁碼、降序升序、查詢條件等條件組合成一條完整的分頁語句,并且支持獲取數量和列表兩種語句接口,非常方便用于數據的分頁處理。


實現代碼:
1)使用例子說明。通過構造PageHelper類,并傳入表名、查詢字段、排序字段、頁面大小、當前頁碼、降序升序、查詢條件等參數,可以生成基于Oracle、SqlServer、Access、MySql數據庫的分頁語句。然后通過具體查詢總數語句、查詢列表語句可以完成獲取指定數據列表的顯示,由于分頁是基于一頁一頁的獲取,這樣提高了數據分頁的效率。

/// <summary>
/// 通過自己組裝分頁語句        
/// </summary>        
/// <param name="where"></param>        
/// <param name="pagerInfo"></param>        
/// <returns></returns>        
private DataTable DirectLoadData(string where, PagerInfo pagerInfo)        
{        
    DataTable dt = null;        
    PagerHelper helper = new PagerHelper("All_Customer", "*", "ID", pagerInfo.PageSize, pagerInfo.CurrenetPageIndex, true, where);        
    string countSql = helper.GetPagingSql(DatabaseType.SqlServer, true);        
    string dataSql = helper.GetPagingSql(DatabaseType.SqlServer, false);        
       
    string value = SqlValueList(countSql);        
    pagerInfo.RecordCount = Convert.ToInt32(value);        
       
    dt = SqlTable(dataSql);        
       
    return dt;        
}  

4、 查詢條件組合輔助類 SearchCondition
**實現效果 **
1)本輔助類主要是用來方便實現對查詢表單的各種條件進行組合,快速拼接Sql語句的操作,類庫可用于Web項目和Winform項目的查詢列表拼接語句。
2)使用場景: 在查詢列表頁面中,一般有好幾個條件, 用戶進行查詢時候,需要根據這幾個條件進行過濾查詢.但在組裝這些過濾條件的時候,代碼比較煩瑣臃腫,本組件代碼為解決該問題而設計。

3)使用目的: 1.減少對參數非空的條件判斷 2. 支持SqlServer、Oracle、Access、MySql數據訪問的Sql語句的生成,根據不同數據庫的一些特點差異,生成對應的語句. 3. 減少拼接語句的代碼并減少出錯的幾率 4.構造Sql語句或者參數化條件更加易讀。
4) SearchCondition輔助類的類圖如下所示,其中SearchCondtion是語句操作對象類,SearchInfo是語句的條件實體類,SqlOperator是各種查詢條件的枚舉對象,方便操作并減少輸入字符條件的出錯。



**實現代碼 **
1)生成Sql語句
如有幾個字段,需要根據不同的字段進行過濾,想生成的SQL語句如下:

Where (1=1) AND AA2 Like '%AA2Value%' AND AA6 >= 'Value6' AND AA7 <= 'value7'
AND AA3 = 'Value3' AND AA4 < 'Value4' AND AA5 > 'Value5' AND AA <> '1'

2)與普通做法的比較。下面我們比較一下使用該控件和不使用在列表查詢頁面中的代碼,可以看出使用了控件后的代碼大大較少了,并且可讀性也增強了

private string GetCondition()    
{    
    SearchCondition search = new SearchCondition();    
    search.AddCondition("GroupID", this.ddlUserGroup.SelectedValue, SqlOperator.Equal, true)//班組ID    
          .AddCondition("DealGroupName", this.ddlDealGroup.SelectedValue, SqlOperator.Equal, true)/*消缺單位*/   
          .AddCondition("VisioStationID", this.ddlStation.SelectedValue, SqlOperator.Like, true)//變電站    
          .AddCondition("VisioImageID", this.ddlLine.SelectedValue, SqlOperator.Like, true)/*饋線*/   
          .AddCondition("BugNo", this.txtBugNo.Text.Trim(), SqlOperator.Like, true)/*編號*/   
          .AddCondition("Finder", this.ddlFindUser.SelectedValue, SqlOperator.Like, true)/*發現人*/   
          .AddCondition("CheckUser", this.ddlCheckUser.SelectedValue, SqlOperator.Like, true)//驗收人    
          .AddCondition("DeviceBug.BugType", this.ddlBugType.SelectedValue, SqlOperator.Equal, true)//缺陷類別    
          .AddCondition("CurrentState", this.ddlCurrentState.SelectedValue, SqlOperator.Equal, true)//處理狀態    
          .AddCondition("FindDate", this.txtFindBeginDate.Text.Trim(), SqlOperator.MoreThanOrEqual, true)//發現日期    
          .AddCondition("FindDate", this.txtFindEndDate.Text.Trim(), SqlOperator.LessThanOrEqual, true)//發現日期    
          .AddCondition("EndDate", this.txtEndBeginDate.Text.Trim(), SqlOperator.MoreThanOrEqual, true)//消缺日期    
          .AddCondition("EndDate", this.txtEndEndDate.Text.Trim(), SqlOperator.LessThanOrEqual, true);//消缺日期    
   
    return search.BuildConditionSql(DatabaseType.SqlServer);    
}    

普通做法,不使用控件在構造列表查詢的語句的函數代碼則比較繁瑣復雜,如下所示。

private string GetCondition()    
{    
  string condition = "";    
  if ( this.ddlUserGroup.SelectedValue != "0")    
  {    
      condition += string.Format( " GroupID = {0}" , this.ddlUserGroup.SelectedValue.ToString() );    
  }    

  //消缺單位    
  if ( this.ddlDealGroup.SelectedValue != "0")    
  {    
      if (condition == "")    
      {    
          condition += string.Format( " DealGroupName = '{0}'" , this.ddlDealGroup.SelectedItem.Text );    
      }    
      else   
      {    
          condition += string.Format( " And DealGroupName = '{0}'" , this.ddlDealGroup.SelectedItem.Text );    
      }    
  }    

      
  if (this.txtStation.Text.Trim() != "")    
  {    
      if (condition == "")    
      {    
          condition += string.Format(" Station like '%{0}%'",this.txtStation.Text.Trim() );    
      }    
      else   
      {    
          condition += string.Format(" And Station like '%{0}%' ",this.txtStation.Text.Trim() );    
      }    
  }   
..............(很多類似的代碼)

**5、轉換IDataReader字段對象的格式輔助類 SmartDataReader **
實現效果
1)本輔助類主要是用來方便轉換IDataReader字段對象的格式輔助類,可以轉換有默認值、可空類型的字段數據。

2)在使用數據庫返回對象IDataReader的時候,我們需要判斷數據庫字段是否為可空類型,并賦予空字段默認值,或者轉換為可空類型數據,使用該輔助類,可以簡化繁瑣的數據庫字段轉換操作,是數據庫字段轉換必備的輔助類。

實現代碼
1)輔助類SmartDataReader的轉換數據格式的操作例子如下。

/// <summary>    
/// 通用獲取集合對象方法    
/// </summary>    
/// <param name="sql">查詢的Sql語句</param>    
/// <param name="paramList">參數列表,如果沒有則為null</param>    
/// <returns></returns>    
private List<T> GetList(string sql, IDbDataParameter[] paramList)    
{    
  T entity = null;    
  List<T> list = new List<T>();    

  Database db = DatabaseFactory.CreateDatabase();    
  DbCommand command = db.GetSqlStringCommand(sql);    
  if (paramList != null)    
  {    
      command.Parameters.AddRange(paramList);    
  }    

  using (IDataReader dr = db.ExecuteReader(command))    
  {    
      while (dr.Read())    
      {    
          entity = DataReaderToEntity(dr);    

          list.Add(entity);    
      }    
  }    
  return list;    
}    
   
/// <summary>    
/// 將DataReader的屬性值轉化為實體類的屬性值,返回實體類    
/// </summary>    
/// <param name="dr">有效的DataReader對象</param>    
/// <returns>實體類對象</returns>    
protected ItemDetailInfo DataReaderToEntity(IDataReader dataReader)    
{    
    ItemDetailInfo itemDetailInfo = new ItemDetailInfo();    
    SmartDataReader reader = new SmartDataReader(dataReader);    
        
    itemDetailInfo.ID = reader.GetInt32("ID");    
    itemDetailInfo.ItemNo = reader.GetString("ItemNo");    
    itemDetailInfo.ItemName = reader.GetString("ItemName");    
    itemDetailInfo.Manufacture = reader.GetString("Manufacture");    
    itemDetailInfo.MapNo = reader.GetString("MapNo");    
    itemDetailInfo.Specification = reader.GetString("Specification");    
    itemDetailInfo.Material = reader.GetString("Material");    
    itemDetailInfo.ItemBigType = reader.GetString("ItemBigType");    
    itemDetailInfo.ItemType = reader.GetString("ItemType");    
    itemDetailInfo.Unit = reader.GetString("Unit");    
    itemDetailInfo.Price = reader.GetDecimal("Price");    
    itemDetailInfo.Source = reader.GetString("Source");    
    itemDetailInfo.StoragePos = reader.GetString("StoragePos");    
    itemDetailInfo.UsagePos = reader.GetString("UsagePos");    
    itemDetailInfo.Note = reader.GetString("Note");    
    itemDetailInfo.WareHouse = reader.GetString("WareHouse");    
    itemDetailInfo.Dept = reader.GetString("Dept");    
        
    return itemDetailInfo;    
} 

2)輔助類SmartDataReader提供了各種類型的數據轉換函數,如Int32、Int16、Decimal、Float、DateTime等數據類型的格式轉換,每個格式提供了幾種方式的處理,如對Int類型的數據轉換,其封裝的函數實現如下所示。

/// <summary>    
/// 轉換為Int類型數據    
/// </summary>    
public int GetInt32(string column)    
{    
    return GetInt32(column, 0);    
}    
   
/// <summary>    
/// 轉換為Int類型數據    
/// </summary>    
public int GetInt32(string column, int defaultIfNull)    
{    
    int data = (reader.IsDBNull(reader.GetOrdinal(column))) ? (int)defaultIfNull : int.Parse(reader[column].ToString());    
    return data;    
}    
   
/// <summary>    
/// 轉換為Int類型數據    
/// </summary>    
public int? GetInt32Nullable(string column)    
{    
    int? data = (reader.IsDBNull(reader.GetOrdinal(column))) ? (int?)null : int.Parse(reader[column].ToString());    
    return data;   
}  

**6、OSql命令操作函數輔助類 SqlScriptHelper **
實現效果
1)本輔助類主要是 OSql命令操作函數(可用于安裝程序的時候數據庫腳本執行)。

2)本輔助類庫通常用在SqlServer數據庫腳本執行,附加、分離、備份、恢復數據庫等操作。

實現代碼
1)主要的類庫函數如下所示。

/// <summary>    
/// 本地執行SQL腳本    
/// </summary>    
/// <param name="path">腳本文件路徑全名</param>    
public static void DoSQL(string path)    
   
/// <summary>    
/// 執行SQL腳本    
/// </summary>    
/// <param name="path">腳本文件路徑全名</param>    
/// <param name="userID">數據庫登錄ID</param>    
/// <param name="password">數據庫登錄密碼</param>    
/// <param name="server">數據庫服務器地址</param>    
public static void DoSQL(string path, string userID, string password, string server)    
   
/// <summary>    
/// 后臺執行DOS文件    
/// </summary>    
/// <param name="fileName">文件名(包含路徑)</param>    
/// <param name="argument">運行參數</param>    
/// <param name="hidden">是否隱藏窗口</param>    
public static void RunDos(string fileName, string argument, bool hidden)    
   
/// <summary>    
/// 在運行腳本之前把腳本中的數據庫名稱替換成安裝界面輸入的數據庫名稱    
/// </summary>    
/// <param name="filePath">腳本文件名</param>    
/// <param name="oldDBName">原有的數據庫名稱</param>    
/// <param name="newDBName">新的數據庫名稱</param>    
public static void ReplaceDBName(string filePath, string oldDBName, string newDBName)    
   
/// <summary>    
/// 附加SqlServer數據庫    
/// </summary>    
public bool AttachDB(string connectionString, string dataBaseName, string dataBase_MDF, string dataBase_LDF)    
   
/// <summary>    
/// 分離SqlServer數據庫    
/// </summary>    
public bool DetachDB(string connectionString, string dataBaseName)    
   
/// <summary>    
/// 還原數據庫    
/// </summary>    
public bool RestoreDataBase(string connectionString, string dataBaseName, string DataBaseOfBackupPath, string DataBaseOfBackupName)    
   
/// <summary>    
/// 備份SqlServer數據庫    
/// </summary>    
public bool BackupDataBase(string connectionString, string dataBaseName, string DataBaseOfBackupPath, string DataBaseOfBackupName)   

2)安裝執行數據庫腳本的操作例子如下所示。

string sqlFilePath = physicalRoot + "Hotel.sql";    
SqlScriptHelper.ReplaceDBName(sqlFilePath, "Hotel_Database", EdnmsDb.Database);    

if (!string.IsNullOrEmpty(EdnmsDb.UserId) && !string.IsNullOrEmpty(EdnmsDb.Password))    
{    
 SqlScriptHelper.DoSQL(sqlFilePath, EdnmsDb.UserId, EdnmsDb.Password, EdnmsDb.Server);    
}    
else   
{    
 SqlScriptHelper.DoSQL(sqlFilePath, EdnmsDb.Server);    
}  

基于時間和篇幅考慮,下次繼續介紹相關的類庫使用,另外提一下,整個系列的CHM文檔也在同步整理中,我們來看看目前進度的完成的CHM文件情況。


再次感謝大家的支持和鼓勵。
CHM幫助文檔持續更新中,統一下載地址是: http://www.iqidi.com/download/commonshelp.rar

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容