亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > .NET > 正文

設計.NET應用程序數據訪問層五大原則

2024-07-10 13:12:10
字體:
來源:轉載
供稿:網友

  摘要:大多數使用.net框架組件工作的開發人員的一個核心工作是實現數據訪問功能,他們建立的數據訪問層(data access layer)是應用程序的精華部分。本文概述了使用visual studio .net和.net框架組件建立數據訪問層需要考慮的五個想法。這些技巧包括通過使用基類(base class)利用面相對象技術和.net框架組件基礎結構,使類容易繼承,在決定顯示方法和外部界面前仔細地檢驗需求。

  如果你正在建立以數據為中心(data-centric)的.net框架組件應用程序,你最終必須建立數據訪問層。也許你知道在.net框架組件中建立自己的代碼有很多好處。因為它支持實現和接口(interface)繼承,你的代碼更容易重復使用,特別是被使用不同的框架組件兼容(framework-compliant)語言的開發人員使用。本文我將概述為基于.net框架組件的應用程序建立數據訪問層的五條規則。

  開始前,我必須提醒你建立的任何基于本文討論的規則的數據訪問層必須與傳統windows平臺上開發人員喜歡的多層或者n層應用程序兼容。在這種結構中,表現層包含web窗體、windows窗體、調用與數據訪問層的工作相應的事務層的xml服務代碼。該層由多個數據訪問類(data access classe)組成。換句話說,在事務處理協調不是必要的情況下,表現層將直接調用數據訪問層。這種結構是傳統的模型-視列表-控制程序(model-view-controller,mvc)模式的變體,在多種情況下被visual studio .net和它暴露的控件采用。

  規則1:使用面向對象特性

  最基本的面向對象事務是建立一個使用實現繼承的抽象類。這個基類可以包括你的所有數據訪問類通過繼承能夠使用的服務。如果那些服務足夠了,它們就能通過在整個組織的基類分布實現重復使用。例如最簡單的情況是基類能夠為衍生類處理連接的建立過程,如列表1所示。

imports system.data.sqlclient

namespace acme.data
public mustinherit class dalbase : implements idisposable
private _connection as sqlconnection

protected sub new(byval connect as string)
_connection = new sqlconnection(connect)
end sub

protected readonly property connection() as sqlconnection
get
return _connection
end get
end property

public sub dispose() implements idisposable.dispose
_connection.dispose()
end sub

end class
end namespace

  列表1.簡單基類

  在列表中可以看到,對dalbase類作了mustinherit標記(c#中的抽象),以確保它在繼承關系中使用。接著該類在公共構造函數中包括了一個實例化的私有sqlconnection對象,它接收連接字符串作為一個參數。當來自idisposable接口的dispose方法確保連接對象已經被配置了的時候,受保護的(protected)connection屬性允許衍生類訪問該連接對象。

  即使在下面簡化的例子中你也能開始看到抽象基類的用處:

public class webdata : inherits dalbase
public sub new()
mybase.new(configurationsettings.appsettings("connectstring"))
end sub

public function getorders() as dataset
dim da as new sqldataadapter("usp_getorders", me.connection)
da.selectcommand.commandtype = commandtype.storedprocedure
dim ds as new dataset()

da.fill(ds)
return ds
end function
end class

  在這種情況下,webdata類繼承自dalbase,結果就是不必擔心實例化sqlconnection對象,而是通過mybase關鍵字(或者c#中的基關鍵字)簡單地把連接字符串傳遞給基類。webdata類的getorders方法能使用me.connection(在c#中是this.connection)訪問受保護的屬性。雖然這個例子相對簡單,但是你將在規則2和3中看到基類也提供了其它的服務。

  當數據訪問層必須在com+環境中運行時抽象的基類很有用。在這種情況下,因為允許組件使用com+的必要代碼復雜得多,所以更好的方式是建立一個如列表2所示的服務組件(serviced component)基類。

transaction(transactionoption.supported), _
eventtrackingenabled(true)> _
public mustinherit class dalservicedbase : inherits servicedcomponent

private _connection as sqlconnection

protected overrides sub construct(byval s as string)
_connection = new sqlconnection(s)
end sub

protected readonly property connection() as sqlconnection
get
return _connection
end get
end property
end class

  列表2.服務組件基類

  在這段代碼中,dalservicedbase類包含的基本功能與列表1中的相同,但是加上了從system.enterpriseservices名字空間的servicedcomponent的繼承,并且包括了一些屬性,指明組件支持對象構造、事務和靜態跟蹤。接著該基類仔細地捕捉組件服務管理器(component services manager)中的構造字符串并且再次建立和暴露sqlconnection對象。我們要注意的是當一個類繼承自dalservicedbase時,它也繼承了屬性的設置。換句話說,一個衍生類的事務選項也設置為supported。如果衍生類想重載這種行為,它能在類的層次重新定義該屬性。

  此外,衍生類在適當情況下應該有利于自身重載和共享方法。使用重載的方法(一個方法有多個調用信號)在本質上有兩種情況。首先,它們在一個方法需要接受多種類型的參數時使用??蚣芙M件中的典型例子是system.convert類的方法。例如tostring方法包含18個接受一個參數的重載方法,每個重載方法的類型不同。其次,重載的方法用于暴露參數數量不斷增長的信號,而不是不同類型的必要參數。在數據訪問層中這類重載變得效率很高,因為它能用于為數據檢索和修改暴露交替的信號。例如getorders方法可以重載,這樣一個信號不接受參數并返回所有訂單,但是附加的信號接受參數以表明調用程序希望檢索特定的顧客訂單,代碼如下:

public overloads function getorders() as dataset
public overloads function getorders(byval customerid as integer) as dataset

  這種情況下的一個好的實現技巧是抽象getorders方法的功能到一個能被每個重載信號調用的私有的或者受保護的方法中。

  共享方法(c#中的靜態方法)也能用于暴露數據訪問類的所有實例能夠訪問的字段、屬性和方法。盡管共享成員不能與使用組件服務(component services)的類一起使用,但是對于在數據訪問類的共享構造函數中檢索并被所有實例讀取的只讀數據是有用的。使用共享成員讀/寫數據時要小心,因為為了訪問該共享數據,執行的多個線程可能會競爭。

  規則2:堅持設計指導

  隨visual studio .net一起發布的在線文檔中有一個叫"類庫開發人員的設計指導(design guidelines for class library developers)"的主題,它覆蓋了類、屬性和方法的名字轉換,是重載的成員、構造函數和事件的補充模式。你必須遵循名字轉換的主要原因之一是.net框架組件提供的跨語言(cross-language)繼承。如果你在visual basic .net中建立一個數據訪問層基類,你想確保使用.net框架組件兼容的其它語言的開發人員能繼承它并容易理解它怎樣工作。通過堅持我概述的指導方針,你的名字轉換和構造就不會是語言特定的(language specific)。例如,你可能注意到在本文例子的代碼中第一個詞小寫,并加上intercaps是用于方法的參數的,每個詞大寫是用于方法的,基類使用base標志來標識它是一個抽象類。

  可以推測.net框架組件設計指導都是普通設計模式,像gang of four (addison-wesley, 1995)寫的design patterns記載的一樣。例如.net框架組件使用了observer模式的一個變體,叫做event模式,在類中暴露事件時你必須遵循它。

  規則3:利用基礎結構(infrastructure)

  .net框架組件包括一些類和構造,它們能輔助處理通常的與基礎結構相關的事務,例如裝置和異常處理。通過基類把這些概念與繼承組合起來將非常強大。例如,你能考慮一下system.diagnostics名字空間中暴露的跟蹤功能。除了提供trace和debug類外,該名字空間還包括衍生自switch和tracelistener的類。switch類的booleanswitch和traceswitch能被配置用于打開和關閉應用程序和配置文件,在traceswitch中可以暴露多層次跟蹤。tracelistener類的textwritertracelistener和eventlogtracelistener分別將trace和debug方法的輸入定位到文本文件和事件日志。

  這樣作的結果是給基類添加了跟蹤功能,使衍生類記錄消息日志更簡單。接著應用程序能使用配置文件控制是否允許跟蹤。你能包括一個booleanswitch類型的私有變量并在構造函數中實例化它來給列表1中的dalbase添加這個功能:

public sub new(byval connect as string)
_connection = new sqlconnection(connect)
_dalswitch = new booleanswitch("dal", "data access code")
end sub

  傳遞給booleanswitch的參數包括名字和描述。接著你能添加一個受保護的屬性打開和關閉開關,也能添加一個屬性使用trace對象的writelineif方法格式化并寫入跟蹤消息:

protected property tracingenabled() as boolean
get
return _dalswitch.enabled
end get
set(byval value as boolean)
_dalswitch.enabled = value
end set
end property

protected sub writetrace(byval message as string)
trace.writelineif(me.tracingenabled, now & ": " & message)
end sub

  通過這種途徑,衍生類自己并不知道開關(switch)和監聽(listener)類,當數據訪問類產生一個有意義的信號時能夠簡單地調用writetrace方法。

type="system.diagnostics.textwritertracelistener"
initializedata="dallog.txt" />

  列表3.跟蹤的配置文件

  為了建立一個監聽器并打開它,需要使用應用程序配置文件。列表3顯示了一個簡單的配置文件,它能夠打開剛才顯示的數據訪問類開關,并通過mylistener調用textwritertracelistener把輸出定位到文件dallog.txt中。當然,你能通過從tracelistener類衍生程序化地建立監聽器并把該監聽器直接包含在數據訪問類中。

public class dalexception : inherits applicationexception
public sub new()
mybase.new()
end sub

public sub new(byval message as string)
mybase.new(message)
end sub

public sub new(byval message as string, byval innerexception as
exception)
mybase.new(message, innerexception)
end sub
'在這兒添加自定義成員
public connectstring as string
end class

  列表4.自定義異常類

  你從中收益的第二個基礎結構是結構化異常處理(seh)。在最基本的層次,數據訪問類能夠暴露它的衍生自system.applicationexception 的exception(異常)對象并能進一步暴露自定義成員。例如,列表4中顯示的dalexception對象能用于包裝數據訪問類中的代碼產生的異常。接著基類能暴露一個受保護的方法包裝該異常,組裝自定義成員,并把它發回給調用程序,如下所示:

protected sub throwdalexception(byval message as string, _
byval innerexception as exception)
dim newmine as new dalexception(message, innerexception)

newmine.connectstring = me.connection.connectionstring
me.writetrace(message & "{" & innerexception.message & "}")
throw newmine
end sub

  使用這種方法,衍生類能簡單地調用受保護的方法,傳遞進去一個特定的數據異常(典型的有sqlexception或者 oledbexception),該異常被截取并添加了從屬于特定數據域的消息?;愒赿alexception中包裝該異常并把它發回到調用程序。這就允許調用程序用一個catch語句輕易地捕捉所有來自數據訪問類的異常。

  作為選擇之一,你可以看一看msdn上發布的"exception management application block overview"。該框架組件通過一系列對象結合了異常和應用程序日志記錄。實際上,通過從.net 框架組件提供的baseapplicationexception類衍生的自定義異常類能夠簡單地插入該框架組件。

  規則4:仔細選擇外部界面

  在你設計數據訪問類的方法時,需要考慮它們怎樣接受和返回數據。對大多數開發人員來說,主要有三個選擇:直接使用ado.net對象、使用xml、使用自定義類。

  如果直接暴露ado.net對象,你能使用一到兩個編程模型。第一個包括數據集和數據表對象,它們對不連接數據訪問很有用。有很多關于數據集和與它關聯的數據表的文章,但是當你必須使用從下層數據存儲斷開的數據時它才最有用處。換句話說,數據集能在應用程序各層之間傳遞,即使那些層在物理上是分布式的,當業務和數據服務層放置在同一群服務器上并且與表現服務分開時也能使用。此外,數據集對象是通過基于xml的web服務返回數據的理想方法,因為它們是可串行化的,因此能在soap回應消息中返回。

  這與使用實現idatareader接口的類(例如sqldatareader 和oledbdatareader)訪問數據不同。數據閱讀器(data reader)用只向前的,只讀的方式訪問數據。兩者之間最大的不同是數據集和數據表對象能在應用程序域之間傳遞,通過傳遞值(by value)實現,然而數據閱讀器能在各處傳遞,但是一般通過引用(by reference)實現。在列表5中,read和getvalues在服務器過程中執行并且它們的返回值復制到客戶端。

  該圖顯示了數據閱讀器怎樣存活在應用程序域中,它在那兒它被建立,并且對它的所有訪問結果都在客戶端和服務器應用程序域之間的循環之中。這意味著當數據訪問方法在相同的應用程序域運行時,應該返回數據閱讀器作為調用者。

  使用數據閱讀器時有兩個問題需要考慮。首先,當你從數據訪問類的一個方法返回數據閱讀器時,你必須考慮與數據閱讀器關聯的連接對象的生存期。默認情況是當調用程序通過數據閱讀器重復時連接仍然是忙的,不幸的是當調用程序結束后,連接仍然打開,因此它不返回到連接池(如果允許連接池)。但是,當通過傳遞commandbehavior.closeconnection 枚舉給command對象的executereader方法,連接的close方法被調用時,你能命令數據閱讀器關閉它的連接。

  其次,為了把表現層從特定的框架組件數據提供程序(例如sqlclient或者oledb)中分離出來,調用代碼應該使用idatareader接口(例如sqldatareader)而不是具體類型來引用返回值。通過這種方法,如果應用程序后端從oracle移植到 sql server,或者數據訪問類的一個方法的返回類型改變了,表現層也不需要更改。

  如果你希望數據訪問類返回xml,你可以從system.xml名字空間中的xmldocument和xmlreader中選擇一個,它與數據集和idatareader類似。換句話說,當數據從數據源斷開時你的方法應該返回一個xmldocument(或者xmldatadocument),然而xmlreader可用于訪問xml數據的流。

  最后,你也能決定與公共屬性一起返回自定義類。這些類可以使用serialization(串行化)屬性標記,這樣它們就能跨越應用程序域復制。另外,如果你從方法中返回多個對象,就需要強化類型(strongly typed)的集合類。

imports system.xml.serialization

_
public class book : implements icomparable
public productid as integer
public isbn as string
public title as string
public author as string
public unitcost as decimal
public description as string
public pubdate as date

public function compareto(byval o as object) as integer _
implements icomparable.compareto
dim b as book = ctype(o, book)
return me.title.compareto(b.title)
end function
end class

public notinheritable class bookcollection : inherits arraylist
default public shadows property item(byval productid as integer) _
as book
get
return me(indexof(productid))
end get
set(byval value as book)
me(indexof(productid)) = value
end set
end property

public overloads function contains(byval productid as integer) as _
boolean
return (-1 <> indexof(productid))
end function

public overloads function indexof(byval productid as integer) as _
integer
dim index as integer = 0
dim item as book

for each item in me
if item.productid = productid then
return index
end if
index = index + 1
next
return -1
end function

public overloads sub removeat(byval productid as integer)
removeat(indexof(productid))
end sub

public shadows function add(byval value as book) as integer
return mybase.add(value)
end function
end class

  列表6.使用自定義類

  上列表(列表6)包含了一個簡單的book類和與它關聯的集合類的例子。你能注意到book類用serializable做了標記,使它跨越應用程序域能使用"by value"語法。該類實現了icomparable接口,因此當它包含在一個集合類中的時候,默認情況下它將按title排序。bookcollection類從system.collections名字空間的arraylist衍生,并且為了將該集合限制到book對象而隱藏了item屬性和add方法。

  通過使用自定義類你完全地控制了數據的表現、開發人員的效率并且沒有依賴ado.net的調用。但是這種途徑需要更多的代碼,因為.net框架組件沒有包含任何與對象相關的技術映射。在這種情況下,你應該在數據訪問類中建立一個數據讀取器并使用它來組合自定義類。

  規則5:抽象.net框架組件數據提供程序

  最后一條規則說明了為什么和怎樣抽象數據訪問類內部使用的.net框架組件數據提供程序(data provider)。先前我說過ado.net編程模型暴露了特定的.net框架組件數據提供程序,包括sqlclient、oledb和其它msdn online web站點上可用的。但是這種設計的結果是提高性能,為數據提供程序暴露特定數據源功能的能力,它強迫你決定使用那種數據提供程序編碼。換句話說,開發人員典型地會選擇使用sqlclient或oledb,接著在各自的名字空間直接對它們的類進行編程。

  如果你想改變.net框架組件數據提供程序,你必須重新編寫數據訪問方法。為了避免這種情況發生,你可以使用abstract factory設計模式。使用這種模式,你能建立一個簡單的類,它暴露方法來建立主要的.net框架組件數據提供程序對象(command、connection、data adapter和parameter),而那些對象基于傳遞給構造函數的.net框架組件數據提供程序的信息。列表7中的代碼就是這樣一個簡單的類。

public enum providertype :int {sqlclient = 0, oledb = 1}

public class providerfactory {
public providerfactory(providertype provider) {
_ptype = provider;
_initclass();
}

public providerfactory() {
_initclass();
}

private providertype _ptype = providertype.sqlclient;
private bool _ptypeset = false;
private type[] _contype, _comtype, _parmtype, _datype;


private void _initclass() {
_contype = new type[2];
_comtype = new type[2];
_parmtype = new type[2];
_datype = new type[2];

// 為提供程序初始化類型
_contype[(int)providertype.sqlclient] = typeof(sqlconnection);
_contype[(int)providertype.oledb] = typeof(oledbconnection);
_comtype[(int)providertype.sqlclient] = typeof(sqlcommand);
_comtype[(int)providertype.oledb] = typeof(oledbcommand);
_parmtype[(int)providertype.sqlclient] = typeof(sqlparameter);
_parmtype[(int)providertype.oledb] = typeof(oledbparameter);
_datype[(int)providertype.sqlclient] = typeof(sqldataadapter);
_datype[(int)providertype.oledb] = typeof(oledbdataadapter);
}

public providertype provider {
get {
return _ptype;
}
set {
if (_ptypeset) {
throw new readonlyexception("provider already set to "
+ _ptype.tostring());
}
else {
_ptype = value;
_ptypeset = true;
}
}
}
public idataadapter createdataadapter(string commandtext,idbconnection
connection) {
idataadapter d;
idbdataadapter da;

d = (idataadapter)activator.createinstance(_datype[(int)_ptype],
false);
da = (idbdataadapter)d;
da.selectcommand = this.createcommand(commandtext, connection);
return d; }

public idataparameter createparameter(string paramname, dbtype
paramtype) {
idataparameter p;
p = (idataparameter)activator.createinstance(_parmtype[(int)_ptype],
false);
p.parametername = paramname;
p.dbtype = paramtype;
return p;
}

public idataparameter createparameter(string paramname, dbtype
paramtype, object value) {
idataparameter p;
p = (idataparameter)activator.createinstance(_parmtype[(int)_ptype],
false);
p.parametername = paramname;
p.dbtype = paramtype;
p.value = value;
return p;
}

public idbconnection createconnection(string connect) {
idbconnection c;
c = (idbconnection)activator.createinstance(_contype[(int)_ptype],
false);
c.connectionstring = connect;
return c;
}

public idbcommand createcommand(string cmdtext, idbconnection
connection) {
idbcommand c;
c = (idbcommand)activator.createinstance(_comtype[(int)_ptype],
false);
c.commandtext = cmdtext;
c.connection = connection;
return c;
}
}

  列表7. providerfactory

  為了使用該類,數據訪問類的代碼必須對多個.net框架組件數據提供程序實現的接口(包括idbcommand、idbconnection、idataadapter和idataparameter)進行編程。例如,為了使用一個參數化存儲過程的返回值來填充數據集,必須在數據訪問類的某個方法中有下面的代碼:

dim _pf as new providerfactory(providertype.sqlclient)
dim cn as idbconnection = _pf.createconnection(_connect)
dim da as idataadapter = _pf.createdataadapter("usp_getbook", cn)

dim db as idbdataadapter = ctype(da, idbdataadapter)
db.selectcommand.commandtype = commandtype.storedprocedure
db.selectcommand.parameters.add(_pf.createparameter("@productid",dbtype.int32, id))

dim ds as new dataset("books")
da.fill(ds)

  典型的情況是你在類的層次聲明providerfactory變量并在數據訪問類的構造函數中實例化它。另外,它的構造函數與從配置文件中讀取的提供程序一起組裝,而不應該是硬代碼。你可以想象,providerfactory是數據訪問類的一個重大的補充,并且能被包括進部件,分發給其它的開發人員。

  結論

  在web服務時代將建立越來越多的應用程序操作來自獨立的應用程序層的數據。如果你遵循一些基本規則并形成習慣,編寫數據訪問代碼將更快、更容易,并且更能重新使用,把你的錯誤保存到服務器,允許你保持數據獨立。

  • 本文來源于網頁設計愛好者web開發社區http://www.html.org.cn收集整理,歡迎訪問。
  • 發表評論 共有條評論
    用戶名: 密碼:
    驗證碼: 匿名發表
    亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
    国产成人精品日本亚洲专区61| 欧美有码在线观看| 视频在线一区二区| 日韩欧美国产视频| 亚洲欧美资源在线| 精品国产成人在线| 久久国产精品亚洲| 性欧美视频videos6一9| 国产精品高潮呻吟久久av无限| 国产色综合天天综合网| 日韩精品亚洲视频| 主播福利视频一区| 日韩一区二区福利| 国产91精品黑色丝袜高跟鞋| 欧美贵妇videos办公室| 国产视频丨精品|在线观看| 日韩美女中文字幕| 98精品国产自产在线观看| 欧美丰满少妇xxxxx做受| 国产精品69久久| 日韩经典一区二区三区| 欧美黄色成人网| 亚洲专区在线视频| 精品视频在线播放色网色视频| 按摩亚洲人久久| 欧美黄网免费在线观看| 亚洲精品久久7777777| 国产亚洲福利一区| 久久躁狠狠躁夜夜爽| 国产精品视频免费在线观看| 动漫精品一区二区| 亚洲黄色www| 亚洲精品免费av| 国产精品久久国产精品99gif| 成人免费福利在线| 久久夜色精品国产| 欧美激情在线狂野欧美精品| 久久91亚洲精品中文字幕奶水| **欧美日韩vr在线| 激情亚洲一区二区三区四区| 日韩精品小视频| 亚洲国产欧美自拍| 最近中文字幕2019免费| 欧美性受xxxx白人性爽| 亚洲欧美在线看| 久久综合久久美利坚合众国| 国产精品高潮呻吟久久av无限| 国产精品亚洲аv天堂网| 久久久免费在线观看| 国产综合久久久久| 亚洲乱码国产乱码精品精| 欧美日韩亚洲视频一区| 91精品在线播放| 精品国产电影一区| 色爱av美腿丝袜综合粉嫩av| 色综合伊人色综合网| 麻豆国产va免费精品高清在线| 国产精品久久久久99| 欧美影院在线播放| 亚洲日韩欧美视频一区| 日韩亚洲欧美中文高清在线| 日韩av片永久免费网站| 91在线观看免费高清完整版在线观看| 高清日韩电视剧大全免费播放在线观看| 国产一区二区视频在线观看| 亚洲剧情一区二区| 亚洲成色777777女色窝| 国产精品爽爽爽爽爽爽在线观看| 久久夜色精品国产亚洲aⅴ| 色偷偷88888欧美精品久久久| 国自产精品手机在线观看视频| 国产主播精品在线| 久久国产精品亚洲| 欧美电影免费观看高清| 国产精品一区二区久久精品| 欧美男插女视频| 国产精品成人一区二区三区吃奶| 中文欧美在线视频| 亚洲无限av看| 一区二区三区美女xx视频| 亚洲欧美国产一本综合首页| 欧美成人精品三级在线观看| 91亚洲精品在线观看| 中文字幕免费精品一区| 午夜精品久久久久久99热软件| 亚洲a级在线播放观看| 欧美日本高清视频| 色久欧美在线视频观看| 91精品国产色综合久久不卡98| 日韩免费观看av| 成人美女免费网站视频| 青青在线视频一区二区三区| 久久久精品欧美| 成年人精品视频| 欧美性受xxxx黑人猛交| 国产在线拍偷自揄拍精品| 亚洲人精选亚洲人成在线| 97超碰蝌蚪网人人做人人爽| 欧美亚洲国产日本| 国产成人亚洲综合青青| 欧美精品福利在线| 国产区精品在线观看| 欧美一级免费视频| 91在线观看免费高清完整版在线观看| yellow中文字幕久久| 欧美成人黑人xx视频免费观看| 国产精品免费一区二区三区都可以| 国产69精品久久久久9| 5566日本婷婷色中文字幕97| 欧美在线视频免费播放| 久久精品国产一区| 国产亚洲精品久久久优势| 日本一欧美一欧美一亚洲视频| 亚洲自拍小视频| 九九精品视频在线| 成人h视频在线| 久久久国产视频| 亚洲欧洲在线观看| 精品久久久久国产| 国产美女91呻吟求| 国产亚洲精品美女久久久| 日韩免费av在线| 51色欧美片视频在线观看| 亚洲国产欧美日韩精品| 久久综合久中文字幕青草| 欧美另类高清videos| 日韩精品中文字幕在线播放| 欧美xxxx做受欧美.88| 日韩欧美国产网站| 日韩在线视频中文字幕| 亚洲国产91精品在线观看| 亚洲自拍偷拍福利| 色综久久综合桃花网| 欧美性xxxxxxxxx| 欧美高清激情视频| 色狠狠久久aa北条麻妃| 97国产精品人人爽人人做| 欧美日在线观看| 国产精品美腿一区在线看| 全亚洲最色的网站在线观看| 午夜精品一区二区三区在线视| 国产精品mp4| 91精品国产91久久久久久不卡| 国产69久久精品成人| 亚洲一级免费视频| 亚洲欧美激情精品一区二区| 日本精品性网站在线观看| 欧美日韩亚洲精品内裤| 日日噜噜噜夜夜爽亚洲精品| 亚洲小视频在线| 国产精品美女主播在线观看纯欲| 91精品在线观看视频| 亚洲国产天堂久久综合| 伊人伊人伊人久久| 91chinesevideo永久地址| 日韩精品在线播放| 亚洲一区av在线播放| 国产精品劲爆视频| 亚洲老头同性xxxxx| 亚洲天堂第二页| 亚洲国产精品小视频| 亚洲精品自产拍| 欧美日韩一区二区免费在线观看| 美女福利精品视频|