一、概述
ADO.NET是用于数据访问的一组类和接口,它提供了一种与数据源(如数据库)进行交互的方法。
Ado.Net的五大对象:
-
Connection对象:这是与数据库建立连接的对象。通过它,你可以打开和关闭与数据库的连接。
-
Command对象:这个对象用于执行SQL命令,如查询、插入、更新和删除操作。
-
DataReader对象:用于从数据源中读取数据。它是一个只读的、只进的数据流,用于快速、高效地从数据库中检索数据。
-
DataAdapter对象:这是一个桥梁,它可以在DataSet和数据源之间建立连接。DataAdapter使用Command对象来执行SQL命令,并将结果填充到DataSet中。
-
DataSet对象:这是ADO.NET中的核心对象之一,它表示一个内存中的数据表集合,可以在断开数据库连接的情况下进行数据的操作。
二、Connection连接对象
1、创建连接
sqlserver的连接nuget包为system.data.sqlclient,连接对象为SqlConnection
//创建连接(告诉连接对象,服务器、用户名、密码、数据库)
//SqlConnection conn = new("Data Source=127.0.0.1;Initial Catalog=demo1;User ID=sa;Password=Cheng2714");
SqlConnection conn = new("server=.;uid=sa;pwd=Cheng2714;database=demo1");
2、连接对象属性
SqlConnection conn = new("server=.;uid=sa;pwd=Cheng2714;database=demo1");
conn.Open();
Console.WriteLine(conn.State); //连接状态 //Open
Console.WriteLine(conn.Database);//连接的数据库 //demo1
Console.WriteLine(conn.ServerVersion);//服务器版本 //16.00.1000
Console.WriteLine(conn.ConnectionTimeout);//连接超时时间,默认15秒 //15
conn.close();
3、连接对象常用方法
conn.Dispose();//释放资源,其实调用close的时候资源就会释放,所以一般不用
conn.BeginTransaction();//开启数据库事务
conn.BeginTransaction(isolationLevel);//以制定的隔离级别开启数据库事务
conn.BeginTransaction(isolationLevel, string);//以指定的隔离级别和事务名称开始数据库事务
conn.CreateCommand();//创建并返回与SqlConnection关联的SqlCommand对象
修改超时时间为30s
SqlConnection conn = new("server=.;uid=sa;pwd=Cheng2714;database=demo1;timeout=30");
使用using自动释放资源,不用再close了
SqlConnection conn = null;
using (conn = new("server=.;uid=sa;pwd=Cheng2714;database=demo1;timeout=30"))
{
conn.Open();
Console.WriteLine(conn.State); //连接状态 //Open
Console.WriteLine(conn.Database);//连接的数据库 //demo1
Console.WriteLine(conn.ServerVersion);//服务器版本 //16.00.1000
Console.WriteLine(conn.ConnectionTimeout);//连接超时时间,默认15秒 //15
}
//C#7.0以后的简写方式
using SqlConnection conn = new("server=.;uid=sa;pwd=Cheng2714;database=demo1;timeout=30");
conn.Open();
Console.WriteLine(conn.State); //连接状态 //Open
Console.WriteLine(conn.Database);//连接的数据库 //demo1
Console.WriteLine(conn.ServerVersion);//服务器版本 //16.00.1000
Console.WriteLine(conn.ConnectionTimeout);//连接超时时间,默认15秒 //15
小知识:GC:垃圾回收器,这里面封装了一些算法规定了什么时候去回收资源,我们自己定义的一些变量,不需要关心它有没有释放掉,因为GC会帮我们自动回收资源。
既然有了GC,为什么我们还需要手动回收资源呢?
GC只能回收托管资源(由.Net CLR管理的资源),而SqlConnection是属于非托管资源(非托管资源一般都会实现IDisposable接口)。
三、Command命令对象
1、概述
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "insert into teacher values('3','邱领','男','1995-02-07','中级教师','1')";
SqlCommand cmd = new SqlCommand(sql, conn);
//SqlCommand cmd = new SqlCommand();
//cmd.CommandText = sql;//执行的SQL语句
//cmd.Connection = conn;//指定连接的数据库
cmd.ExecuteNonQuery();//非查询执行,也就是增删改的操作
四、DataReader
1、概述
SqlDataReader在读取数据的过程中需要一直与数据库保持连接,适合数据量小的情况。将SqlDataReader读取数据的方式称为连接模式。一般和SQLCommand连用。和SQLDataAdapter比较的话,SqlDataReader相对用的较少
2、属性
FieldCount 获取当前行中的列数
HasRows 获取一个bool值,指示是否包含一行或多行
3、使用例子
3.1、ExecuteScalar() 获取首行首列的数据
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "select * from teacher";
SqlCommand cmd = new SqlCommand(sql, conn);
//ExecuteScalar() 获取首行首列的数据
var a = cmd.ExecuteScalar();//获取首行首列的数据
Console.WriteLine(a);//结果是1
3.2、读取数据
操作1,以下标方式读取,不推荐
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "select * from teacher";
SqlCommand cmd = new SqlCommand(sql, conn);
//读取数据的操作
SqlDataReader dataReader = cmd.ExecuteReader();
while (dataReader.Read())
{
var a = dataReader.GetValue(0);//获取tno的数据,因为tno下标是0
var b = dataReader.GetValue(1);//获取tname的数据,因为tname下标是1
Console.WriteLine($"id={a},name={b}");
}
操作2,以下标方式读取,结合实例对象
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "select * from teacher";
SqlCommand cmd = new SqlCommand(sql, conn);
//读取数据的推荐操作,以下标方式读取,结合了面向对象
SqlDataReader dataReader = cmd.ExecuteReader();
while (dataReader.Read())
{
//Console.WriteLine($"id={a},name={b}");
Teacher teacher = new Teacher(dataReader.GetString(0), dataReader.GetString(1));
Console.WriteLine(teacher.Tno + ", " + teacher.Tname);
}
record Teacher(string Tno, string Tname);
操作3,以字段名方式读取,结合实例对象
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "select * from teacher";
SqlCommand cmd = new SqlCommand(sql, conn);
//读取数据的推荐操作,以字段名方式读取,结合了面向对象
SqlDataReader dataReader = cmd.ExecuteReader();
while (dataReader.Read())
{
//Console.WriteLine($"id={a},name={b}");
Teacher teacher = new Teacher(dataReader["tno"].ToString(), dataReader["tname"].ToString());
Console.WriteLine(teacher.Tno + ", " + teacher.Tname);
}
record Teacher(string Tno, string Tname);
五、DataAdapter
1、概述
表示用于填充DataSet(表示数据的内存中的数据库,由多个table组成,相当于一个小型的数据库,只不过是放在内存中的)和更新SQLserver数据库的一组数据命令和一个数据库连接。
适配器模式也称为断开模式。即一次连接取得数据之后,即可断开,在用户非常多的情况下,不会占用太多的连接池资源。还有一点,就是一次性从数据库中获得数据之后,这些数据是存在内存中的,而不会再去操作数据库,所以对这些数据做任何操作,都只是先修改内存,然后一次性修改数据库。
2、DataReader和DataAdapter对比
DataReader
(例如 SqlDataReader
)
- 用途:用于从数据源(如 SQL Server 数据库)中快速、只读地检索数据流。
- 性能:
DataReader
是一个轻量级的、前向只读的数据流,因此它在读取大量数据时非常高效。由于它不加载整个数据集到内存中,因此它使用较少的系统资源。 - 功能:提供了逐行读取数据的方法,如
Read()
、GetString()
、GetInt32()
等。你不能使用DataReader
来更新或修改数据。 - 用法:通常与
Command
对象一起使用,通过执行 SQL 查询语句来检索数据。 - 适用场景:当需要快速遍历大量数据时,或者当内存资源有限时,使用
DataReader
是合适的。
DataAdapter
- 用途:
DataAdapter
是一个桥梁,它提供了在 DataSet 和数据源(如 SQL Server 数据库)之间建立连接的功能。它用于从数据源检索数据并填充到DataSet
中,也可以将DataSet
中的更改更新回数据源。 - 性能:
DataAdapter
使用DataReader
来从数据源中检索数据,但它在将数据加载到DataSet
时会增加一些开销。由于DataSet
是一个在内存中缓存数据的断开式数据集,因此它允许在断开连接的情况下对数据进行操作。 - 功能:
DataAdapter
提供了填充DataSet
和更新数据源的方法,如Fill()
、Update()
等。它还支持批处理更新和事务处理。 - 用法:通常与
Command
对象和DataSet
对象一起使用。你可以使用DataAdapter
来执行 SQL 查询语句并将结果填充到DataSet
中,然后可以在断开连接的情况下对DataSet
进行操作。 - 适用场景:当需要在断开连接的情况下操作数据时,或者当需要缓存数据时,使用
DataAdapter
和DataSet
是合适的。例如,在 Windows 窗体应用程序中,你可能希望在用户界面中显示和编辑数据,然后在用户完成更改后一次性更新到数据库中。
总结
- 如果你只需要快速读取数据并且不需要在断开连接的情况下对其进行操作,那么使用
DataReader
是合适的。 - 如果你需要在断开连接的情况下操作数据,并且希望缓存数据以供后续使用,那么使用
DataAdapter
和DataSet
是合适的。
3、DataAdapter使用
3.1、使用案例1
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "select * from teacher";
//SqlDataAdapter adapter = new SqlDataAdapter();//创建适配器对象
//adapter.SelectCommand = new SqlCommand(sql, conn);
SqlDataAdapter adapter = new SqlDataAdapter(new SqlCommand(sql, conn));//s上面两行可以简写成这行
DataSet dataSet = new DataSet();//创建DataSet对象 //由于adapter只有一个表,所以也可以用DataTable来装,但这里使用DataSet也行
adapter.Fill(dataSet);//将adapter拿到的数据库充满到dataSet中
Console.WriteLine(dataSet.Tables[0].Rows.Count);//输出3
使用案例2
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "select * from teacher";
//SqlDataAdapter adapter = new SqlDataAdapter();//创建适配器对象
//adapter.SelectCommand = new SqlCommand(sql, conn);
SqlDataAdapter adapter = new SqlDataAdapter(new SqlCommand(sql, conn));//s上面两行可以简写成这行
DataTable dataTable = new DataTable();//创建DataSet对象 //由于adapter只有一个表,所以也可以用datatable来装,但这里使用DataSet也行
adapter.Fill(dataTable);//将adapter拿到的数据库充满到dataSet中
Console.WriteLine(dataTable.Rows.Count);//输出3
foreach (DataRow row in dataTable.Rows)
{
Console.WriteLine($"tno = {row["tno"]}, tname = {row["tname"]}");
}
使用案例2,转化成list,面向对象(推荐)
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "select * from teacher";
//SqlDataAdapter adapter = new SqlDataAdapter();//创建适配器对象
//adapter.SelectCommand = new SqlCommand(sql, conn);
SqlDataAdapter adapter = new SqlDataAdapter(new SqlCommand(sql, conn));//s上面两行可以简写成这行
DataTable dataTable = new DataTable();//创建DataSet对象 //由于adapter只有一个表,所以也可以用datatable来装,但这里使用DataSet也行
adapter.Fill(dataTable);//将adapter拿到的数据库充满到dataSet中
List<Teacher> teachers = new List<Teacher>();
for (int i = 0; i < dataTable.Rows.Count; i++)
{
Teacher teacher = new Teacher(dataTable.Rows[i]["tno"].ToString(), dataTable.Rows[i]["tname"].ToString());
teachers.Add(teacher);
}
foreach (var item in teachers)
{
Console.WriteLine(item.Tno+","+item.Tname);
}
record Teacher(string Tno, string Tname);
六、数据连接池
1、概述
ADO.NET的数据连接池是一种用于提高数据库访问性能的技术,它通过在应用程序启动时创建一组初始化的数据库连接,并将这些连接保存在连接池中,以便在需要时能够迅速获取并重用这些连接。
打个比方,以上的连接数据库的操作相当于,我今天需要人帮忙干活,干完了,就把他们辞了赔了N+1,明天我如果再需要人干活,就需要再招人,干完之后又把他们辞了,又赔了N+1。这样效率不高又费钱。
改善:所以现在有个连接池,我需要人干活,就从池子里摇人来干活,干完之后,还回池子里,下次干活,再从池子里摇人。这就是连接池。
2、使用
数据连接池默认是打开的,如果需要关闭,则
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;pooling=false";
七、参数化查询
1、概述
这里讲下什么是Sql注入?
本来sql是这样的
select * from teacher where tname = '王伟' and tsex = '男'
但用户在输入时输入的是这样
select * from teacher where tname = '王伟' and tsex = '男' or 1='1'
这样无论tname和tsex输入的是否正确,都能查出所有结果
如何解决Sql注入的问题?使用参数化或存储过程。
这里只说参数化,所谓参数化,即所要传递的sql参数不通过拼接的形式,而通过变量的形式进行传递,如下
select * from teacher where tname = @name and tsex = @sex
在使用参数化查询的情况下,数据库服务器不会将参数的内容视为SQL指令的一部分来处理。相反,数据库会先对SQL语句进行编译,然后在执行时再将参数的值传递给SQL语句。这意味着,即使参数中包含了恶意的SQL代码,由于它们已经被当作数据而非代码处理,因此不会被数据库执行。
2、ParaMeters参数
案例1:对于参数化查询,可以使用SqlCommand
的Parameters
集合来添加参数。
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;pooling=false";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "select * from teacher where tno = @no and tname = @name";
SqlCommand command = new SqlCommand(sql, conn);
command.Parameters.AddWithValue("@no", "2");
command.Parameters.AddWithValue("@name", "王伟");
SqlDataReader dataReader = command.ExecuteReader();
while (dataReader.Read())
{
Console.WriteLine(dataReader["tno"]+","+ dataReader["tname"]+","+ dataReader["tsex"]);
}
案例2:也可以用下面这个,但有点笨
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;pooling=false";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "select * from teacher where tno = @no and tname = @name";
SqlCommand command = new SqlCommand(sql, conn);
//command.Parameters.AddWithValue("@no", "2");
//command.Parameters.AddWithValue("@name", "王伟");
SqlParameter sqlParameter1 = new SqlParameter();
sqlParameter1.ParameterName = "@no";
sqlParameter1.Size = 100;
sqlParameter1.SqlDbType = SqlDbType.VarChar;
sqlParameter1.Value = "2";
SqlParameter sqlParameter2 = new SqlParameter();
sqlParameter2.ParameterName = "@name";
sqlParameter2.Size = 100;
sqlParameter2.SqlDbType = SqlDbType.VarChar;
sqlParameter2.Value = "王伟";
command.Parameters.Add(sqlParameter1);
command.Parameters.Add(sqlParameter2);
SqlDataReader dataReader = command.ExecuteReader();
while (dataReader.Read())
{
Console.WriteLine(dataReader["tno"]+","+ dataReader["tname"]+","+ dataReader["tsex"]);
}
案例3:这个也可以,也是笨笨的,都不如第一个简单
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;pooling=false";
using SqlConnection conn = new(connStr);
conn.Open();
string sql = "select * from teacher where tno = @no and tname = @name";
SqlCommand command = new SqlCommand(sql, conn);
SqlParameter[] sqlParameters =
{
new SqlParameter("@no","2"),
new SqlParameter("@name","王伟")
};
command.Parameters.AddRange(sqlParameters);
SqlDataReader dataReader = command.ExecuteReader();
while (dataReader.Read())
{
Console.WriteLine(dataReader["tno"]+","+ dataReader["tname"]+","+ dataReader["tsex"]);
}
案例4:事务案例
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;pooling=false";
using SqlConnection conn = new(connStr);
conn.Open();
var sqltransaction = conn.BeginTransaction();
string sql = "insert into teacher(tno,tname,trank, dno) values(@tno,@tname,'助教','1') ";
SqlCommand cmd = new SqlCommand(sql, conn, sqltransaction);
cmd.Parameters.AddWithValue("@tno", "6");
cmd.Parameters.AddWithValue("@tname", "张三");
cmd.ExecuteNonQuery();
sqltransaction.Commit();
Console.WriteLine("成功");
案例5:存储过程案例
首先要先在数据库写一个存储过程
create procedure proce1(
@tno char(6),
@tname varchar(20)
)
as
begin
delete from teacher where tno = @tno and tname = @tname
end
然后
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;pooling=false";
using SqlConnection conn = new(connStr);
conn.Open();
SqlCommand cmd = new SqlCommand("proce1", conn);//proce1,存储过程名字
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@tno", "6");
cmd.Parameters.AddWithValue("@tname", "张三");
cmd.ExecuteNonQuery();
Console.WriteLine("成功");
案例6:带输出参数的存储过程
首先,创建一个存储过程
create procedure proce2(
@tno char(6),
@tname varchar(20),
@flag varchar(10) output
)
as
begin
delete from teacher where tno = @tno and tname = @tname;
set @flag = '结束了';
end
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True;pooling=false";
using SqlConnection conn = new(connStr);
conn.Open();
SqlCommand cmd = new SqlCommand("proce2", conn);//proce1,存储过程名字
cmd.CommandType = CommandType.StoredProcedure;
//new 参数
SqlParameter sqlParameter1 = new SqlParameter("@tno", SqlDbType.Char);
sqlParameter1.Value = "5";
cmd.Parameters.Add(sqlParameter1);
SqlParameter sqlParameter2 = new SqlParameter("@tname", SqlDbType.VarChar, 20);//需要设置动态大小
sqlParameter2.Value = "kawa2";
cmd.Parameters.Add (sqlParameter2);
SqlParameter sqlParameter3 = new SqlParameter("@flag", SqlDbType.VarChar, 20);
sqlParameter3.Direction = ParameterDirection.Output;
cmd.Parameters.Add(sqlParameter3);
cmd.ExecuteNonQuery();
Console.WriteLine("成功");
Console.WriteLine(sqlParameter3.Value);
练习7事务:该事务包含两个操作:插入一条新记录和更新一条已存在的记录。如果其中任何一个操作失败,整个事务应该回滚。
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True";
using SqlConnection conn = new SqlConnection(connStr);
conn.Open();
SqlTransaction trans = conn.BeginTransaction();
SqlCommand cmd;
try
{
cmd = new SqlCommand("insert into teacher(tno,tname) values('8','kawa5')",conn, trans);
cmd.ExecuteNonQuery();
cmd = new SqlCommand("update teacher set tname = 'kawa2' where tno = '4'", conn, trans);
cmd.ExecuteNonQuery();
trans.Commit();
}
catch(Exception e)
{
trans.Rollback();
Console.WriteLine(e);
}
练习8存储过程:
存储过程,它接受一个员工ID作为参数,并返回该员工的详细信息。编写一个C#方法,该方法调用这个存储过程,并将结果输出到控制台
string connStr = "server=KSLZ28OF4793\\MSSQLSERVER1;database=stuManage;Integrated Security=True";
using SqlConnection conn = new SqlConnection(connStr);
conn.Open();
SqlCommand cmd = new SqlCommand("pro2",conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@tno", '1');
SqlParameter sqlParameter = new SqlParameter("@tname",SqlDbType.VarChar, 20);
sqlParameter.Direction = ParameterDirection.Output;
cmd.Parameters.Add(sqlParameter);
cmd.ExecuteNonQuery();
Console.WriteLine(sqlParameter.Value);