在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
ADO.NET的名称起源于ADO(ACTIVEX DATA OBJECTS)
1 USING SYSTEM; 2 USING SYSTEM.COLLECTIONS.GENERIC; 3 USING SYSTEM.DATA; 4 USING SYSTEM.DATA.SQLCLIENT; 5 6 CLASS PROGRAM 7 { 8 STATIC VOID MAIN() 9 { 10 STRING CONNECTIONSTRING = GETCONNECTIONSTRING(); 11 STRING QUERYSTRING = "SELECT CATEGORYID, CATEGORYNAME FROM DBO.CATEGORIES;"; 12 USING (SQLCONNECTION CONNECTION = NEW SQLCONNECTION(CONNECTIONSTRING)) 13 { 14 SQLCOMMAND COMMAND = CONNECTION.CREATECOMMAND(); 15 COMMAND.COMMANDTEXT = QUERYSTRING; 16 TRY 17 { 18 CONNECTION.OPEN(); 19 SQLDATAREADER READER = COMMAND.EXECUTEREADER(); 20 WHILE (READER.READ()) 21 { 22 CONSOLE.WRITELINE("\T{0}\T{1}",READER[0], READER[1]); 23 } 24 CONSOLE.READKEY(); 25 READER.CLOSE(); 26 } 27 CATCH (EXCEPTION EX) 28 { 29 CONSOLE.WRITELINE(EX.MESSAGE); 30 } 31 } 32 } 33 34 STATIC PRIVATE STRING GETCONNECTIONSTRING() 35 { 36 // TO AVOID STORING THE CONNECTION STRING IN YOUR CODE, 37 // YOU CAN RETRIEVE IT FROM A CONFIGURATION FILE. 38 RETURN "DATA SOURCE=(LOCAL);INITIAL CATALOG=NORTHWIND;"+ "INTEGRATED SECURITY=SSPI"; 39 } 40 } ADO.NET 对象模型中有五个主要的组件,分别是Connection对象、Command对象、DataAdapter对象、DataSet对象以及DataReader对象。 ADO.NET对数据库的访问1.断开式数据库访问连接ADO.NET技术中,最重要的一条就是采用断开式数据库访问连接。所谓断开式数据库访问连接就是指客户端从数据源获取数据后,断开与数据源的连接,所有的数据操作都是针对本地数据缓存里的数据,当需要从数据源获取新数据或者被处理后的数据回传,这时客户端再与数据源相连接来完成相应的操作。断开式数据库访问连接最为核心的对象是DataSet对象,一旦通过数据适配器将数据填充至DataSet对象后,则今后的数据访问将直接针对DataSet对象展开。
首先,数据适配器(DataAdapter对象)选择连接和命令从数据库获取数据(Connection对象),通过填充操作将数据装载到数据集(DataSet对象);然后,数据集作为WinForm程序控件的“数据源”,通过数据绑定控件使数据集中的数据与WinForm程序控件中的数据保持一致。这时客户端使用的数据,在填充那一刻就已经被缓存了。用户通过WinForm应用程序操作的数据都是针对本地缓存里的数据进行的。当数据被修改后需要回传,再通过数据适配器重新连接数据库,将数据保存在数据库内。这里需要注意点是:将数据传递到用户界面时,ADO.NET采用XML格式。
2.连线式数据库访问连接
ADO.NET技术中,另一种数据库访问连接方式就是连线式数据库访问连接。所谓连线式数据库访问连接就是指客户端从数据源获取数据后,通过DataReader对象一条一条的从数据源之中将访问到的数据行读取到客户端的过程,在这种只读前进式的数据访问读取过程中,DataReader对象必须时刻与数据源保持连接,因此这种方式访问数据库的特点是:在前进只读方式访问数据表时候其效率较高。
3.对于Insert,Update,Delete等单向操作 对于插入、删除、修改等操作,由于是客户端应用程序向数据库提出的请求,不需要返回源数据,这个过程是单向操作。
主要有三个过程:首先,建立针对具体数据库的Connection对象,利用Connection对象的Open()方法打开数据库;然后,包含插入、删除、修改等命令信息的Command对象和DataAdapter内伴生的Command对象都可以利用Execute系列方法执行命令信息对应的命令;最后,利用Connection对象的Close()方法关闭数据库。
这里有一点要强调,DataAdapter内伴生的Command对象有四个:SelectCommand、InsertCommand、DeleteCommand、UpdateCommand 。命令信息中要求执行的是Select命令则由SelectCommand调用相应方法实现,Insert用InsertCommand,Delete用DeleteCommand,Update用UpdateCommand。
4.对于Select的双向操作对于查询操作而言,由于是客户端应用程序向数据库提出的请求,需要返回源数据,这个过程是双向操作。
首先,建立针对具体数据库的Connection对象,利用Connection对象的Open()方法打开数据库;然后,包含查询命令信息的Command对象和DataAdapter内伴生的SelectCommand对象都可以执行命令信息对应的命令,查询得到的数据可以通过DataReader对象获取或者被DataAdapter对象用Fill()方法填充在DataSet对象中;最后,利用Connection对象的Close()方法关闭数据库。 -1-4 ADO.NET连接数据库管理系统1.ADO.NET连接数据库的类型及方法ADO.NET连接数据库的内容包括:连接到数据库、执行数据库操纵命令和检索结果。可以直接处理检索到的结果,也可以将其放入DataSet对象,方便与来自多个源的数据和在层之间进行远程处理的数据组合在一起,以特殊方式向用户公开。
数据提供程序的四个核心对象表
列出的其他重要对象。
表4-2 .NET数据提供程序的其他对象表
四类命名空间分别是:SQLClient、OLEDB、Oracle、ODBC,其对应的数据提供程序
每一类数据提供程序的命名空间里都有一套对象,它们是通过前缀名进行区别的,例如:SQLClient命名空间包含的对象有:SqlConnection、SqlCommand、SqlDataReader、SqlDataAdapter等,OLEDB命名空间包含的对象有:OleDbConnection、OleDbCommand、OleDbDataReader、OleDbDataAdapter等。
调用sql 数据库 头文件 using System.Data.SqlClient; oracle 数据库
using System.Data;
using System.Data.OracleClient; access 数据库 头文件 using System.Data.OleDb; OLEDB数据提供程序所对应的数据源驱动程序表
OLE DB .NET Framework数据提供程序类位于System.Data.OleDb命名空间中。以下代码示例显示如何在应用程序中包含System.Data.OleDb命名空间。
using System.Data.OleDb;
注意:
不推荐将Access (Jet)数据库用作多线程应用程序(如ASP.NET应用程序)的数据源。如果必须将Access数据库用作ASP.NET应用程序的数据源,而且不能使用其他数据库(如SQL Server或MSDE),那么请注意,连接到Access数据库的ASP.NET应用程序可能会遇到连接问题,这些问题通常与安全权限有关。
ADO.NET与ODBC的关联DSN(数据源)是在odbc里设置的,用于连接数据库,ODBC只能用于关系型数据库。作为DSN的数据库连接可分为以前三种:
n 用户DSN:数据源对计算机来说是本地的,并且只能被当前用户访问,所以只有建立该数据源的用户才能访问该数据源,而且不能从网络上访问该数据源。
n 系统DSN:数据源对于计算机来说也是本地的,但并不是用户专用的,任何具有权限的用户都可以访问“系统DSN”,但是它还是不能从网络上来访问。
n 文件DSN:则与前两者不同,它们可以在所有安装了相同驱动程序的用户中共享,这些数据源对于计算机说都是本地的。也就是说可以通过网络上来访问。
显示了用ADO.NET测试的ODBC驱动程序。
ODBC数据提供程序所对应的数据源驱动程序表
ODBC .NET Framework数据提供程序类位于System.Data.Odbc命名空间中。以下代码示例显示如何在您的应用程序中包含System.Data.Odbc命名空间。
using System.Data.Odbc;
ADO.NET与Oracle的关联ADO.NET提供程序通过Oracle客户端连接软件启用对Oracle数据源的数据访问,该数据提供程序支持Oracle客户端软件8.1.7版或更高版本。
ADO.NET为与Oracle数据库关联提供程序类位于System.Data.OracleClient命名空间中,并包含在System.Data.OracleClient.dll程序集中。在编译使用该数据提供程序的应用程序时,需要同时引用System.Data.dll和System.Data.OracleClient.dll。
以下代码示例显示如何在您的应用程序中包含System.Data.OracleClient命名空间。
using System.Data;
using System.Data.OracleClient;
各个对象属性介绍 1.Connection 对象 要开发数据库应用程序,首先需要建立与数据库的连接。在ADO.NET中,数据库连接是通过Connection对象管理的。此外,事务管理也通过Connection对象进行。
x Connection对象的使用(1)Connection对象的属性和方法
Connection对象最常用的属性有ConnectionString和Database。其中ConnectionString属性用来获取或设置用于打开SQL Server数据库的字符串。Database属性用来获取当前数据库或连接打开后要使用的数据库的名称。ConnectionString属性要使用到连接字符串
Connection对象最常用的方法有Open()和Close()。其中Open()方法使用ConnectionString所指定的属性设置打开数据库连接。如果SqlConnection超出范围,则不会将其关闭。因此,必须通过调用Close显式关闭该连接。Close()方法用来关闭与数据库的连接。这是关闭任何打开连接的首选方法。
1 private static void OpenSqlConnection(string connectionString) 2 { 3 using (SqlConnection connection = new SqlConnection(connectionString)) 4 { 5 connection.Open(); 6 Console.WriteLine("ServerVersion: {0}", connection.ServerVersion); 7 Console.WriteLine("State: {0}", connection.State); 8 } 9 }
(2)可以通过图形来实现 .NET开发平台将一部分对象做成可视化的对象组件,程序员在编程的时候可以不用编写大量代码去定义和使用,只需要在工具箱中拖拽一个组件到窗体上,就可以进行配置使用。这种方式大幅的提高了编程效率,降低了出错率,但是在实际项目开发中不建议采取这种拖拽的形式进行ADO.NET开发。
通过拖拽形式建立Connection对象的数据库连接实验首先右击工具箱弹出菜单,在弹出菜单中点击“选择项”,在出现的选择工具箱箱中选中要使用到的SqlConnection等一系列ADO.NET对象组件,在它们前面的复选框中打上对号,并;最后点击“确定”,SqlConnection等组件就出现在工具箱中
在工具箱中选择“数据”选项卡,找到刚刚添加上的SqlConnection对象组件,点击后向窗体拖放,在设计窗口下的组件栏出现SqlConnection1对象。选中SqlConnection1对象,在属性窗口找到ConnectionString属性,选择右侧下拉框中的“新建连接”。设计窗口会弹出一个“添加连接”窗口这个窗口是配置SqlConnection1对象的连接属性而专用的窗口。在“添加连接”窗口,首先要在列表中选择SQL Server服务器,然后要指定用户和密码,最后选择要连接的数据库。点击“测试连接”,如果连接成功,则会显示一则成功消息。这时,就可以看到SqlConnection1对象的ConnectionString属性右侧栏中出现一长串字符,这就是连接字符串。在后续的代码编写过程中,就可以使用SqlConnection1对象对数据库进行访问了。
代码实现
SQL Client方式的连接字符串如下:
"Data Source=127.0.0.1;Persist Security Info=False;Initial Catalog=MyDB;Integrated Security=SSPI"
"Data Source"是关键字,这里与"server"可以呼唤,都用来表示数据库服务器,等号后面就要对应具体的数据库服务器的IP或者名称。"Persist Security Info"关键字,如果将该关键字设置为true或yes,将允许在打开连接后,从连接中获得涉及安全性的信息(包括用户标识和密码)。如果在建立连接时必须提供用户标识和密码,最安全的方法是在使用信息打开连接后丢弃这些信息,在Persist Security Info设置为false或no时会发生这种情况。当您向不可信的源提供打开的连接,或将连接信息永久保存到磁盘时,这点尤其重要。如果将Persist Security Info保持为false,可帮助确保不可信的源无法访问连接中涉及安全性的信息,并帮助确保任何涉及安全性的信息都不会随连接字符串信息在磁盘上持久化。"Initial Catalog"关键字可以与"database"关键字互换,用来指定需要连接的数据库服务器中具体某一数据库,等号后面就要对应具体的数据库,本例中就是"MyDB"。"Integrated Security"关键字表示是否使用Windows身份验证(通常称为集成安全性)连接到服务器数据库上。如果键值为"true"或者"SSPI"表示指定Windows身份验证,如果键值为"false"表示不指定Windows身份验证。但是,只有键值"SSPI"将适用于OleDb .NET Framework数据提供程序。对于ODBC .NET Framework数据提供程序,必须使用以下键/值"Trusted_Connection=yes;"指定Windows身份验证。对于不使用Windows身份验证而使用SQL Server身份验证,使用"User ID=*****;Password=*****;"替换掉"Integrated Security"关键字及其键值,来指定用户名和密码。
② OLE DB方式的连接字符串如下:
"Provider=SQLOLEDB;Data Source=127.0.0.1;Persist Security Info=False;
Initial Catalog=MyDB;Integrated Security=SSPI"
"Provider"关键字用来指定哪一类数据源,其键值取值可以参见表4-4,其他关键字用法与SQL Client方式相同。
③ ODBC方式的连接字符串如下:
"Driver={SQL Server};Server=127.0.0.1;Database=MyDB;Trusted_Connection=Yes;UID=Administrator"
"Driver"关键字用来指定哪一类数据源,其键值取值可以参见表4-5,其他关键字用法与SQL Client方式相似。区别就是"Trusted_Connection"、"UID"与"Integrated Security"、"User ID",关键字虽然不同,但是用法相同。
方式一
SqlConnection sql=new SqlConnection("Data Source=10.5.0.30;Initial Catalog=TTDB; User ID=TrainingDeveloper Pwd=Password");
sql.open();
方式二 SqlConnection SqlConnection1 = new SqlConnection();
SqlConnection1.ConnectionString = "Data Source=10.5.0.30;Initial Catalog=TTDB;
User ID=TrainingDeveloper Pwd=Password";
2. Command对象 数据库连接建立好以后,要操作数据库就得向数据库发送命令信息。所谓命令信息就是指SQL语句或者存储过程名称。除了增删查改数据外,命令信息还可以对数据源执行一些不返回结果集的查询,以及改变数据源结构的数据定义命令信息,如DDL语言。 在ADO.NET中,命令信息是通过Command对象管理的。与数据库建立连接之后,可以使用Command对象执行命令并从数据源返回结果。
Command对象的使用(1)Command对象的属性和方法
Command对象最常用的属性有CommandText、CommandType和Connection。其中CommandText属性用来获取或设置欲执行的内容,可以是SQL语句或者存储过程名称。CommandType属性用来获取或设置命令类型,指示当前命令是SQL语句还是存储过程名称。当将CommandType属性设置为StoredProcedure时,应将CommandText属性设置为存储过程的名称;CommandType属性设置为Text时,应将CommandText属性设置为SQL语句。Connection属性用来获取或设置Command对象使用的Connection对象。
Command对象最常用的方法有ExecuteNonQuery()、ExecuteReader()和ExecuteScalar()。其中ExecuteNonQuery()方法对连接执行Transact-SQL语句并返回受影响的行数。可以使用ExecuteNonQuery()方法来执行目录操作(例如查询数据库的结构或创建诸如表等的数据库对象),或通过执行UPDATE、INSERT或DELETE语句,在不使用 DataSet 的情况下更改数据库中的数据。虽然ExecuteNonQuery()方法不返回任何行,但映射到参数的任何输出参数或返回值都会用数据进行填充。对于UPDATE、INSERT和DELETE语句,返回值为该命令所影响的行数。对于所有其他类型的语句,返回值为-1。如果发生回滚,返回值也为-1。ExecuteReader()方法将CommandText所设置的命令信息发送到数据库,并生成一个SqlDataReader对象。ExecuteScalar()方法执行查询,并返回查询所返回的结果集中第一行的第一列,忽略其他列或行。使用ExecuteScalar()方法从数据库中检索单个值(例如一个聚合值)。与使用ExecuteReader()方法,然后使用SqlDataReader()返回的数据执行生成单个值所需的操作相比,此操作需要的代码较少。 eg. 1 //参数 执行的sql 语句 ,连接字符串 2 private static void CreateCommand(string queryString,string connectionString) 3 { 4 using (SqlConnection connection = new SqlConnection(connectionString)) 5 { 6 SqlCommand command = new SqlCommand(); 7 8 command.Connection = connection; 9 10 command.CommandTimeout = 15; 11 command.CommandType = CommandType.Text; 12 command.CommandText = queryString; 13 connection.Open(); 14 //查询 15 SqlDataReader reader = command.ExecuteReader(); 16 while (reader.Read()) 17 { 18 Console.WriteLine(String.Format("{0}, {1}",reader[0], reader[1])); 19 } 20 } 21 } 实训 向窗体添加Command对象组件并配置实验 Sqlconnection,SqlCommand,oledbconnection,oledbcommand对象组件在默认情况下工具箱中是没有的,需要手动添加。 鼠标左键单击SqlCommand对象,配置其Connection属性为sqlConnection1对象,配置其commandtext为用户自定义的SQL语句,如:select * from student。
u实验步骤(3):配置oledbCommand组件SQL命令
鼠标左键单击oledbconnection对象,命名为oledbconnection1,在其connectionstring属性之中点击下拉列表框,在弹出的添加连接对话框中点击数据源的更改按钮,在弹出的更改数据源对话框中,选择数据源为“Microsoft Access数据库文件”,点击确定后浏览本机Access数据库文件即可。
鼠标左键单击oledbCommand对象,配置其Connection属性为oleDbConnection1对象,配置其commandtext为用户自定义的SQL语句,如:select * from teacher。
学习2:通过编写代码来设置Command对象实验
SqlCommand类的构造函数表
单击button按钮后,向school数据库的student表之中插入一条数据。 1 private void button1_Click(object sender, EventArgs e) 2 { 3 SqlConnection conn = new SqlConnection(); 4 conn.ConnectionString = "Data Source=(local);Initial Catalog=school;User ID=sa"; 5 conn.Open(); 6 //开始事务 7 SqlTransaction sqltran = conn.BeginTransaction(); 8 string sqlstring = "insert into student(sno,sname) values(3390220,'张三')"; 9 SqlCommand comm = new SqlCommand(sqlstring, conn); 10 comm.Transaction = sqltran; 11 int p = comm.ExecuteNonQuery(); 12 //事务提交 13 sqltran.Commit(); 14 comm.Dispose(); 15 comm.Clone(); 16 conn.Dispose(); 17 conn.Close(); 18 } 事务代码 1 static void Main(string[] args) 2 { 3 4 SqlConnection sqlConn = new SqlConnection( 5 ConfigurationManager.ConnectionStrings["ConnStr"].ConnectionString); 6 SqlTransaction sqlTrans = null; 7 try 8 { 9 sqlConn.Open(); 10 sqlTrans = sqlConn.BeginTransaction();//事务开始 11 SqlCommand sqlComm = new SqlCommand("", sqlConn, sqlTrans); 12 sqlComm.CommandTimeout = 120; 13 sqlComm.CommandType = System.Data.CommandType.Text; 14 15 string insertSql = "insert into dbo.TransTestTable values (66,'66');"; 16 string updateSql = "update dbo.TransTestTable set [Name] = '77' where [Id] = 66;"; 17 18 sqlComm.CommandText = insertSql; 19 sqlComm.ExecuteNonQuery();//执行insert 20 21 sqlComm.CommandText = updateSql; 22 sqlComm.ExecuteNonQuery();//执行update 23 //throw new Exception("test exception.the transaction must rollback"); 24 25 sqlTrans.Commit();//事务提交 26 } 27 catch (Exception ex) 28 { 29 sqlTrans.Rollback();//事务回滚 30 Console.WriteLine(ex.Message); 31 } 32 finally 33 { 34 if (sqlConn.State != System.Data.ConnectionState.Closed) 35 sqlConn.Close(); 36 } 37 38 Console.ReadLine(); 39 }
sql 函数大全 https://www.cnblogs.com/ldy_ai/p/3644619.html
7 .NET中的事务处理例如在一个销售系统里,通过帐单处理模块完成对销售表的数据处理,客户端销售人员已经将库存货品销售出去,但与销售表相关的库存表尚未及时更新,结果娶她销售人员再读取库存数据就会出现数据不一致的现象。 所谓事务就是这样的一系列操作,这些操作被视为一个操作序列,要么全做,要么全部做,是一个不可分割的程序单元。在数据库数据处理中经常会发生数据更新事件,为了保证数据操作的安全与一致,大型数据库服务器都支持事务处理,以保证数据更新在可控的范围内进行。ADO.NET通过Connection对象的BeginTransaction()方法实现对事务处理的支持,该方法返回一个实现IDbTransaction接口的对象,而该对象是在System.Data中被定义的。
1.事务处理命令调用Connection对象的BeginTransaction()方法,返回的是一个DbTransaction对象。DbTransaction对象常用的事务处理命令包括下面三个:
nBegin:在执行事务处理中的任何操作之前,必须使用Begin命令来开始事务处理;
nCommit:在成功将所有修改都存储于数据库时,才算是提交了事务处理;
nRollback:由于在事务处理期间某个操作失败,而取消事务处理已做的所有修改,这时将发生回滚;
不同命名空间里的DbTransaction类名称是不同,表示也不同。参见表4-9:
表4-9 DbTransaction类在不同命名空间里的表
SqlTransaction对象的使用SqlTransaction对象的属性和方法
SqlTransaction对象表示要对数据源进行的事务处理,其常用的属性有Connection。Connection属性是用来获取与该事务关联的SqlConnection对象,或者如果该事务不再有效,则为空引用。SqlTransaction对象常用的方法有Save()、Commit()和Rollback(),其中Save()方法在事务中创建保存点(它可用于回滚事务的一部分),并指定保存点名称;Commit()方法用来提交数据库事务,Rollback()方法从挂起状态回滚事务。
private void button1_Click(object sender, EventArgs e) { SqlConnection conn = new SqlConnection(); conn.ConnectionString = "Data Source=(local);Initial Catalog=school;User ID=sa"; conn.Open(); //注意此处为一个事务开始之处 SqlTransaction sqltran = conn.BeginTransaction(); string sqlstring = "insert into student(sno,sname) values(3390220,'张三')"; SqlCommand comm = new SqlCommand(sqlstring, conn); comm.Transaction = sqltran; int p = comm.ExecuteNonQuery(); sqltran.Commit(); //此处为一个事务正常结束之处 comm.Dispose(); comm.Clone(); conn.Dispose(); conn.Close(); } /* 第一步:通过连接对象产生事务对象sqltran; 第二步:将事务对象sqltran绑定到命令对象的Transaction属性; 第三步:执行命令对象; 第四步:通过事务对象sqltran的Commit()方法,提交事务,或者通过事务对象sqltran的Rollback()方法回滚事务。 */
2 ADO.Net 查询和检索数据
n 掌握DataSet对象
n DataTable、DataColumn和DataRow对象
n DataAdapter对象
n DataReader对象
-1 DataSet对象1.数据集DataSet概述数据集DataSet是断开与数据源的连接时,可以被使用的数据记录在内存中的缓存,断开式数据库访问连接部分提到,可以把数据集DataSet看作是内存中的数据库。它在应用程序中对数据的支持功能十分强大。DataSet一经创建,就能在应用程序中充当数据库的位置,为应用程序提供数据支持。
数据集DataSet的数据结构可以在.net开发环境中通过向导完成,也可以通过代码来增加表、数据列、约束以及表之间的关系。数据集DataSet中的数据既可以来自数据源,也可以通过代码直接向表中增加数据行。这也看出,数据集DataSet类似一个客户端内存中的数据库,可以在这个数据库中增加、删除数据表,可以定义数据表结构和表之间的关系,可以增加、删除表中的行。
数据集DataSet不考虑其中的表结构和数据是来自数据库、XML文件还是程序代码,因此数据集DataSet不维护到数据源的连接。这缓解了数据库服务器和网络的压力。对数据集DataSet的特点总结可以总结为四点:
第一,使用数据集对象DataSet无需与数据库直接交互;
第二,DataSet对象是存储从数据库检索到的数据的对象;
第三,DataSet对象是零个或多个表对象的集合,这些表对象由数据行和列、约束和有关表中数据关系的信息组成;
第四,DataSet对象既可容纳数据库的数据,也可以容纳非数据库的数据源。
2.DataSet的结构,常用属性及方法数据集DataSet是以DataSet对象形式存在的。DataSet对象是一种用户对象,此对象表示一组相关表,在应用程序中这些表作为一个单元来引用。DataSet对象的常用属性是Tables、Relations等
DataSet对象由数据表及表关系组成,所以DataSet对象包含DataTable对象集合Tables和DataRelation对象集合 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论