ADO.NET


一、簡單介紹ADO.NET

System.Data:DataTable,DataSet,DataRow,DataColumn,DataRelation,Constraint

System.Data.Common(各種數據訪問類的基類和接口):DataColumnMapping,DataTableMapping

System.Data.SqlClient(對Sql Server進行操作的數據訪問類):

1)SqlConnection:數據庫連接器

2)SqlCommand:數據庫命名對象

3)SqlCommandBuilder:生存SQL命令

4)SqlDataReader:數據讀取器

5)SqlDataAdapter:數據適配器,填充DataSet

6)SqlParameter:為存儲過程定義參數

7)SqlTransaction:數據庫事務

二、SqlConnection(連接對象) 

1、連接字符串

基本語法:數據源(Data Source)+數據庫名稱(Initial Catalog)+用戶名(User ID)+密碼(Password)。

說明:

(1)必須指定Sql Server支持的兩種身份驗證方法(即Windows身份驗證和Sql Server身份驗證)中的一種。要想使用Windows身份驗證,必須在連接字符串中包括 Integrated Security 屬性:Data Source=ServerName;Integrated Security=True;默認情況下,Integrated Security屬性為False。

(2)連接字符串中可用的選項:

Application Name(應用程序名稱):應用程序的名稱。如果沒有被指定的話,它的值為.NET SqlClient Data Provider(數據提供程序)。

AttachDBFileName/extended properties(擴展屬性)/Initial File Name(初始文件名): 可連接數據庫的主要文件的名稱,包括完整路徑名稱。數據庫名稱必須用關鍵字數據庫指定。

Connect Timeout(連接超時)/Connection Timeout(連接超時):一個到服務器的連接在終止之前等待的時間長度(以秒計),缺省值為15。

Connection Lifetime(連接生存時間): 當一個連接被返回到連接池時,它的創建時間會與當前時間進行對比。如果這個時間跨度超過了連接的有效期的話,連接就被取消。其缺省值為0。

Connection Reset(連接重置): 表示一個連接在從連接池中被移除時是否被重置。缺少值為真。

Current Language(當前語言): SQL Server語言記錄的名稱。

Data Source(數據源)/Server(服務器)/Address(地址)/Addr(地址)/Network Address(網絡地址):SQL Server實例的名稱或網絡地址。

Encrypt(加密):當值為真時,如果服務器安裝了授權證書,SQL Server就會對所有在客戶和服務器之間傳輸的數據使用SSL加密。被接受的值有true(真)、false(偽)、yes(是)和no(否)。

Enlist(登記):表示連接池程序是否會自動登記創建線程的當前事務語境中的連接,其缺省值為真。

Database(數據庫)/Initial Catalog(初始編目):數據庫的名稱。

Integrated Security(集成安全)/Trusted Connection(受信連接):表示Windows認證是否被用來連接數據庫。它可以被設置成真、偽或者是和真對等的sspi,其缺省值為偽。

Max Pool Size(連接池的最大容量): 連接池允許的連接數的最大值,其缺省值為100。

Min Pool Size(連接池的最小容量): 連接池允許的連接數的最小值,其缺省值為0。

Network Library(網絡庫)/Net(網絡):用來建立到一個SQL Server實例的連接的網絡庫。支持的值包括: dbnmpntw (Named Pipes)、dbmsrpcn (Multiprotocol/RPC)、dbmsvinn(Banyan Vines)、dbmsspxn (IPX/SPX)和dbmssocn (TCP/IP)。協議的動態鏈接庫必須被安裝到適當的連接,其缺省值為TCP/IP。

Packet Size(數據包大小):用來和數據庫通信的網絡數據包的大小。其缺省值為8192。

Password(密碼)/Pwd:與帳戶名相對應的密碼。

Persist Security Info(保持安全信息):用來確定一旦連接建立了以后安全信息是否可用。如果值為真的話,說明像用戶名和密碼這樣對安全性比較敏感的數據可用,而如果值為偽則不可用。重置連接字符串將重新配置包括密碼在內的所有連接字符串的值。其缺省值為偽。

Pooling(池):確定是否使用連接池。如果值為真的話,連接就要從適當的連接池中獲得,或者,如果需要的話,連接將被創建,然后被加入合適的連接池中。其缺省值為真。

User ID(用戶ID):用來登陸數據庫的帳戶名。

Workstation ID(工作站ID):連接到SQL Server的工作站的名稱。其缺省值為本地計算機的名稱。

(3)例舉典型連接字符串

1>SQL Server連接字符串

標准安全連接: 

Data Source=myServerAddress;Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;或者

Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;Trusted_Connection=False;

可信連接:

Data Source=myServerAddress;Initial Catalog=myDataBase;Integrated Security=SSPI;或者

Server=myServerAddress;Database=myDatabase;Trusted_Connection=True; 

2> Access連接字符串

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\myDatabase.mdb;User Id=admin;Password=;

3>MySQL連接字符串

Server=myServerAddress;Database=myDatabase;Uid=myUsername;Pwd=myPassword;

4>DB2連接字符串

Server=myAddress:myPortNumber;Database=myDatabase;UID=myUsername;PWD=myPassword;

5>Oracle連接字符串

Data Source=TORCL;User Id=myUsername;Password=myPassword; 

2、創建連接對象

SqlConnectionStringBuilder connectionStringBuilder =  new  SqlConnectionStringBuilder()
{
    DataSource =  "" ,
    InitialCatalog =  "" ,
    UserID =  "" ,
    Password =  ""
};

SqlConnection connection = new SqlConnection(connectionStringBuilder.ToString()); 

3、打開和關閉連接對象

using (SqlConnection connection =  new  SqlConnection(connectionStringBuilder.ToString()))
{
     connection.Open();
     connection.Close();
}  

4、連接池

(1)建立一個數據庫連接是非常消耗時間和消耗資源的事情。ADO.NET提供了名為連接池的優化方法。連接池就是一個容器,它存放了一定數量的與數據庫服務器的物理連接。因此,當我們需要連接數據庫服務器的時候,只需去池(容器)中取出一條空閑的連接,而不是新建一條連接。這樣的話,我們就可以大大減少連接數據庫的開銷,從而提高了應用程序的性能。

(2)連接池具有類別區分,同一個時刻同一應用程序域可以有多個不同類型的連接池。細致的講,連接池是由進程、應用程序域、連接字符串以及windows標識(在使用集成的安全性時)共同組成簽名來標識區分的。但對於同一應用程序域來說,一般只由連接字符串來標識區分。當打開一條連接時,如果該條連接的類型簽名與現有的連接池類型不匹配,則創建一個新的連接池。反之,則不創建新的連接池。

例如:

// 創建連接對象1
using (SqlConnection conn1  =   new SqlConnection("DataSource = (local);Integrated Security = SSPI;Initial Catalog = Northwind"))    
{        
    conn1. Open ();       
}


// 創建連接對象2
using (SqlConnection conn2  new SqlConnection("DataSource = (local);Integrated Security = SSPI;Initial Catalog = pubs"))   
{       
    conn2. Open ();      
}


// 創建連接對象3
using (SqlConnection conn3  =   new SqlConnection("DataSource = (local);Integrated Security = SSPI;Initial Catalog = Northwind"))  
{        
    conn3. Open ();      

} 

創建了三個SqlConnection對象,但是管理時只需要兩個連接池。因為conn1與conn2的連接字符串相同,所有可以共享一個連接池。conn2與conn1,conn3不同,所以需要創建新的連接池。

(3)連接池行為可以通過連接字符串控制:

Connection Timeout:連接請求等待超時時間。默認為15秒。

Max Pool Size:連接池中最大連接數。默認為100。

Min Pool Size:連接池中最小連接數。默認為0。

Pooling:是否啟用連接池。ADO.NET默認是啟動的。

(4)使用T-SQL語句監視連接狀態:exec sp_who

(5)高效使用連接池的基本原則:

     ● 在最晚的時刻申請連接,在最早的時刻釋放連接。

     ● 關閉連接時先關閉相關用戶定義的事務。

     ● 確保並維持連接池中至少有一個打開的連接

     ● 盡力避免池碎片的產生。主要包括集成安全性產生的池碎片以及使用許多數據庫產生的池碎片。

三、SqlCommand(命令對象)

1、實例化的時候默認初始化的四個屬性:

CommandText:空字符串("") 

CommandTimeout:30

CommandType:CommandType.Text

Connection:Null

2、創建命令對象:

SqlCommand command = connection.CreateCommand();  // 這種方式比較好

SqlCommand command=new SqlCommand(); 

3、幾個重要的屬性:

(1)CommandText:獲取或設置要對數據源執行的 Transact-SQL 語句、表名或存儲過程!

(2)CommandType:設置你執行的SQL語句是存儲過程還是T-SQL(是一個枚舉)!

● Text:SQL文本命令(默認)

● StoredProcedure:存儲過程名稱

  ● TableDirect:表的名稱

(3)Parameters:設置你T-SQL中你需要用到的參數。

4、幾個重要的方法:

(1)ExecuteNonQuery:返回是影響的行數(int),主要執行更新,添加,刪除等操作!

(2)ExecuteReader:執行SQL或存儲過程,返回的是SqlDataReader類型,主要用來查詢!

注意這個方法的重載CommandBehavior枚舉,成員如下:

● Default:此查詢可能返回多個結果集。執行查詢可能會影響數據庫狀態。當不設置CommandBehavior標志時默認為Default。

● SingleResult:查詢返回個結果集。

● SchemaOnly:查詢僅返回列信息。當使用SchemaOnly時,用於SQL Server的.NET Framework數據提供程序將在要執行的語句前加上SET FMTONLY ON。

● KeyInfo:此查詢返回列和主鍵信息。

● SingleRow: 查詢應返回一行。

● SequentialAccess:提供一種方法,以便DataReader處理包含帶有大量二進制值的列的行。SequentialAccess不是加載整行,而是使DataReader將數據作為流來加載。然后可以使用GetBytes或GetChars方法來指定開始讀取操作的字節位置以及正在返回的數據的有限的緩沖區大小。

● CloseConnection:在執行該命令時,如果關閉關聯的DataReader對象,則關聯的Connection對象也將關閉。

(3)ExecuteScalar:返回執行結果集中的第一行第一列,如果沒有數據,則返回NULL!

可能返回NULL值,需要對結果進行判斷,如下:

object  my = cmd.ExecuteScalar();
if  ( object .Equals(my, null ))   // 可以使用Equals進行Null值的判斷,易讀性強
  Console.WriteLine( " Not Data " );
else
  Console.WriteLine( " Yes " );  

(4)CreateParameter:創建SqlParameter實例。 

5、異步執行命令:執行Command對象命令時,需要等待命令完成才能執行其他操作。比如,執行ExcuteNonQuery()方法,應用程序將會保持阻塞,直到數據操作成功完成或者異常終止以及連接超時。異步執行的根本思想是,在執行命令操作時,無需等待命令操作完成,可以並發的處理其他操作。ADO.NET提供了豐富的方法來處理異步操作,BeginExecuteNonQuery和EndExcuteNonQuery就是一對典型的為異步操作服務的方法。BeginExecuteNonQuery方法返回System.IAsyncResult接口對象。我們可以根據IAsyncResult的IsCompleted屬性來輪詢(檢測)命令是否執行完成。

using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Text;
using  System.Data; // 必須引入
using  System.Data.SqlClient; // 必須引入
namespace  Command
{
     class  Program
    {
         static  void  Main( string [] args)
        {
            SqlConnectionStringBuilder connStr =  new  SqlConnectionStringBuilder();
            connStr.DataSource =  @" .\SQLEXPRESS " ;
            connStr.IntegratedSecurity =  true ;
            connStr.InitialCatalog =  " db_MyDemo " ;
            connStr.AsynchronousProcessing =  true ; // 必須顯示說明異步操作
            StringBuilder strSQL =  new  StringBuilder();
             // 插入100個測試客戶
             for  ( int  i =  1 ; i <=  500 ; ++i)
            {
                strSQL.Append( " insert into tb_SelCustomer  " );
                strSQL.Append( " values(' " );
                 string  name =  " 測試客戶 "  + i.ToString();
                strSQL.Append(name);
                strSQL.Append( " ','0','0','13822223333','liuhaorain@163.com','廣東省深圳市寶安區',12.234556,34.222234,'422900','備注信息');  " );
            }
             using  (SqlConnection conn =  new  SqlConnection(connStr.ConnectionString))
            {
                conn.Open();
                SqlCommand cmd =  new  SqlCommand(strSQL.ToString(), conn);
                IAsyncResult pending = cmd.BeginExecuteNonQuery(); // 開始執行異步操作
                 double  time =  0 ;
                 // 檢查異步處理狀態
                 while  (pending.IsCompleted ==  false )
                {
                    System.Threading.Thread.Sleep( 1 );
                    time++;
                    Console.WriteLine( " {0}s " , time *  0.001 );
                }
                 if  (pending.IsCompleted ==  true )
                {
                    Console.WriteLine( " Data is inserted completely...\nTotal coast {0}s " , time *  0.001 );
                }
                cmd.EndExecuteNonQuery(pending); // 結束異步操作
            }
            Console.Read();
        }
    }

} 

6、如何獲取插入行的ID

using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Text;
using  System.Data;
using  System.Data.SqlClient;
namespace  Command2
{
     class  Program
    {
         static  void  Main( string [] args)
        {
             // 構造連接字符串
            SqlConnectionStringBuilder connStr =  new  SqlConnectionStringBuilder();
            connStr.DataSource =  @" .\SQLEXPRESS " ;
            connStr.IntegratedSecurity =  true ;
            connStr.InitialCatalog =  " db_MyDemo " ;
             // 拼接SQL語句
            StringBuilder strSQL =  new  StringBuilder();
            strSQL.Append( " insert tb_SelCustomer(Name)  " );
            strSQL.Append( " OUTPUT inserted.ID values(@Name) " );
             using  (SqlConnection conn =  new  SqlConnection(connStr.ConnectionString))
            {
                SqlCommand cmd =  new  SqlCommand(strSQL.ToString(), conn);
                SqlParameter para =  new  SqlParameter( " @Name " , SqlDbType.VarChar,  20 );
                para.Value =  " Kemi " ;
                cmd.Parameters.Add(para);
                 try
                {
                    conn.Open();
                     int  insertedID = ( int )cmd.ExecuteScalar(); // 獲取單個值
                    Console.WriteLine( " Inserted ID:{0} " , insertedID);
                }
                 catch  (Exception ex)
                {
                    Console.WriteLine( " {0} " , ex.Message);
                }
            }
            Console.Read();
        }
    }

} 

四、SqlParameter(Sql參數) 

1、幾個重要的屬性

ParameterName:設置參數名

Value:給參數設置值

Size:設置參數字節最大大小

SqlDbType:參數在SQL中的類型

2、命令對象添加參數集合的幾種方法

(1)AddWithValue

(2)Add

(3)AddRange

using  (SqlConnection connection =  new  SqlConnection( "" ))
  {
      SqlCommand command = connection.CreateCommand();
      command.CommandText =  "" ;
 
       // 可以使用這種方式添加多個參數,不過方式不夠好
      command.Parameters.Add( " @name " , SqlDbType.NVarChar).Value =  " yang " // 第一種方式
      command.Parameters.Add( " @age " , SqlDbType.Int).Value =  888 ;
      command.Parameters.Add( " @address " , SqlDbType.NVarChar,  100 ).Value =  " Jiang Su " ;
 
       // 這種方式直接給定參數名和參數就可以了,可操作性比較差
      command.Parameters.AddWithValue( " @name " " yang " );
      command.Parameters.AddWithValue( " @age " 888 ).SqlDbType = SqlDbType.Int;
      command.Parameters.AddWithValue( " @address " " Jiang su " ).SqlDbType = SqlDbType.NVarChar;
 
       // 直接使用參數集合添加你需要的參數,推薦這種寫法
      SqlParameter[] parameters =  new  SqlParameter[]
      {
           new  SqlParameter( " @name " ,SqlDbType.NVarChar, 100 ){Value =  " yang " },
           new  SqlParameter( " @age " ,SqlDbType.Int, 2 ){Value =  888 },
           new  SqlParameter( " @address " ,SqlDbType.NVarChar, 20 ){Value =  " Jiang Su " }, 
      };
      command.Parameters.AddRange(parameters);   // 參數也可以是一個Array數組,如果采用數組參數代碼的可讀性和擴展性就不是那么好了
 
      
// 當我們把參數都添加好之后,會生成一個“SqlParameterCollection”集合類型,相當於參數的集合
      
// 那么我們就可以對這些參數進行修改和移除了
      
// 說穿了“SqlParameterCollection”內部其實是一個List<SqlParameter>的集合,只是它里面的復雜度比較高,考慮的很全面
      command.Parameters[ 0 ].Value =  " hot girl " ;
      command.Parameters[ 0 ].Size =  200 ;

  } 

五、SqlDataReader(數據流讀取器)

1、基本用法 

using (SqlConnection conn = new SqlConnection(""))
{
    conn.Open();
    SqlCommand command = conn.CreateCommand();
    command.CommandText = "";
    using (SqlDataReader dr = command.ExecuteReader(CommandBehavior.CloseConnection))
    {
        while (dr.Read())
        {
            //開始讀取數據了,接下來你想怎么樣就怎么樣了
            string str = dr.GetSqlString(0).ToString();
        }
    }

} 

2、常用方法

(1)GetOrdinal:獲取指定列名的列序號(索引號),使用這個方法可以把經常變動的列進行固定

int name=dr.GetOrdinal("name"); //通過列名來獲取當前列的索引號

(2)GetName:  獲取列名,參數為指定列名的序列號,返回string

string columnName=dr.GetName(name);//通過列名所處的索引號來獲取列名名稱 

(3)IsDBNull:判斷當前讀取的數據是否為Null返回類型為Bool

dr.IsDBNull(coContractID)?"NULL":dr.GetInt32(coContractID).ToString();

(4)NextResult:當查詢為批處理查詢時,使用這個方法去讀取下一個結果集,返回值為Bool,如果存在多個結果集,則為 true;否則為 false。

(5)Read:讀取數據

3、常用屬性

(1)HasRow:判斷是否包含一行或多行,也就是判斷有沒有數據,返回類型為Bool。

(2)FieldCount:獲取讀取的列數,返回類型為Int。

(3)IsClosed:判斷讀取的數據流是否關閉。

4、性能剖析

讀取數據的時候會有很多種寫法,如dr[0].ToString(),dr["Name"].ToString(),dr.GetString(0),dr.GetSqlString(0)等等的讀取方式的寫法。

讀取數據性能總結:

SqlDataReader讀取方法

(1)DataReader 索引+基於[序列號]->dr[0].ToString() |Index-based access

(2)DataReader 索引+基於[列名]->dr["Name"].ToString() |性能最差

(3)GetString 開頭的+基於[序列號]->dr.GetString(0) |type-access

(4)GetSql 開頭的+基於[序列號]->dr.GetSqlString(0) |Provider-specific typed accessor 

(5)GetOrdinal() 通過列名獲取這個列的序列號 |這個方法在提高性能上面有作用

性能(4)-->(3)-->(1)-->(2) 

5、補充 

SqlDataReader是連接相關的,SqlDataReader中的查詢結果並不是放在程序中,而是放在數據庫服務器中,SqlDataReader只是相當於一個指針(游標),只能讀取當前游標指向的行,連接斷開就不能再讀取。這樣無論查詢結果有多少條,對程序占用的內存都幾乎沒有影響。但SqlDataReader對於小數據量的數據來說帶來的只有麻煩。 

六、SqlTransaction(事務)

1、事務中的命名存儲點

一旦你定義了命名存儲點,只能回滾命名存儲點之后的操作,這是要看情況而使用!

using  (SqlConnection conn =  new  SqlConnection(str))
{
    conn.Open();
    SqlTransaction transaction = conn.BeginTransaction();
    SqlCommand cmd = conn.CreateCommand();
    cmd.CommandText =  "" ;
    cmd.Transaction = transaction;
     // 使用命名存儲點
    transaction.Save( " this is point " );   // 定義命名存儲點,使用Save方法先保存存儲點,定義回滾數據的開始位置                 
    
// 這邊是你要回滾的操作代碼,TO DO... 
    
// 把從命名存儲點到這里的操作進行回滾
    transaction.Rollback( " this is point " );   // 回滾命名存儲點              

} 

2、SQL語句中的事務

BEGIN  TRANSACTION
      -- 你需要執行的更新,刪除,插入的語句
IF ( @@ERROR  >  0 // 這是系統變量,存儲你在執行更新,刪除,插入操作時發生錯誤的記錄編號
      ROLLBACK
ELSE

   COMMIT 

3、TransactionScope

using  (TransactionScope transactionScope =  new  TransactionScope())
{
     try
    {
         using  (SqlConnection connection =  new  SqlConnection())
        {
             //  TO DO
            
// 提交事務,如果有異常,他會自動回滾的
            transactionScope.Complete();
        }
    }
     catch  (Exception)
    {
         // 捕獲異常
         throw ;
    }

} 

七、SqlDataAdapter(數據適配器)

1、構造函數

四個重載:

(1)無參

(2)SqlDataAdapter(SqlCommand) 執行命令對象實例

(3)SqlDataAdapter(String,SqlConnection) 只能指定查詢語句,連接對象實例

(4)SqlDataAdapter(String,ConnectionString)

2、填充數據(Fill) 

DataSet dataSet =  new  DataSet();
using  (SqlConnection conn =  new  SqlConnection( "" ))
{
    conn.Open();
    SqlCommand command = conn.CreateCommand();
    command.CommandText =  " select name,age,address from MyInformation " ;
    SqlDataAdapter dataAdapter =  new  SqlDataAdapter(command);
    dataAdapter.Fill(dataSet);   // 填充數據
}

 

3、使用“SqlCommandBuilder”對數據進行增刪改查

(1)添加數據

using  (SqlConnection conn =  new  SqlConnection(ConnectionString()))
{
    conn.Open();
     // 構建查詢語句,也可以指定SqlCommand,其中變換的方法有很多
    SqlDataAdapter da =  new  SqlDataAdapter( " select LastName,FirstName from dbo.Employees " , conn);
    DataSet ds =  new  DataSet();
    da.Fill(ds);
     // 這句話很重要,它會把你在DataSet增加的數據轉化為SQL語句用來更新數據庫
    SqlCommandBuilder cmdBuilder =  new  SqlCommandBuilder(da);
     // 添加行,實例化一個行對象,注意是用NewRow來創建行
    DataRow row = ds.Tables[ 0 ].NewRow();
    row[ 0 ] =  " Yang " ;
    row[ 1 ] =  " 鬼頭 " ;
    ds.Tables[ 0 ].Rows.Add(row);   // 添加到表中
    da.Update(ds);              // 把DataSet中表和數據庫進行對比,更新

} 

(2)修改數據

using  (SqlConnection conn =  new  SqlConnection( "" ))
{
    SqlDataAdapter da =  new  SqlDataAdapter( " SQL語句或你自己定義的命令對象 " , conn);
    DataSet ds =  new  DataSet();
    da.Fill(ds);
    SqlCommandBuilder cmdBuilder =  new  SqlCommandBuilder(da);
    ds.Tables[ 0 ].Rows[ 12 ][ 1 ] =  "" // 修改數據
    da.Update(ds);
     // 調用Update方法其中隱式的調用了AcceptChanges方法,更新數據集中的數據
    
// 如果你繼續使用這個數據集而沒有調用這個方法,在后面的使用會出現異常
    ds.AcceptChanges();   // 這句話可以不寫的

} 

(3)刪除數據

using  (SqlConnection conn =  new  SqlConnection( "" ))
{
    SqlDataAdapter da =  new  SqlDataAdapter( " SQL語句或你自己定義的命令對象 " , conn);
    DataSet ds =  new  DataSet();
    da.Fill(ds);
    SqlCommandBuilder cmdBuilder =  new  SqlCommandBuilder(da);
     // 刪除數據
    ds.Tables[ 0 ].Rows[ 12 ].Delete();
    da.Update(ds);   // 這邊會隱式調用DataTable的AcceptChanges方法
}

4、關於“SqlDataAdapter”中Fill方法

ds.Fill(ds,5,10,"MyTable"); 

八、DataSet、DataTable、DataRow、DataColumn

表示數據存放在緩存中,DataSet里面可以包含多個DataTable,DataTable中有多個DataColumn和多個DataRow,包括對各種對DataTable的操作,以及對列和行的操作,在進行DataSet,DataTable進行操作的時候,應該先判斷它們是否為Null。


本貼屬於整理資料,非本人原創

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM