從C#到Objective-C,循序漸進(jìn)學(xué)習(xí)蘋果開發(fā)(7)--使用FMDB對(duì)Sqlite數(shù)據(jù)庫(kù)進(jìn)行操作

本隨筆系列主要介紹從一個(gè)Windows平臺(tái)從事C#開發(fā)到Mac平臺(tái)蘋果開發(fā)的一系列感想和體驗(yàn)歷程署照,本系列文章是在起步階段逐步積累的祸泪,希望帶給大家更好,更真實(shí)的轉(zhuǎn)換歷程體驗(yàn)建芙。本篇主要開始介紹基于XCode進(jìn)行IOS程序的開發(fā)没隘,介紹使用FMDB對(duì)Sqlite數(shù)據(jù)庫(kù)進(jìn)行操作,以及對(duì)數(shù)據(jù)庫(kù)操作類進(jìn)行抽象設(shè)計(jì)禁荸,以期達(dá)到重用右蒲、簡(jiǎn)化阀湿、高效開發(fā)的目的。
在.NET領(lǐng)域開發(fā)了很多年瑰妄,一般常見的項(xiàng)目都需要操作數(shù)據(jù)庫(kù)陷嘴,包括有Oracle、SqlServer间坐、Mysql灾挨、Sqlite、Access等數(shù)據(jù)庫(kù)竹宋,這些數(shù)據(jù)庫(kù)是很常見的劳澄,我們?cè)?NET環(huán)境里面開發(fā)的各種系統(tǒng),可能都或多或少需要和其中一種以上的數(shù)據(jù)庫(kù)打交道蜈七,這也是我致力于提煉我的.NET領(lǐng)域的Winform開發(fā)框架秒拔、Web開發(fā)框架、混合式開發(fā)框架的目的飒硅,盡可能達(dá)到簡(jiǎn)化砂缩、重用、高效開發(fā)的目的三娩。
雖然現(xiàn)在在IOS領(lǐng)域做一些研究開發(fā)庵芭,即使IOS設(shè)備更多強(qiáng)調(diào)的是一個(gè)多媒體的設(shè)備,但是數(shù)據(jù)庫(kù)的操作還是必不可少尽棕,因此我先從我熟悉的數(shù)據(jù)庫(kù)這塊入手喳挑,了解其中數(shù)據(jù)庫(kù)是如何操作的,有哪些現(xiàn)成的組件進(jìn)行參考學(xué)習(xí)等等滔悉。
在IOS里面開發(fā)伊诵,提起和數(shù)據(jù)庫(kù)打交道,可能很多人都熟悉FMDB這個(gè)數(shù)據(jù)庫(kù)的組件回官,它對(duì)IOS里面操作Sqlite數(shù)據(jù)庫(kù)進(jìn)行了很大程度的簡(jiǎn)化曹宴,簡(jiǎn)化后,我們大多數(shù)情況下歉提,只需要和FMDatabase和FMResultSet打交道即可笛坦,使用起來非常方便。

1苔巨、FMDB的操作

為了較好介紹整體性的內(nèi)容版扩,我們先從FMDB的各種操作進(jìn)行介紹。
1)數(shù)據(jù)庫(kù)打開操作

FMDatabase *db= [FMDatabase databaseWithPath:dbPath] ;  
if (![db open]) {  
NSLog(@"無法打開數(shù)據(jù)庫(kù)");  
return ;  
}  

2)數(shù)據(jù)庫(kù)操作executeUpdate

使用FMDB侄泽,對(duì)于沒有返回記錄的操作礁芦,都可以用executeUpdate進(jìn)行操作,如下是刪除記錄的函數(shù)

- (BOOL) deleteByCondition:(NSString *) condition {
    NSString *query = [NSString stringWithFormat:@"Delete FROM %@ Where %@ ", self.tableName, condition];
    BOOL result = [self.database executeUpdate:query];
    return result;
}

當(dāng)然,對(duì)于SQLite很多數(shù)據(jù)庫(kù)操作柿扣,我們可以使用參數(shù)化語(yǔ)句進(jìn)行操作肖方,如下例子所示

[db executeUpdate:@"INSERT INTO User (Name,Age) VALUES (?,?)",@"張三",[NSNumber numberWithInt:30]]

參數(shù)化也可以使用 : 字符作為參數(shù)標(biāo)識(shí),如下所示未状,是我封裝的一個(gè)數(shù)據(jù)庫(kù)操作函數(shù)

- (BOOL) deleteById:(id) key
{
    NSDictionary *argsDict = [NSDictionary dictionaryWithObjectsAndKeys:key, @"id", nil];
    NSString *query = [NSString stringWithFormat:@"Delete FROM %@ Where %@ =:id", self.tableName, self.primaryKey];
    BOOL result = [self.database executeUpdate:query withParameterDictionary:argsDict];
    return result;
}

3)返回集合的操作

操作有返回集合的語(yǔ)句俯画,我們就需要用到FMResultSet對(duì)象了,這個(gè)對(duì)象類似于以前見過的游標(biāo)司草,不過不一樣的東西而已艰垂。

FMResultSet *rs=[db executeQuery:@"SELECT * FROM User"];
rs=[db executeQuery:@"SELECT * FROM User WHERE Age = ?",@"20"];
while ([rs next]){
NSLog(@"%@ %@",[rs stringForColumn:@"Name"],[rs stringForColumn:@"Age"]);
}

上面的代碼操作,返回一個(gè)Resultset集合進(jìn)行遍歷使用翻伺,我們可以根據(jù)Resultset的一些方法獲取到不同的數(shù)據(jù)內(nèi)容材泄。

相對(duì)于FMResult的操作方式沮焕,原生態(tài)的Sqlite數(shù)據(jù)庫(kù)操作代碼如下所示吨岭。看完是不是感覺使用FMDB類庫(kù)操作數(shù)據(jù)庫(kù)方便很多呢峦树。

NSString *sqlQuery = @"SELECT * FROM User";
sqlite3_stmt * statement;
    
if (sqlite3_prepare_v2(db, [sqlQuery UTF8String], -1, &statement, nil) == SQLITE_OK) {
    while (sqlite3_step(statement) == SQLITE_ROW) {
        char *name = (char*)sqlite3_column_text(statement, 1);
        NSString *nsNameStr = [[NSString alloc]initWithUTF8String:name];
        int age = sqlite3_column_int(statement, 2);
        char *address = (char*)sqlite3_column_text(statement, 3);
        NSString *nsAddressStr = [[NSString alloc]initWithUTF8String:address];
        
        NSLog(@"name:%@  age:%d  address:%@",nsNameStr,age, nsAddressStr);
    }
}
sqlite3_close(db);

FMResultSet方法有下面幾種辣辫,分別用于獲取不同的數(shù)據(jù):
intForColumn:

longForColumn:

longLongIntForColumn:

boolForColumn:

doubleForColumn:

stringForColumn:

dateForColumn:

dataForColumn:

dataNoCopyForColumn:

UTF8StringForColumnIndex:

objectForColumn:

2、數(shù)據(jù)庫(kù)操作層的設(shè)計(jì)

上面小節(jié)介紹了使用FMDB類庫(kù)對(duì)Sqlite數(shù)據(jù)庫(kù)的操作魁巩,使我們大致了解了在IOS里面對(duì)數(shù)據(jù)庫(kù)操作的過程急灭。但是單純?nèi)绻榻B這些,我覺得太泛泛了谷遂,而且對(duì)我們使用起來也還是很不方便葬馋,很多時(shí)候,我總是想通過設(shè)計(jì)的方式肾扰,來簡(jiǎn)化我的各種操作處理畴嘶。如我們知道,IOS里面的Objective C也提供了很多高級(jí)語(yǔ)言都有的屬性集晚,如對(duì)象繼承窗悯,接口、多態(tài)等等偷拔。
我很早之前寫過的隨筆《Winform開發(fā)框架之?dāng)?shù)據(jù)訪問層的設(shè)計(jì)》 蒋院,介紹了我在.NET領(lǐng)域里面的數(shù)據(jù)庫(kù)訪問層的設(shè)計(jì),由于這種設(shè)計(jì)莲绰,能很大程度上減少代碼量欺旧,并提高開發(fā)效率,因此蛤签,我也想再IOS里面形成這樣的數(shù)據(jù)訪問設(shè)計(jì)辞友,雖然IOS里面可能主要是使用單機(jī)版的數(shù)據(jù)庫(kù),如SQLite數(shù)據(jù)庫(kù)顷啼,所以我想簡(jiǎn)化一些.NET里面的設(shè)計(jì)模型踏枣。
在.NET里面昌屉,我的框架分層主要如下所示,這種框架的設(shè)計(jì)模式茵瀑,已經(jīng)很好應(yīng)用在了我的Winform開發(fā)框架间驮、WCF及混合式開發(fā)框架、Web框架里面马昨。它們常見的分層模式竞帽,可以分為UI層、BLL層鸿捧、DAL層屹篓、IDAL層、Entity層匙奴、公用類庫(kù)層等等


而其中的DAL層的設(shè)計(jì)堆巧,示意圖如下所示,DAL層主要是通過繼承自BaseDAL基類(如BaseDALSQL)進(jìn)行, BaseDALSQL進(jìn)行更高一層的抽象泼菌,已達(dá)到更好的應(yīng)用目的谍肤。

而我們?cè)贗OS里面,則可以主要考慮SQLite數(shù)據(jù)庫(kù)即可哗伯,因此荒揣,我把設(shè)計(jì)通過簡(jiǎn)化,構(gòu)造下面的設(shè)計(jì)模型

在項(xiàng)目里面焊刹,數(shù)據(jù)訪問層的文件如下所示(為了演示測(cè)試方便系任,使用User表進(jìn)行操作)。

為了實(shí)現(xiàn)數(shù)據(jù)的承載虐块,我們需要把表的數(shù)據(jù)轉(zhuǎn)換為實(shí)體類進(jìn)行顯示和操作盈滴,因此實(shí)體類的設(shè)計(jì)也需要考慮好侈玄。由于數(shù)據(jù)訪問層的基類嚎货,封裝了大多數(shù)的數(shù)據(jù)操作瀑焦,也包括返回的數(shù)據(jù)對(duì)象和集合,因此數(shù)據(jù)訪問層的基類敞嗡,也涉及到了數(shù)據(jù)類型返回的問題颁糟。
由于IOS里面的Objective C里面沒有泛型這樣的東西,因此有兩種方式可以來實(shí)現(xiàn)基類實(shí)體類的處理:一是使用動(dòng)態(tài)類型id類型作為實(shí)體類類型喉悴,一種是使用一種半類型化的類型(實(shí)體類的基類)作為對(duì)象棱貌,我傾向于使用后者,因?yàn)楫吘贡容^接近真實(shí)的類型了箕肃。

//
//  BaseEntity.h
//  MyDatabaseProject
//
//  Created by 伍華聰 on 14-4-2.
//  Copyright (c) 2014年 伍華聰. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface BaseEntity : NSObject

@end
//
//  UserInfo.h
//  MyDatabaseProject
//
//  Created by 伍華聰 on 14-3-30.
//  Copyright (c) 2014年 伍華聰. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "BaseEntity.h"

@interface UserInfo : BaseEntity

@property(nonatomic, copy) NSString *ID;
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *fullName;

@end

這種繼承模式和.NET的繼承關(guān)系差不多了婚脱。

對(duì)于數(shù)據(jù)訪問層的設(shè)計(jì)代碼,它就是在數(shù)據(jù)訪問基類的定義如下,基類使用了FMDB的數(shù)據(jù)訪問類進(jìn)行操作的障贸,里面很多操作的接口就是模仿我在.NET領(lǐng)域里面的數(shù)據(jù)訪問層的設(shè)計(jì)错森。

//
//  BaseDAL.h
//  MyDatabaseProject
//
//  Created by 伍華聰 on 14-3-30.
//  Copyright (c) 2014年 伍華聰. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "FMDatabase.h"
#import "BaseEntity.h"

@interface BaseDAL : NSObject
{
    NSString *pathToDatabase;
}

#pragma 數(shù)據(jù)庫(kù)相關(guān)屬性

@property (nonatomic, strong) NSString *pathToDatabase;
@property (nonatomic, readonly) FMDatabase *database;  // 數(shù)據(jù)庫(kù)操作對(duì)象
@property (nonatomic, strong) NSString *tableName;//表名稱
@property (nonatomic, strong) NSString *primaryKey;//主鍵
@property (nonatomic, strong) NSString *sortField;//排序,默認(rèn)為主鍵
@property (nonatomic, assign, getter=isDescending) BOOL descending;//是否降序查詢


//將DataReader的屬性值轉(zhuǎn)化為實(shí)體類的屬性值篮洁,返回實(shí)體類(子類必須重寫)
-(id) rsToEntity:(FMResultSet *)rs;

//將實(shí)體對(duì)象的屬性值轉(zhuǎn)化為字典列表對(duì)應(yīng)的鍵值(子類必須重寫)
-(NSDictionary *) dictByEntity:(BaseEntity *) info;


#pragma 基礎(chǔ)操作接口

//根據(jù)指定對(duì)象的ID,從數(shù)據(jù)庫(kù)中刪除指定對(duì)象
- (BOOL) deleteById:(id) key;

//根據(jù)指定條件,從數(shù)據(jù)庫(kù)中刪除指定對(duì)象
- (BOOL) deleteByCondition:(NSString *) condition;

//更新對(duì)象屬性到數(shù)據(jù)庫(kù)中
-(BOOL) update:(BaseEntity *) info byKey:(id) key;

//插入指定對(duì)象到數(shù)據(jù)庫(kù)中
-(BOOL) insert:(BaseEntity *) info;

//插入指定對(duì)象到數(shù)據(jù)庫(kù)中,并返回最后插入的ID
-(NSInteger) insert2:(BaseEntity *) info;

//查詢數(shù)據(jù)庫(kù),檢查是否存在指定ID的對(duì)象
- (BaseEntity *) findById:(id) key;

//根據(jù)條件查詢數(shù)據(jù)庫(kù),如果存在返回第一個(gè)對(duì)象
-(BaseEntity *) findSingle:(NSString *) condition;


//根據(jù)條件查詢數(shù)據(jù)庫(kù),如果存在返回第一個(gè)對(duì)象
-(BaseEntity *) findSingle:(NSString *) condition orderBy:(NSString *) orderBy;

//根據(jù)條件查詢數(shù)據(jù)庫(kù),并返回對(duì)象集合
- (NSArray *) find:(NSString *) condition;

//根據(jù)條件查詢數(shù)據(jù)庫(kù),并返回對(duì)象集合
- (NSArray *) find:(NSString *) condition orderBy:(NSString *) orderBy;

//獲取表的全部數(shù)據(jù)
- (NSArray *) getAll;

//獲取表的全部數(shù)據(jù)
- (NSArray *) getAll:(NSString *) orderBy;

//獲取某字段數(shù)據(jù)字典列表
-(NSArray *) getFieldList:(NSString *) fieldName;

//獲取表的所有記錄數(shù)量
-(int) getRecordCount;

//根據(jù)條件涩维,獲取表查詢的記錄數(shù)量
-(int) getRecordCount:(NSString *) condition;

//根據(jù)條件,判斷是否存在記錄
-(BOOL) isExistRecord:(NSString *)condition;

//查詢數(shù)據(jù)庫(kù),檢查是否存在指定鍵值的對(duì)象
-(BOOL) isExist:(NSString *)fieldName value:(id) value;

//根據(jù)主鍵和字段名稱袁波,獲取對(duì)應(yīng)字段的內(nèi)容
-(NSString *) getFieldValue:(NSString *)key fieldName:(NSString *)fieldName;

//執(zhí)行SQL查詢語(yǔ)句瓦阐,返回查詢結(jié)果的所有記錄的第一個(gè)字段,用逗號(hào)分隔。
-(NSString *) sqlValueList:(NSString *)query;

#pragma 數(shù)據(jù)庫(kù)初始化函數(shù)及關(guān)閉操作

//根據(jù)SQLite數(shù)據(jù)庫(kù)地址初始化數(shù)據(jù)庫(kù)
-(id) initWithPath:(NSString *)filePath;

//根據(jù)SQLite數(shù)據(jù)庫(kù)名稱初始化數(shù)據(jù)庫(kù)
-(id) initWithFileName:(NSString *)fileName;

// 關(guān)閉連接
-(void) close;

@end

和我的.NET框架里面的數(shù)據(jù)訪問層設(shè)計(jì)一樣篷牌,數(shù)據(jù)訪問基類已經(jīng)封裝了大多數(shù)的數(shù)據(jù)訪問操作睡蟋,因此各個(gè)表的數(shù)據(jù)訪問對(duì)象,它的代碼就可以很簡(jiǎn)潔了枷颊。從上面的基類接口設(shè)計(jì)可以看到戳杀,里面一些實(shí)體類返回函數(shù)或者列表返回函數(shù),都使用了BaseEntity作為對(duì)象偷卧,我們具體在起子類使用的時(shí)候豺瘤,把它返回的對(duì)象再一次轉(zhuǎn)換即可。對(duì)于數(shù)據(jù)庫(kù)訪問基類听诸,我們以一個(gè)返回集合的接口實(shí)現(xiàn)來分析。

- (NSArray *) find:(NSString *) condition orderBy:(NSString *) orderBy {
    NSString *query = [NSString stringWithFormat:@"SELECT * FROM %@ ", self.tableName];
    if (condition != nil) {
        query = [query stringByAppendingFormat:@" where %@ ", condition];
    }
    
    if (orderBy != nil) {
        query = [query stringByAppendingString:orderBy];
    }
    else {
        query = [query stringByAppendingFormat:@" ORDER BY %@ %@ ", self.sortField, self.isDescending ? @"DESC" : @"ASC"];
    }
    
    FMResultSet *rs = [self.database executeQuery:query];
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:[rs columnCount]];
    
    BaseEntity *info = nil; //默認(rèn)初始化為空
    while ([rs next]) {
        info = [self rsToEntity:rs];
        [array addObject:info];
    }
    [rs close];
    
    return array;
}

上面代碼使用了參數(shù)化的SQL語(yǔ)句進(jìn)行查詢蚕泽,并且晌梨,對(duì)返回的數(shù)據(jù)庫(kù)的ResultSet進(jìn)行轉(zhuǎn)換為實(shí)體類。

info = [self rsToEntity:rs];

由于基類封裝了大多數(shù)的數(shù)據(jù)庫(kù)操作函數(shù)须妻,因此數(shù)據(jù)訪問層的具體表的實(shí)現(xiàn)類仔蝌,可以很簡(jiǎn)潔,但是已經(jīng)具備了常見的CRUD操作荒吏,以及一些分頁(yè)查詢等復(fù)雜的數(shù)據(jù)操作功能敛惊。

//
//  UserDAL.h
//  MyDatabaseProject
//
//  Created by 伍華聰 on 14-3-30.
//  Copyright (c) 2014年 伍華聰. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "FMDatabase.h"
#import "BaseDAL.h"
#import "UserInfo.h"

@interface UserDAL : BaseDAL
{
}

//單例模式
+(UserDAL *) defaultDAL;

@end

基于篇幅的原因,我將在下一篇介紹如何在界面層中使用這樣的數(shù)據(jù)訪問設(shè)計(jì)類绰更,先放上一些測(cè)試程序的界面截圖瞧挤。



最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市儡湾,隨后出現(xiàn)的幾起案子特恬,更是在濱河造成了極大的恐慌,老刑警劉巖徐钠,帶你破解...
    沈念sama閱讀 212,332評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件癌刽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)显拜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門衡奥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人远荠,你說我怎么就攤上這事杰赛。” “怎么了矮台?”我有些...
    開封第一講書人閱讀 157,812評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵乏屯,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我瘦赫,道長(zhǎng)辰晕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,607評(píng)論 1 284
  • 正文 為了忘掉前任确虱,我火速辦了婚禮含友,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘校辩。我一直安慰自己窘问,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評(píng)論 6 386
  • 文/花漫 我一把揭開白布宜咒。 她就那樣靜靜地躺著惠赫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪故黑。 梳的紋絲不亂的頭發(fā)上儿咱,一...
    開封第一講書人閱讀 49,919評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音场晶,去河邊找鬼混埠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诗轻,可吹牛的內(nèi)容都是我干的钳宪。 我是一名探鬼主播,決...
    沈念sama閱讀 39,071評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼扳炬,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吏颖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鞠柄,我...
    開封第一講書人閱讀 37,802評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤侦高,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后厌杜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奉呛,經(jīng)...
    沈念sama閱讀 44,256評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡计螺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞧壮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片登馒。...
    茶點(diǎn)故事閱讀 38,712評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖咆槽,靈堂內(nèi)的尸體忽然破棺而出陈轿,到底是詐尸還是另有隱情,我是刑警寧澤秦忿,帶...
    沈念sama閱讀 34,389評(píng)論 4 332
  • 正文 年R本政府宣布麦射,位于F島的核電站,受9級(jí)特大地震影響灯谣,放射性物質(zhì)發(fā)生泄漏潜秋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評(píng)論 3 316
  • 文/蒙蒙 一胎许、第九天 我趴在偏房一處隱蔽的房頂上張望峻呛。 院中可真熱鬧,春花似錦辜窑、人聲如沸钩述。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)牙勘。三九已至,卻和暖如春惨远,著一層夾襖步出監(jiān)牢的瞬間谜悟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工北秽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人最筒。 一個(gè)月前我還...
    沈念sama閱讀 46,473評(píng)論 2 360
  • 正文 我出身青樓贺氓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親床蜘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辙培,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容