C#学习笔记24——数据库编程

各种.Net平台开发语言开发数据库应用程序,一般并不直接对数据库操作(直接在程序中调用存储过程等除外),而是先完成数据连接和通过数据适配器填充DataSet对象,然后客户端再通过读取DataSet来获得需要的数据,同样更新数据库中数据,也是首先更新DataSet,然后再通过DataSet来更新数据库中对应的数据的。可见了解、掌握ADO.NET,首先必须了解、掌握DataSet。DataSet主要有三个特性:
  1. 独立性。DataSet独立于各种数据源。微软公司在推出DataSet时就考虑到各种数据源的多样性、复杂性。在.Net中,无论什么类型数据源,它都会提供一致的关系编程模型,而这就是DataSet。
  2. 离线(断开)和连接。DataSet既可以以离线方式,也可以以实时连接来操作数据库中的数据。这一点有点像ADO中的RecordSet。
  3. DataSet对象是一个可以用XML形式表示的数据视图,是一种数据关系视图。

 

什么是ADO.NET架构?

如今大部分的应用程序都需要后台的数据库来为其提供大量的数据来源,而应用程序与数据库之间的交流称为数据访问。而ASP.NET则使用ADO.NET(Active X Data Object)作为数据的访问与操作的协议,从而使得我们可以在Internet上操作这些数据。

ADO.NET的数据访问分为两大部分:数据集(DataSet)与数据提供源(Data Provider,我并不清楚中文该怎么翻译,就叫它数据提供源好了,不要和“数据源”—Data Source搞混)。

数据集:

数据集是一个非在线,完全由内存表示的一系列数据,可以被看作一份本地磁盘数据库中部分数据的拷贝。数据集完全驻留内存,可以被独立于数据库地访问或者修改。当数据集的修改完成后,更改可以被再次写入数据库,从而保留我们所做过的更改。数据集中的数据可以由任何数据源(Data Source)提供,比如SQL Server或者Oracle。

数据提供源:

数据提供源用于提供并维护应用程序与数据库之间的连接。

数据提供源是一系列为了提供更有效率的访问而协同工作的组件。如今微软在ADO.NET中提供了两组数据提供源,一组叫做SQL Data Provider(SQL数据提供源),用于提供应用程序与SQL Server 7.0或者更高版本的访问。另一组叫做OleDb DataProvider(Object Linking and Embedding DataBase DataProvider),可以允许我们访问例如Oracle 之类的第三方数据源。

每组数据提供源中都包含了如下四个对象:

Connect对象提供了对数据库的连接。

Command对象可以用来执行命令。

DataReader对象提供了只读的数据记录集。

DataAdapter对象提供了对数据集更新或者修改的操作。

总体来说,使用ADO.NET访问数据可以被概括为以下步骤:

首先应用程序创建一个Connect对象用来建立与数据库之间的连接。然后Command对象提供了执行命令的接口,可以对数据库执行相应的命令。当命令执行后数据库返回了大于零个数据时,DataReader会被返回从而提供对返回的结果集的数据访问。或者,DataAdapter可以被用来填充数据集,然后数据库可以由Command对象或者DataAdapter对象进行相应的更改。

具体来看数据提供源的四种对象

Connect 对象

Connect对象用来提供对数据库的连接,Microsoft Visual Studio .Net中微软提供了两种Connect对象,分别为SqlConnection对象,用来提供对SQL Server 7.0或更高版本的连接,同时还有OleDbConnection对象,用来提供对Access与其他第三方数据库的连接。

Command 对象

同样,Command对象分为两组,SqlCommand与OleDbCommand。Command对象被用来执行针对数据库的命令,比如执行数据库的存储过程(StoredProcedure),SQL命令,或者直接返回一个完整的表。Command对象提供三种方法(Methods)用来执行上述操作。

ExecuteNonQuery用来执行一个不需返回数据的命令,比如表的插入,更新或者删除操作。

ExecuteScalar返回从一个查询得到的单独的值。

ExecuteReader用来返回一个从DataReader来的结果集。

DataReader对象

DataReader提供了forward-only, read-only, connected stream的结果集。

这里的forward-only表示数据只能够向前,如果我们访问了一条数据后想要再次访问这条数据就必须重新开始。

Read-only表示只读,不能够对结果集进行操作。

Connected stream表示DataReader对象是面向流连接的。所谓的流可以把它看作是管道,这边放进去东西那边就可以得到,实际上TCP协议就是面向连接的流协议。

不同于其他的三种对象,DataReader不能够被用户直接创建,必须也只能由ExecuteReader返回。

SqlCommand.ExecuteReader返回SqlDataReader。

同理,OleDbCommand.ExecuteReader返回OleDbDataReader

需要注意的是,DataReader对应用程序提供行级访问(每次只能访问数据的一行),当你需要多行的时候就需要多次的访问这个对象。这样做的好处就是内存中永远只需要保留一行的数据,缺点就是每次访问都要开启Connect的连接。

DataAdapter对象

DataAdapter对象是ADO.NET数据访问的核心。实际上它是数据集与数据库的中间层。DataAdapter可以使用Fill方法来为DataTable或者DataSet填充数据。然后当内存操作完成后DataAdapter可以确认之前的操作从而对真正存于数据库上的数据进行修改。

DataAdapter包含四种属性用来代表不同的数据库命令:

SelectCommand用来查询数据

InsertCommand用来插入数据

DeleteCommand用来删除数据

UpdateCommand用来更新数据当Update方法被调用后,数据集中的数据被更改然后拷贝回数据库,紧接着使用InsertCommand, DeleteCommand, UpdateCommand中合适一个来进行数据更新。

ADO.NET,并提供了一系列方便实用的类。应用这些数据库访问的类,你就能轻松、准确而且是面向对象的操纵数据库中的各种数据了。如图,这就是C#中提供的数据库访问ADO.NET的结构图。

从这张图中,我们能清晰的了解到ADO.NET的数据访问技术的架构。ADO.NET支持SQL Server数据访问和OLE DB数据访问。两者相比,前者是针对SQL Server的数据库访问引擎,所以访问SQL Server数据库效率会高许多,但只支持SQL Server。后者是比较通用的数据库访问引擎,能支持广泛的数据库,但效率不如前者。对研发者来说,如果不用到某种数据库的特性,其大体使用方法是一致的。

 

ADO.NET(c#.net)数据库类介绍

一.用SqlConnection连接SQL Server

1..加入命名空间

using System.Data.SqlClient;

2.连接数据库

SqlConnection myConnection = new SqlConnection();

myConnection.C;

myConnection.Open();

改进(更通用)的方法:

string MySqlC;

SqlConnection myConnection = newSqlConnection(MySqlConnection);

myConnection.Open();

二。用OleDbConnection连接

1.加入命名空间

using System.Data.OleDb;

2.连接sql server

string MySqlC;

SqlConnection myConnection = newSqlConnection(MySqlConnection);

myConnection.Open();

3.连接Access(可通过建立.udl文件获得字符串)

string MySqlCProvider=MSDAORA;DataSource=db; user id=sa;password=sinofindb";

三.创建Command对象

1.SqlCommand 构造函数

①初始化SqlCommand 类的新实例。publicSqlCommand();

SqlCommand myCommand = new SqlCommand();

②初始化具有查询文本的SqlCommand 类的新实例。publicSqlCommand(string);

String mySelectQuery = "Select * FROMmindata";

SqlCommand myCommand = newSqlCommand(mySelectQuery);

③初始化具有查询文本和SqlConnection 的SqlCommand类实例。

Public SqlCommand(string, SqlConnection);

String mySelectQuery = "Select * FROMmindata";

string myC;

SqlConnection myConnection = newSqlConnection(myConnectString);

SqlCommand myCommand = newSqlCommand(mySelectQuery,myConnection);

④初始化具有查询文本、SqlConnection和 Transaction 的 SqlCommand 类实例。

public SqlCommand(string, SqlConnection,SqlTransaction);

SqlTransaction myTrans =myConnection.BeginTransaction();

String mySelectQuery = "Select * FROMmindata";

string myC;

SqlConnection myConnection = newSqlConnection(myConnectString);

SqlCommand myCommand = newSqlCommand(mySelectQuery,myConnection, myTrans);

2.建立SqlCommand与SqlConnection的关联。

myCommand.Connection = myConnection;

或者:SqlCommandmyCommand = myConnection.CreateCommand;

3.设置SqlCommand的查询文本。

myCommand.CommandText = "Select * FROMmindata";

或者第2种构造:SqlCommand myCommand = newSqlCommand(mySelectQuery);

给SqlCommand对象提供两个查询字符串,每个查询字符串访问不同的表,返回不同的结果集。

两个查询语句用分号分隔。

4. 执行命令。

ExecuteReader

返回一行或多行

ExecuteNonQuery

对 Connection 执行 Transact-SQL 语句并返回受影响的行数(int)

ExecuteScalar

返回单个值(如一个聚合值).返回结果集中第一行的第一列。忽略额外的列或行

ExecuteXmlReader

将 CommandText 发送到 Connection 并生成一个 XmlReader 对象。

SqlDataReader myReader =myCommand.ExecuteReader();

或SqlDataReadermyReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);

while(myReader.Read()) //循环读取数据

{

Console.WriteLine(myReader.GetString(0));//获取指定列的字符串形式的值

Console.WriteLine(myReader. GetValue(1));//获取以本机格式表示的指定列的值

}

CommandText = "select count(*) asNumberOfRegions from region";

Int count = (int)myCommand.ExecuteScalar();

关于OleDbCommand对象的使用。

四.DataReader的使用

1.遍历结果集

while (myReader.Read())

Console.WriteLine("\t{0}\t{1}",myReader.GetInt32(0), myReader.GetString(1));

myReader.Close();

2.使用序数索引器。

while (myReader.Read())

Console.WriteLine("\t{0}\t{1}",myReader[0].ToString(), myReader[1].ToString());

myReader.Close();

3.使用列名索引器。

while (myReader.Read())

Console.WriteLine("\t{0}\t{1}",myReader["code].ToString(), myReader["name"].ToString());

myReader.Close();

4.使用类型访问器。

public char GetChar(int i); 获取指定列的单个字符串形式的值

public DateTime GetDateTime(int i); 获取指定列的 DateTime 对象形式的值

public short GetInt16(int i); 获取指定列的 16 位有符号整数形式的[C#]

public string GetString(int i); 获取指定列的字符串形式的值

5.得到列信息。

myReader.FieldCount 获取当前行中的列数

myReader.GetFieldType(序号) 获取是对象的数据类型的 Type

myReader.GetDataTypeName(序号) 获取源数据类型的名称

myReader.GetName(序号) 获取指定列的名称

myReader.GetOrdinal(序号) 在给定列名称的情况下获取列序号

6.得到数据表的信息。

myReader.GetSchemaTable() 返回一个 DataTable

7.操作多个结果集。

myReader.NextResult() 使数据读取器前进到下一个结果集

do

{

while (myReader.Read())

Console.WriteLine("\t{0}\t{1}",myReader.GetInt32(0), myReader.GetString(1));

}

while(myReader.NextResult());

五.DataAdapter

1.创建SqlDataAdapter

初始化SqlDataAdapter 类的新实例。

public SqlDataAdapter();

将指定的SqlCommand 作为SelectCommand属性,初始化SqlDataAdapter 类的新实例。

public SqlDataAdapter(SqlCommand);

用selectcommand字符串 和 SqlConnection对象初始化SqlDataAdapter 类的新实例。

public SqlDataAdapter(string,SqlConnection);

用selectcommand字符串 和一个连接字符串 初始化SqlDataAdapter 类的新实例。

public SqlDataAdapter(string, string);

2.DataAdapter和SqlConnection,SqlCommand建立关联。

1.DataAdapter在构造参数时建立

2.SqlDataAdapter adapter = newSqlDataAdapter();

adapter.SelectCommand = newSqlCommand(query, conn);

3.DataAdapter.Fill()方法。

在 DataSet 中添加或刷新行以匹配使用 DataSet 名称的数据源中的行,并创建一个名为“Table”的 DataTable。

public override int Fill(DataSet);

在 DataSet 中添加或刷新行以匹配使用 DataSet 和 DataTable 名称的数据源中的行。

public int Fill(DataSet, string);

在 DataSet 的指定范围中添加或刷新行以匹配使用 DataSet 和 DataTable 名称的数据源中的行。

public int Fill(DataSet, int, int, string);

在 DataTable 中添加或刷新行以匹配使用 DataTable 名称的数据源中的行。

public int Fill(DataTable);

在 DataTable 中添加或刷新行以匹配使用指定 DataTable 和 IDataReader 名称的数据源中的行。

protected virtual int Fill(DataTable,IDataReader);

在 DataTable 中添加或刷新行以匹配使用 DataTable 名称、指定的 SQL Select 语句和 CommandBehavior 的数据源中的行。

protected virtual int Fill(DataTable,IDbCommand, CommandBehavior);

六.DataTable 类

七.DataColumn 类

八.DataRow 类

九.DataSet 类

1.创建DataSet 对象

初始化 DataSet 类的新实例。

public DataSet();

用给定名称初始化DataSet 类的新实例。

public DataSet(string);

2.用DataAdapter填充DataSet

DataSet myds=new DataSet();

adapter.fill(myds)

adapter.fill(myds,”表名”); 用一个表去填充DataSet.

十.DataTableCollection 类。 表示 DataSet 的表的集合。

DataTableCollection dtc = ds.Tables;

DataTable table = dtc[“表名”];

String strExpr = "id > 5";

String strSort = "name DESC";

DataRow[] foundRows = customerTable.Select(strExpr, strSort,);

进行动态的筛选和排序。

DataTable.Select() 方法 : 获取 DataRow 对象的数组,

①获取所有 DataRow 对象的数组。

public DataRow[] Select();

②按主键顺序(如没有主键,则按照添加顺序),获取与筛选条件相匹配的所有DataRow 对象的数组。

public DataRow[] Select(string);

③获取按照指定的排序顺序且与筛选条件相匹配的所有 DataRow 对象的数组。

public DataRow[] Select(string, string);

④获取与排序顺序中的筛选器以及指定的状态相匹配的所有 DataRow 对象的数组。

public DataRow[] Select(string, string,DataViewRowState);

十一。DataView 类 : 是DataTable内容的动态视图。

1. 创建对象

初始化 DataView 类的新实例。

public DataView();

用指定的 DataTable初始化 DataView 类的新实例。

public DataView(DataTable);

用指定的 DataTable、RowFilter、Sort 和 DataViewRowState 初始化 DataView 类的新实例。

public DataView(DataTable, string, string,DataViewRowState);

DataView myview = newDataView(ds.Tables["Suppliers"], "id > 5", "nameDESC",

DataViewRowState.CurrentRows);

2 .得到DataView的行数据。

foreach (DataRowView myrowview in myview)

{

for (int i = 0; i <myview.Table.Columns.Count; i++)

Console.Write(myrowview + "\t");

Console.WriteLine();

}

 

ADO.NET增删改查代码实例

SqlDataAdapter myDataAdapter = newSqlDataAdapter ();

  DataSetmyDataSet = new DataSet ();

  stringstrCom = "SELECT * FROM myTable";

  myDataAdapter.SelectCommand= new SqlCommand (strCom,myConnection);

  SqlCommandBuildermyCB = new SqlCommandBuilder (myDataAdapter);

  myDataAdapter.Fill(myDataSet,"myTable");

  这时我们已经得到了一个填充好的myDataSet,其中有一个DataTable对象叫myTable。然后我们通过DataTable对象的NewRow方法构造一个新的DataRow对象,在完成设定并赋值后,由DataTable.Rows(即DataRowCollection)的Add方法来完成添加。

  DataRowmyDataRow; //定义一个DataRow

  DataTablemyDataTable; //定义一个DataTable

  myDataTable= myDataSet.Tables["myTable"]; //引用DataSet中的一个DataTable

  myDataRow =myDataTable.NewRow(); //调用NewRow方法得到一个DataRow

  myDataRow["myName"]= "杨扬"; //将此DataRow中的myName字段置为"杨扬"

  myDataTable.Rows.Add(myDataRow);//将此DataRow添加到myDataTable中

  你用的上述方法添加完成后,记录并没有进入到真正的数据库中,而只是在DataSet中完成了添加。如果想要在数据库中真正添加,还要应用DataAdapter的Update方法将DataSet回写到数据库中。这种方法虽然没有直接操作文件显得快捷,但却是现在技术发展的趋势和必然要求,因为现在的数据库系统几乎都是面向网络环境的,特别是分布式的系统更是对数据库访问技术提出了更高的要求。以往的那种独享操纵数据库的方式是绝对不能适合的。ADO.NET访问数据库的方法就是数据库连接成功后,将数据库中表信息的子集创建一个DataSet,然后建议断开数据库连接。在完成对数据库的一系列操作后,再将DataSet回写到数据库的相应表中。这种方式有许多优点,比如减少网络通讯压力,便于事务处理,提高访问效率等等,但缺点就是必须多增加一步回写操作。不过我相信与众多优点相比,这点也算不上什么。

  接下来,我们来看看修改操作的实现。有了上面添加操作的方法,修改操作我想也就容易多了,即直接对DataTable对象中的某个DataRow对象进行修改。请看下面的代码。

  DataRowmyDataRow; //定义一个DataRow

  DataTablemyDataTable; //定义一个DataTable

  myDataTable= myDataSet.Tables["myTable"]; //引用DataSet中的一个DataTable

  myDataRow =myDataTable.Rows[1]; //得到一个要修改的DataRow

  myDataRow["myName"]= "杨扬"; //将此DataRow中的myName字段置为"杨扬"

  有了上面的基础,我觉得删除操作读者都可以自己无师自通了。先自己想想该如何做,我马上就告诉大家。说对了,就是应用DataTable对象的DataRow子对象的Delete方法。比如删除第1条记录,代码如下:

  DataRowmyDataRow; //定义一个DataRow

  DataTablemyDataTable; //定义一个DataTable

  myDataTable= myDataSet.Tables["myTable"]; //引用DataSet中的一个DataTable

  myDataRow =myDataTable.Rows[1]; //得到一个要删除的DataRow

  myDataRow.Delete();

  DataRow的Delete方法不像Visual Foxpro中的DELETE命令,只做一个删除标记,还要配合PACK才能真正删除记录。DataRow的Delete方法是一次性删除,当然只是删除了DataTable中的某条记录,还要Update的。

  上面总说要应用Update将DataTable回写到数据库的表中,可具体做法是什么呢?下面代码给出具体的方法。

  myDataAdapter.Update(myDataSet,"myTable");

 

数据库连接语句

SQL Server

ConnectionString ="integrated security=SSPI;data source=(local);persist securityinfo=False;initial catalog=[数据库名称]"

ConnectionString ="driver=SQL Server;DATABASE=[数据库名称];UID=sa;PWD=;SERVER=[服务器名称]"

Access

ConnectionString ="Provider=Microsoft.Jet.OLEDB.4.0 ;Data Source=[路径+*.mdb];UserID=[ ];Password=[ ]";

ConnectionString = "Provider=Microsoft.Jet.OleDb.4.0;DataSource="+Server.MapPath("*.mdb")

PostgreSQL

ConnectionString= "Server= [服务器IP];Port= [端口号] ;User Id= [用户名] ;Password= [密码];Database= [数据库名称] ;Encoding=UNICODE;"

 

Ole DB
ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=通讯录.accdb"; //指定ADO.NET连接的ConnectionString

C# 数据库访问通用类 (ADO.NET)

http://www.oschina.net/code/snippet_12_1317

 

ADO.NET事务处理
在ADO.NET 中,可以使用Connection 和Transaction 对象来控制事务。若要执行事务,请执行下列操作:
• 调用Connection 对象的BeginTransaction 方法来标记事务的开始。
• 将Transaction 对象分配给要执行的Command的Transaction 属性。
• 执行所需的命令。
• 调用Transaction 对象的Commit 方法来完成事务,或调用Rollback 方法来取消事务。
当然ADO.NET事务处理有优点和缺点,运用起来看具体情况了。
• 优点:
– 简单性
– 和数据据事务差不多的快
– 独立于数据库,不同数据库的专有代码被隐藏了
• 缺点:
– 事务不能跨越多个数据库连接
– 事务执行在数据库连接层上,所以需要在事务过程中维护一个数据库连接。

下边我们看一个例子。

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using System.Data.SqlClient;

 

namespace WebApplication1

{

   public partial class AdoAction : System.Web.UI.Page

    {

       protected void Page_Load(object sender, EventArgs e)

       {

 

       }

 

       protected void btn_Click(object sender, EventArgs e)

       {

           SqlConnection con = new SqlConnection();

           con.ConnectionString=ConfigurationManager.ConnectionStrings["DSN"].ConnectionString;

           con.Open();

           //启动一个事务。

           SqlTransaction myTran = con.BeginTransaction();

           //为事务创建一个命令,注意我们执行双条命令,第一次执行当然成功。我们再执行一次,失败。

           //第三次我们改其中一个命令,另一个不改,这时候事务会报错,这就是事务机制。

           SqlCommand myCom = new SqlCommand();

           myCom.Connection = con;

           myCom.Transaction = myTran;

           try

           {

                myCom.CommandText ="insert into SqlAction values ('测试2','111')";

                myCom.ExecuteNonQuery();

                myCom.CommandText ="insert into SqlAction values ('测试3','111')";

                myCom.ExecuteNonQuery();

                myTran.Commit();

                Response.Write("成功执行");

 

           }

           catch (Exception Ex)

           {

                myTran.Rollback();

                //创建并且返回异常的错误信息

                Response.Write(Ex.ToString());

                Response.Write("写入数据库失败");

           }

           finally

           {

                con.Close();

           }

 

       }

 

 

    }

}


 

 

事务处理需要一个数据库连接以及一个事务处理对象。在SQL Server和ADO.NET中使用事务处理的难点在于SqlTransaction类。此类名称随所使用的数据库平台的不同而会有一些变化。例如,对于OLEDB数据库来说,事务处理类名为OleDbTransaction。

System.Data.SqlClient namespace包括了SqlTransaction类。此类包括了两个属性:

Connection:指示同事务处理相关联的SqlConnection对象;

IsolationLevel:定义事务处理的IsolationLevel。

属性IsolationLevel是包括如下成员的枚举对象:

Chaos:从高度独立的事务处理中出现的pending changes不能被覆盖;

ReadCommitted:当数据需要被非恶意读取时,采用共享锁定(shared locks),但数据仍然可以在事务处理结束时被更新,这造成了非重复性的数据读取(nonrepeatable reads)或phantom data的产生;

ReadUncommitted:恶意读取数据是可能发生的,这表示没有使用共享锁定(sharedlocks),并且没有实现独占锁定(exclusivelocks);

RepeatableRead:锁定查询中所用到的所有数据,由此避免其他用户对数据进行更新。在phantomrows仍然可用的状态下,这可以避免非重复性的数据读取(nonrepeatable reads);

Serialisable:在DataSet中进行范围锁定,由此防止其他用户在事务处理结束之前更新数据或在数据库中插入行;

IsolationLevel定义锁定记录的级别,但这一概念不在本文论述范围之内。对象SqlTransaction也提供了类似的方法。你可以使用以下方法来进行事务处理:

Commit:提交数据库事务处理;

Rollback:从未决状态(pending state)反转(roll back)事务处理。事务处理一旦被提交后即不能执行此操作;

Save:在事务处理中创建savepoint可以对事务处理的一部分进行反转,并且指定savepoint名称。

以下的C#示例将这些部分综合起来。

这一简单的控制台程序将通过以下步骤将两行插入到Northwind数据库的表格中:

调用Connection对象的BeginTransaction方法以标记事务处理的起始位置。BeginTransaction方法对事务处理返回了一个坐标(reference),此坐标被指定给事务处理所用到的Command对象。

将Transaction对象指定给将要执行的Command的Transaction属性。如果某Command在活动Transaction中的Connection上被执行,并且Transaction对象还没有被指定到Command的Transaction属性,则会产生一个异常。

调用Transaction对象的Commit方法来结束事务处理,或者调用Rollback方法来取消事务处理。

等价的VB.NET代码与之类似。

事务处理结束

尽管这是一个简单的示例,但它还是充分显示了在.NET应用程序中使用事务处理是多么的简单。请记住,事务处理只有在处理一组命令时才是必要的。

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byxdaz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值