西西軟件園多重安全檢測(cè)下載網(wǎng)站、值得信賴(lài)的軟件下載站!
軟件
軟件
文章
搜索

首頁(yè)西西教程數(shù)據(jù)庫(kù)教程 → 怎么用LINQ Expression建立Query Object 模式

怎么用LINQ Expression建立Query Object 模式

相關(guān)軟件相關(guān)文章發(fā)表評(píng)論 來(lái)源:本站整理時(shí)間:2011/1/19 22:09:06字體大小:A-A+

作者:佚名點(diǎn)擊:315次評(píng)論:0次標(biāo)簽: LINQ

  • 類(lèi)型:數(shù)據(jù)庫(kù)類(lèi)大。1.7M語(yǔ)言:英文 評(píng)分:5.0
  • 標(biāo)簽:
立即下載
 這個(gè)問(wèn)題來(lái)源于Apworks應(yīng)用開(kāi)發(fā)框架的設(shè)計(jì)。由于命令與查詢(xún)職責(zé)的分離,使得基于CQRS體系結(jié)構(gòu)風(fēng)格的應(yīng)用系統(tǒng)的外部存儲(chǔ)系統(tǒng)的結(jié)構(gòu)變得簡(jiǎn)單起來(lái):在“命令”部分,簡(jiǎn)單地說(shuō),只需要Event Store和Snapshot Store來(lái)保存Domain Model;而“查詢(xún)”部分,則又是基于事件派送與偵聽(tīng)的系統(tǒng)集成。之前我也提到過(guò),“查詢(xún)”部分由于不牽涉到Domain Model,于是,它的設(shè)計(jì)應(yīng)該隨意性很大:不需要受到Domain Model的牽制,例如,我們可以根據(jù)UI所需的數(shù)據(jù)源結(jié)構(gòu)進(jìn)行“查詢(xún)”庫(kù)的設(shè)計(jì)。Greg Young在他的“CQRS Documents”一文中也提到了這樣一些相關(guān)話題:就“查詢(xún)”部分而言,雖然也存在“阻抗失衡”效應(yīng),但是事件模型與關(guān)系模型之間的這種效應(yīng),要遠(yuǎn)遠(yuǎn)小于對(duì)象模型與關(guān)系模型之間的“阻抗失衡”效應(yīng)。這是因?yàn)椋录P捅旧頉](méi)有結(jié)構(gòu),它僅僅表述“該對(duì)關(guān)系模型做哪些操作”這樣的概念。在設(shè)計(jì)上,Greg Young建議,采用一種非正規(guī)的方式設(shè)計(jì)“查詢(xún)”數(shù)據(jù)庫(kù),以便盡量減少讀取數(shù)據(jù)時(shí)所需的JOIN操作,比如可以選用基于第一范式(1NF)的關(guān)系模型。這是一種反范式模型,雖然Greg Young并沒(méi)有建議根據(jù)UI所需的數(shù)據(jù)源結(jié)構(gòu)進(jìn)行設(shè)計(jì),但思想是相同的:基于事件而不拘泥于對(duì)象模型本身。由此引申出來(lái)的另一個(gè)好處就是外部存儲(chǔ)系統(tǒng)架構(gòu)的隨意性:你可以選用任何存儲(chǔ)技術(shù)和存儲(chǔ)媒介,這又給基于系統(tǒng)架構(gòu)的性能優(yōu)化提供了便利。因?yàn)椴⒎撬械拇鎯?chǔ)架構(gòu)都支持“表”、“字段”、“存儲(chǔ)過(guò)程”、“JOIN”這些概念。

根據(jù)上面的簡(jiǎn)單分析,我們得到一個(gè)結(jié)論:通常情況下,或許基于CQRS體系結(jié)構(gòu)風(fēng)格的應(yīng)用系統(tǒng)更多的是采用的“平整”的外部存儲(chǔ)結(jié)構(gòu),簡(jiǎn)而言之,就是一個(gè)數(shù)據(jù)訪問(wèn)對(duì)象(DAO)對(duì)應(yīng)一張數(shù)據(jù)表。這也是我所設(shè)計(jì)的Apworks應(yīng)用開(kāi)發(fā)框架中默認(rèn)支持的一種存儲(chǔ)結(jié)構(gòu)。有讀過(guò)Apworks源代碼的朋友會(huì)發(fā)現(xiàn),在Apworks.Events.Storage命名空間下,有兩個(gè)定制的DAO:DomainEventDataObject,用于表述領(lǐng)域事件的數(shù)據(jù)結(jié)構(gòu),以及SnapshotDataObject,用于表述快照的數(shù)據(jù)結(jié)構(gòu),與之相對(duì)應(yīng)的就是數(shù)據(jù)庫(kù)中的兩張表:DomainEvents和Snapshots。雖然結(jié)構(gòu)變得這么簡(jiǎn)單,但是映射關(guān)系總還是需要維護(hù)的:最簡(jiǎn)單的就是需要在對(duì)象類(lèi)型名稱(chēng)與數(shù)據(jù)表名之間,以及對(duì)象屬性與數(shù)據(jù)表字段之間建立起映射關(guān)系。在Apworks中,這種映射關(guān)系是由Apworks.Storage.IStorageMappingResolver接口完成的。有關(guān)這個(gè)接口的內(nèi)容不是本文討論的重點(diǎn),暫且不深入分析了。

至此,也許你不會(huì)接受我上面的討論,認(rèn)為“基于UI設(shè)計(jì)數(shù)據(jù)庫(kù)結(jié)構(gòu)”或者“采用1NF、反范式設(shè)計(jì)數(shù)據(jù)庫(kù)結(jié)構(gòu)”是無(wú)法接受的,那么,接下來(lái)的討論可能對(duì)你來(lái)說(shuō)意義也不大了。因?yàn)橄旅娴膯?wèn)題是以上面的描述為基礎(chǔ)的:一個(gè)數(shù)據(jù)訪問(wèn)對(duì)象對(duì)應(yīng)一張數(shù)據(jù)表。不過(guò)即使你不認(rèn)同我的觀點(diǎn),我也建議你繼續(xù)看完本文。


Query Object模式
雖然只是簡(jiǎn)單的映射,但畢竟不能忽略這樣的映射關(guān)系。Apworks作為一個(gè)應(yīng)用開(kāi)發(fā)框架,需要提供方便的整合接口,以便今后能夠根據(jù)不同的客戶(hù)需求進(jìn)行擴(kuò)展。例如在存儲(chǔ)部分,數(shù)據(jù)的增刪改查(CRUD)是基于數(shù)據(jù)訪問(wèn)對(duì)象(DAO)的,這樣做的一個(gè)好處是能夠?qū)ν獠看鎯?chǔ)系統(tǒng)進(jìn)行抽象,使得訪問(wèn)存儲(chǔ)系統(tǒng)的部分能夠無(wú)需關(guān)系存儲(chǔ)系統(tǒng)的細(xì)節(jié)問(wèn)題?蛻(hù)有可能選擇SQL Server、Oracle、MySQL等關(guān)系型數(shù)據(jù)庫(kù)作為存儲(chǔ)系統(tǒng),也可以選擇其它的非關(guān)系型數(shù)據(jù)庫(kù)作為存儲(chǔ)系統(tǒng),因此,我們的設(shè)計(jì)不能僅僅局限于關(guān)系型數(shù)據(jù)庫(kù),我們需要同時(shí)考慮其它形式的數(shù)據(jù)存儲(chǔ)產(chǎn)品以便將來(lái)能夠方便地集成新的存儲(chǔ)方案。假設(shè)我們要設(shè)計(jì)一個(gè)針對(duì)DomainEventDataObject的“查詢(xún)”功能,我們需要考慮的問(wèn)題可能會(huì)有(但不一定僅限于):

需要查詢(xún)對(duì)象的哪些屬性(或者說(shuō)與DomainEventDataObject相對(duì)應(yīng)的數(shù)據(jù)表的哪些字段)
需要根據(jù)什么樣的條件進(jìn)行查詢(xún)
查詢(xún)是否需要排序
是否只查結(jié)果集中的任意一條記錄,還是要返回所有的記錄
在Apworks框架的Alpha版本中,查詢(xún)的方法定義在Apworks.Storage.IStorage接口中。比如,根據(jù)給定的查詢(xún)條件和排序方式,對(duì)指定DAO進(jìn)行查詢(xún)的方法定義如下:

1: /// <summary>
2: /// Gets a list of ordered objects from storage by given selection criteria and order.
3: /// </summary>
4: /// <typeparam name="T">The type of the object to get.</typeparam>
5: /// <param name="criteria">The <c>PropertyBag</c> instance which contains the criteria.</param>
6: /// <param name="orders">The <c>PropertyBag</c> instance which contains the ordering fields.</param>
7: /// <param name="sortOrder">The sort order.</param>
8: /// <returns>A list of ordered objects.</returns>
9: IEnumerable<T> Select<T>(PropertyBag criteria, PropertyBag orders, SortOrder sortOrder)
10: where T : class, new();

這個(gè)方法是個(gè)泛型方法,泛型類(lèi)型就是DAO的類(lèi)型。它接受三個(gè)參數(shù):前兩個(gè)是用于指定查詢(xún)條件和排序字段的PropertyBag,最后一個(gè)是指定排序方式的SortOrder。之所以采用PropertyBag,而不是接受SQL字符串,原因不僅是因?yàn)榭蚣鼙旧硇枰诮窈竽軌蚍奖愕刂С址顷P(guān)系型數(shù)據(jù)庫(kù),而且更重要的是,雖然SQL已經(jīng)成為一種業(yè)界標(biāo)準(zhǔn),但實(shí)際上不同的關(guān)系型數(shù)據(jù)庫(kù)產(chǎn)品對(duì)SQL的支持和實(shí)現(xiàn)方式也有所不同,有些關(guān)系型數(shù)據(jù)庫(kù)產(chǎn)品或許只支持SQL的一些子集,如果單純地把SQL字符串作為Select方法的參數(shù),明顯是不合理的。事實(shí)上,Apworks.Storage.IStorage實(shí)現(xiàn)了Query Object模式[MF, PoEAA],Martin Fowler在他的PoEAA(《企業(yè)應(yīng)用架構(gòu)模式》)中有以下幾點(diǎn)可以供讀者參考:

“編程語(yǔ)言是可以支持SQL語(yǔ)句的,但大多數(shù)開(kāi)發(fā)人員對(duì)此不太熟悉。而且,你需要了解數(shù)據(jù)庫(kù)設(shè)計(jì)方案以便構(gòu)造出查詢(xún)。可以通過(guò)創(chuàng)建特殊的、隱藏了SQL內(nèi)部參數(shù)化方法的查詢(xún)器方法避免這一點(diǎn)。但這樣難以構(gòu)造更多的特別查詢(xún)。而且,如果數(shù)據(jù)庫(kù)設(shè)計(jì)方案改變,就會(huì)需要復(fù)制到SQL語(yǔ)句中”
“查詢(xún)對(duì)象的一個(gè)共同特征是,它能夠利用內(nèi)存對(duì)象的語(yǔ)言而不是用數(shù)據(jù)庫(kù)方案來(lái)描述查詢(xún)。這意味著我可以使用對(duì)象和域名,而不是表和列名。如果對(duì)象和數(shù)據(jù)庫(kù)具有相同的結(jié)構(gòu),這一點(diǎn)就不重要,如果兩者有差異,查詢(xún)對(duì)象就很有用。為了實(shí)現(xiàn)這樣的視角變化,查詢(xún)對(duì)象需要知道數(shù)據(jù)庫(kù)結(jié)構(gòu)怎樣映射到對(duì)象結(jié)構(gòu),這一功能實(shí)際上要用到元數(shù)據(jù)映射”【daxnet注:上面提到過(guò),在Apworks框架中,這個(gè)元數(shù)據(jù)映射的實(shí)現(xiàn),就是IStorageMappingResolver】
“為了描述任意的查詢(xún),你需要一個(gè)靈活的查詢(xún)對(duì)象。然而,應(yīng)用程序經(jīng)常能用遠(yuǎn)少于SQL全部功能的操作來(lái)完成這一任務(wù),在此情況下,你的查詢(xún)對(duì)象就會(huì)比較簡(jiǎn)單。它不能代表任何東西,但它可以滿(mǎn)足特定的需要。此外,當(dāng)需要更多功能而進(jìn)行功能擴(kuò)充時(shí),通常不會(huì)比從零開(kāi)始創(chuàng)建一個(gè)全能的查詢(xún)對(duì)象更麻煩。因此,應(yīng)該為當(dāng)前需求創(chuàng)建一個(gè)功能最小化的查詢(xún)對(duì)象,并隨著需求的增加改進(jìn)這個(gè)查詢(xún)對(duì)象”
以上三點(diǎn)讓我很有感觸,特別是第三點(diǎn)。目前基于PropertyBag的設(shè)計(jì),只能夠支持以AND連接的查詢(xún)條件,比如,類(lèi)似“WHERE a=va AND b=vb AND c=vc…”這樣的查詢(xún),雖然在Apworks Alpha版本中,這樣的查詢(xún)已經(jīng)夠用了,但它不具備擴(kuò)展性,基于關(guān)系型數(shù)據(jù)庫(kù)的存儲(chǔ)設(shè)計(jì)Apworks.Storage.RdbmsStorage已經(jīng)將這種邏輯寫(xiě)死了,倘若我們需要一個(gè)復(fù)雜的查詢(xún),這種方式不僅沒(méi)法勝任,而且沒(méi)法擴(kuò)展。PropertyBag應(yīng)該要退休了。

在下一個(gè)版本的Apworks中,我使用.NET中的LINQ Expression代替了PropertyBag,并引入了一個(gè)WhereClauseBuilder的對(duì)象,專(zhuān)門(mén)根據(jù)LINQ Expression,針對(duì)關(guān)系型數(shù)據(jù)庫(kù)產(chǎn)生WHERE子句。使用LINQ Expression的好處有:

LINQ Expression是.NET下的一種查詢(xún)標(biāo)準(zhǔn),多數(shù)存儲(chǔ)系統(tǒng)產(chǎn)品能夠提供針對(duì)LINQ Expression的查詢(xún)解決方案,即使不提供,也可以自己定制Provider,雖然麻煩一點(diǎn),但總歸是可以實(shí)現(xiàn)的
LINQ Expression能夠完美地“利用內(nèi)存對(duì)象的語(yǔ)言而不是用數(shù)據(jù)庫(kù)方案”來(lái)描述查詢(xún),語(yǔ)言集成的特性,為開(kāi)發(fā)人員帶來(lái)了更多的便捷
Apworks中,Specification是基于LINQ Expression的,于是,Apworks.Storage.IStorage就能夠?qū)崿F(xiàn)基于Specification的查詢(xún),實(shí)現(xiàn)接口統(tǒng)一
于是技術(shù)問(wèn)題來(lái)了:如何將LINQ Expression轉(zhuǎn)換成WHERE子句,以便Apworks.Storage.IStorage的類(lèi)(Query Objects)能夠使用這個(gè)WHERE子句構(gòu)造出SQL語(yǔ)句,進(jìn)而通過(guò)ADO.NET直接訪問(wèn)數(shù)據(jù)庫(kù)?Apworks選用的是Expression Visitor的方案:使用Expression Visitor遍歷表達(dá)式樹(shù)(Expression Tree)然后產(chǎn)生WHERE子句。在討論Expression Visitor之前,讓我們回顧一下對(duì)象結(jié)構(gòu)以及Visitor模式。

Visitor模式

網(wǎng)上面有關(guān)Visitor模式的文章太多了,還有相當(dāng)一部分討論的比較深入透徹,我也就不多說(shuō)了?傊,Visitor模式在處理較復(fù)雜的對(duì)象結(jié)構(gòu)時(shí)會(huì)顯得十分自然:它能夠遍歷結(jié)構(gòu)中的每一個(gè)對(duì)象,然后針對(duì)不同的對(duì)象類(lèi)型作不同的處理。這就看上去像是為這些對(duì)象擴(kuò)展了一些方法一樣。之前,我有用過(guò)Visitor模式來(lái)驗(yàn)證程序配置節(jié)點(diǎn)的合理性,當(dāng)節(jié)點(diǎn)的類(lèi)型增加后,只需要擴(kuò)展Visitor即可實(shí)現(xiàn)新的驗(yàn)證邏輯,非常方便。模式歸模式,不同的應(yīng)用場(chǎng)景,實(shí)現(xiàn)方式還是有所不同的。經(jīng)典的Visitor例子,通常都是利用了函數(shù)的重載(多態(tài)性),并結(jié)合了Composite模式來(lái)說(shuō)明問(wèn)題,但實(shí)際上Visitor并非一定需要使用函數(shù)重載,也不是僅能用在Composite上。Expression Visitor的實(shí)現(xiàn)方式,就與這經(jīng)典的Visitor案例有所不同。

Expression Visitor

在System.Linq.Expressions命名空間下,有一個(gè)ExpressionVisitor的抽象類(lèi),我們只需要繼承這個(gè)抽象類(lèi),并重寫(xiě)其中的某些Visit方法,即可實(shí)現(xiàn)WHERE子句的生成。在這里我不打算繼續(xù)去細(xì)究ExpressionVisitor是如何遍歷表達(dá)式樹(shù)的,我還是描述一下實(shí)現(xiàn)WHERE子句生成的幾個(gè)細(xì)節(jié)問(wèn)題。

支持哪些運(yùn)算?
LINQ Expression的類(lèi)型有85種,但并不是SQL中會(huì)支持到所有的這85種類(lèi)型。目前Apworks打算支持常用的條件運(yùn)算,比如:大于、大于等于、小于、小于等于、不等于、等于這幾種,打算支持常用的邏輯運(yùn)算:AND、OR、NOT
支持哪些方法(函數(shù))?
目前Apworks支持的方法僅有三種:object.Equals、string.StartsWith和string.EndsWith。object.Equals將被翻譯成“object = value”,string.StartsWith和string.EndsWith將被翻譯成“LIKE”子句
支持內(nèi)聯(lián)函數(shù)和變量?
目前僅支持變量,不支持內(nèi)聯(lián)函數(shù)。
比如:可以用下面的方式來(lái)指定Expression:
1: int a = GetAge();
2: Expression<Func<Employee, bool>> expr = p => p.Age.Equals(a);

而不能使用下面的方式來(lái)指定Expression:
1: Expression<Func<Employee, bool>> expr = p => p.Age.Equals(GetAge());

支持?jǐn)U展?
當(dāng)然,只需要繼承已有的ExpressionVisitor類(lèi),并重寫(xiě)其中某些方法即可
在當(dāng)前的Apworks版本中,Apworks.Storage.Builders命名空間下定義了針對(duì)關(guān)系型數(shù)據(jù)庫(kù)的IWhereClauseBuilder接口,以及一個(gè)抽象實(shí)現(xiàn):Apworks.Storage.Builders.WhereClauseBuilder類(lèi),它不僅實(shí)現(xiàn)了IWhereClauseBuilder接口,同時(shí)繼承于System.Linq.Expressions.ExpressionVisitor抽象類(lèi),因此,WHERE子句生成的主體邏輯都在這個(gè)類(lèi)中。SqlWhereClauseBuilder類(lèi)繼承WhereClauseBuilder類(lèi),以便實(shí)現(xiàn)特定于SQL Server語(yǔ)法的WHERE子句生成器。

由于Apworks.Storage.Builders.WhereClauseBuilder類(lèi)的源代碼比較長(zhǎng),我就不貼在這里了,讀者朋友請(qǐng)【點(diǎn)擊此處】查看該類(lèi)的全部源代碼。



與規(guī)約(Specification)整合

在《EntityFramework之領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)實(shí)踐(十):規(guī)約模式》一文中,我提出了基于.NET的規(guī)約模式的實(shí)現(xiàn)方式,為了迎合.NET對(duì)LINQ Expression的支持,規(guī)約模式的實(shí)現(xiàn)也采用了LINQ Expression,而原來(lái)的IsSatisfiedfiedBy方法則改為直接使用LINQ Expression來(lái)獲得結(jié)果:
1: public interface ISpecification<T>
2: {
3: bool IsSatisfiedBy(T obj);
4: Expression<Func<T, bool>> Expression { get; }
5: }
6:
7: public abstract class Specification<T> : ISpecification<T>
8: {
9:
10: #region ISpecification Members
11:
12: public virtual bool IsSatisfiedBy(T obj)
13: {
14: return this.Expression.Compile()(obj);
15: }
16:
17: public abstract Expression<Func<T, bool>> Expression { get; }
18:
19: #endregion
20: }

回過(guò)頭來(lái)考察Select方法,原本第一個(gè)參數(shù)是用Expression<Func<T, bool>>類(lèi)型代替PropertyBag的,現(xiàn)在則可以直接使用ISpecification接口了,于是,我們的Query Object可以使用規(guī)約模式來(lái)支持?jǐn)?shù)據(jù)查詢(xún)了。


執(zhí)行過(guò)程與客戶(hù)端調(diào)用示例

基于上面的討論,Select方法的定義,已經(jīng)從使用PropertyBag作為查詢(xún)條件,轉(zhuǎn)變?yōu)槭褂肐Specification接口。注意:orders參數(shù)仍然使用PropertyBag,因?yàn)槟壳安淮蛩阒С只诒磉_(dá)式的排序條件:
1: IEnumerable<T> Select<T>(ISpecification<T> specification, PropertyBag orders, SortOrder sortOrder)
2: where T : class, new();

在Apworks.Storage.RdbmsStorage中,使用WhereClauseBuilder.BuildWhereClause方法,根據(jù)LINQ Expression生成WHERE子句,進(jìn)而產(chǎn)生SQL語(yǔ)句并使用ADO.NET訪問(wèn)關(guān)系型數(shù)據(jù)庫(kù):
1: public IEnumerable<T> Select<T>(ISpecification<T> specification, PropertyBag orders, Storage.SortOrder sortOrder)
2: where T : class, new()
3: {
4: try
5: {
6: Expression<Func<T, bool>> expression = null;
7: WhereClauseBuildResult whereBuildResult = null;
8: string sql = string.Format("SELECT {0} FROM {1}",
9: GetFieldNameList<T>(), GetTableName<T>());
10: if (specification != null)
11: {
12: expression = specification.GetExpression();
13: whereBuildResult = GetWhereClauseBuilder<T>().BuildWhereClause(expression);
14: sql += " WHERE " + whereBuildResult.WhereClause;
15: }
16: if (orders != null && sortOrder != Storage.SortOrder.Unspecified)
17: {
18: sql += " ORDER BY " + GetOrderByFieldList<T>(orders);
19: switch (sortOrder)
20: {
21: case Storage.SortOrder.Ascending:
22: sql += " ASC";
23: break;
24: case Storage.SortOrder.Descending:
25: sql += " DESC";
26: break;
27: default: break;
28: }
29: }
30: using (DbCommand command = CreateCommand(sql))
31: {
32: if (command.Connection == null)
33: command.Connection = Connection;
34: if (Transaction != null)
35: command.Transaction = Transaction;
36: if (specification != null)
37: {
38: command.Parameters.Clear();
39: var parameters = GetSelectCriteriaDbParameterList<T>(whereBuildResult.ParameterValues);
40: foreach (var parameter in parameters)
41: {
42: command.Parameters.Add(parameter);
43: }
44: }
45: DbDataReader reader = command.ExecuteReader();
46: List<T> ret = new List<T>();
47: while (reader.Read())
48: {
49: ret.Add(CreateFromReader<T>(reader));
50: }
51: reader.Close(); // Very important: reader MUST be closed !!!
52: return ret;
53: }
54: }
55: catch (ExpressionParseException)
56: {
57: throw;
58: }
59: catch (InfrastructureException)
60: {
61: throw;
62: }
63: catch (Exception ex)
64: {
65: throw ExceptionManager.HandleExceptionAndRethrow<StorageException>(ex,
66: Resources.EX_SELECT_FROM_STORAGE_FAIL,
67: typeof(T).AssemblyQualifiedName,
68: specification != null ? specification.ToString() : "NULL",
69: orders != null ? orders.ToString() : "NULL",
70: sortOrder);
71: }
72: }
73:


下面這個(gè)方法將根據(jù)Aggregate Root的類(lèi)型與ID,返回與之相關(guān)的所有Domain Events:
1: public virtual IEnumerable<IDomainEvent> LoadEvents(Type aggregateRootType, long id)
2: {
3: try
4: {
5:
6: PropertyBag sort = new PropertyBag();
7: sort.AddSort<long>("Version");
8: var aggregateRootTypeName = aggregateRootType.AssemblyQualifiedName;
9: ISpecification<DomainEventDataObject> specification = Specification<DomainEventDataObject>
10: .Eval(p => p.AggregateRootId == id && p.AggregateRootType == aggregateRootTypeName);
11:
12: return Select<DomainEventDataObject>(specification, sort, Apworks.Storage.SortOrder.Ascending)
13: .Select(p => p.ToEntity());
14: }
15: catch { throw; }
16: }

    相關(guān)評(píng)論

    閱讀本文后您有什么感想? 已有人給出評(píng)價(jià)!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過(guò)難過(guò)
    • 5 囧
    • 3 圍觀圍觀
    • 2 無(wú)聊無(wú)聊

    熱門(mén)評(píng)論

    最新評(píng)論

    發(fā)表評(píng)論 查看所有評(píng)論(0)

    昵稱(chēng):
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字?jǐn)?shù): 0/500 (您的評(píng)論需要經(jīng)過(guò)審核才能顯示)