Maxon's Blog
ADO.NET 开搞

ADO.NET 开搞

8

【ado.net访问 数据库技术】 Net 6 开发系列(已完结)_哔哩哔哩_bilibili

目前认为应该是和JDBCTemplate类似的东西。

1.Connection连接对象

Connection是数据库连接对象,用于与数据库建立连接。SqlServer连接类是SqlConnectionMysql的连接类是MySqlConnectionOracle数据库连接类是OracleConnection,这些类都实现了IDbConnection接口

1.1 SqlConnection

常用方法

方法名描述
Open()打开数据库连接
Close()关闭数据库连接 其实包含了Disopose()的功能
Dispose()执行与释放或重置非托管资源关联的应用程序定义的任务
BeginTransaction()开始数据库事务
BeginTransaction(IsolationLevel)以指定的隔离级别启动数据库事物
BeginTransaction(IsolationLevel,string)以指定的隔离级别和事务名称启动数据库事物
CreateCommand创建并返回与SqlConnection关联的SqlCommand对象
C#
// 1. nuget安装程序集   引入
using System.Data.SqlClient;

// 2. 创建连接对象 
// 告诉连接对象服务器地址 用户名 密码 数据库
SqlConnection connection = new("server=10.10.11.248;uid=sa;pwd=password;database=LearnADO");

// 3. 打开数据库连接
connection.Open();
Console.WriteLine(connection.State);
Console.WriteLine(connection.Database);
Console.WriteLine(connection.ServerVersion);
Console.WriteLine(connection.ConnectionTimeout);

// 4. 关闭数据库连接
connection.Close();

2.自动释放

using 关键字可以自动释放资源

C#
using System.Data.SqlClient;

SqlConnection connection;
// 1. using 自动释放资源
using (connection = new("server=10.10.11.248;uid=sa;pwd=Ff17301576208;database=LearnADO"))
{
    // 2. 打开数据库连接
    connection.Open();
    Console.WriteLine(connection.State);

    // 3. using  {} 结束的时候会自动释放资源
}
Console.WriteLine(connection.State);


//C# 7.0以后的简写方式
// 1. connection 作用域返回最后一行进行关闭
// 2. 此方法运行结束时关闭
using SqlConnection connection = new SqlConnection("server=10.10.11.248;uid=sa;pwd=Ff17301576208;database=LearnADO");
connection.Open();
Console.WriteLine(connection.State);
Console.WriteLine(connection.State);

using

  • 引用命名空间
  • 释放资源
  1. 为什么以前定义的变量不需要释放资源呢? 答:GC(垃圾回收器)封装了一些算法,这些算法规定了什么时候回收资源。我们自己定义的变量不需要关系它有没有释放掉,因为GC会帮助我们自动回收资源。
  2. 既然已经有了GC为什么还需要手动释放资源呢? 答:GC只能回收托管资源(由 .Net CLR 管理的资源),而SQLConnection属于非托管资源。
  3. 怎么知道一个资源是非托管资源呢? 答:如果它实现了 IDisposable接口,那么就可以使用 using 来手动释放资源。

3.Command 命令对象

构造方法

方法名描述
SqlCommand()初始化SqlCommand类的新实例
SqlCommand(string)使用查询的文本初始化SqlCommand实例
SqlComamnd(string,SqlConnection)使用查询文本和SqlConnection初始化SqlCommand实例
SqlCommand(string,SqlConnection,SqlTransaction)使用查询文本、SQL Connection以及SqlTransaction初始化SqlCommand实例

常用属性

名称描述
CommandText获取或设置要在数据源中执行的Transact-SQL语句、表名或存储过程
CommandTimeout获取或设置在终止尝试执行命令并生成错误之前的等待时间 s为单位
CommandType获取或设置一个值,该值指示解释 CommandText 属性的方式
Connection获取或设置SqlCommand对象使用的SqlConnection
Parameters获取SqlParameterCollection
Transaction获取或设置要在其中执行SqlTransaction的SqlCommand

常用方法

方法名描述
Cancle()尝试取消SqlCommand的执行
CreateParameter()创建SqlParameter对象实例
Dispose()执行与释放或重置非托管资源关联的应用程序定义的任务 (继承自DbCommand)
Dispose(Boolean)释放由DbCommand占用的非托管资源,还可以另外再释放托管资源。
ExecuteNonQuery()对连接执行Transact-SQL语句并返回受影响的行数
ExecuteScalar()执行查询,并返回查询所返回的结果集中第一行的第一列。忽略其他列或行。
ExecuteReader()将CommandText发送到Connection,并生成SqlDataReader
C#

// 一次简单的插入操作
using SqlConnection connection = new SqlConnection("server=10.10.11.248;uid=sa;pwd=password;database=LearnADO");
connection.Open();
// 编写Sql语句
string sql = "insert into users values('Oho','123312')";

//创建Command对象
SqlCommand cmd = new();
//设置连接对象
cmd.Connection = connection;
cmd.CommandText = sql;
cmd.ExecuteNonQuery();//执行增删改


//简写以上代码
using SqlConnection connection = new ("server=10.10.11.248;uid=sa;pwd=Ff17301576208;database=LearnADO");
connection.Open();
using SqlCommand cmd = new("select * from users", connection);
var res=Convert.ToInt32(cmd.ExecuteScalar());//获取首行首列  一般用于获取总条数
Console.WriteLine(res);

4.SqlDataReader

提供一种从SQL Server数据库中读取只进的行流的方式。在读取数据的过程中需要一直与数据库保持连接,适合数据量小的情况,执行效率还是可以的。我们将SqlDataReader读取数据的方式称为连接模式

属性

属性名描述
Connection获取与SqlConnection关联的SqlDataReader
Depth获取一个值, 用于指示当前行的嵌套深度
FieldCount获取当前行中列数
HasRows获取一个值,该值表示SqlDataReader是否包含一行还是多行
IsClosed检索一个布尔值,表示是否已经关闭指定的SqlDataReader实例
Item[Int32]在给定列序号的情况下,获取指定列的以本机格式表示的值
Item[String]在给定列名称情况下,获取指定列以本机格式表示的值
RecordsAffected获取执行Transact-SQL语句所更改、插入或删除的行数
VisibleFieldCount获取SqlDataReader中未隐藏的字段数目

方法

方法名描述
Close()关闭连接
Dispose()释放资源
GetName(Int32)获取指定列名称
GetOriginal(String)获取指定列名的序号
Read()让SqlDataReader前进到下一条记录,返回bool
......
C#
//一次读取数据的尝试
using SqlConnection connection = new ("server=10.10.11.248;uid=sa;pwd=password;database=LearnADO");
connection.Open();
using SqlCommand cmd = new("select * from users", connection);
using var sqlDataReader = cmd.ExecuteReader();

//行流
while (sqlDataReader.Read()) {
    /*var uidIndex = sqlDataReader.GetOrdinal("uid");
    var uidValue = sqlDataReader.GetValue(uidIndex);         //在此情况下 uidValue的类型为Object 被自动装箱 所以需要强制转换
    var nameValue = sqlDataReader.GetValue(1);
    var passwordValue = sqlDataReader["password"];*/         //也可以使用这种方式来获取值  但是也需要强制抓换
    var user = new Users(  
        sqlDataReader.GetInt32(0),
        sqlDataReader.GetString(1),
        sqlDataReader.GetString(2)
    );
    Console.WriteLine($"uid:{user.uid} username:{user.username} password:{user.password}");
}

//先封装一次  
record Users(int uid, string username, string password);

断开模式与连接模式

连接模式 :SqlDataReader 就是连接模式, 需要一直与数据库保持连接。

断开模式:SqlDataAdapter 就是断开模式,一次连接取得数据之后,即可断开

5. SqlDataAdapter 适配器 (常用)

表示用于填充DataSet(表示数据的内存中的数据库,它可以由多个table组成)和更新SQL Server数据库的一组数据命令和一个数据库连接。适配器模式也被称为断开模式。

一次连接取得数据之后,即可断开,在用户非常多的情况下,不会占用太多的连接池资源。 还有一点,就是一次性的从数据库中取得了数据之后,这些数据是存在内存中的,而不会再去操作数据库,所以你对这些数据做任何的操作,都只是修改内存,不会改变数据库中的内容。

缺点:

  • 效率相对于 SqlDataReader低
  • 占内存多

构造方法

方法名描述
SqlDataAdapter()
SqlDataAdapter(SqlCommand)初始化SqlDataAdapter实例,用地指定的SqlCommand作为SelectCommand的属性
SqlDataAdapter(String,SqlConnection)使用SqlDataAdapter和SelectCommand对象初始化SQL Connection类的一个新实例==(用的最多)==
SqlDataAdapter(String,String)使用SqlDataAdapter和一个连接字符串初始化SelectCommand类的一个新实例
C#
//一次简单的尝试
using SqlConnection connection = new ("server=10.10.11.248;uid=sa;pwd=Ff17301576208;database=LearnADO");
// 1. 此处并未 手动打开连接
// 2. 适配器
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter();
// 3. 创建命令对象
sqlDataAdapter.SelectCommand=new SqlCommand("select * from users",connection);

// 4. 读取数据
DataSet dataSet = new DataSet();
// 5. 填充数据到DataSet
sqlDataAdapter.Fill(dataSet);

Console.WriteLine(dataSet.Tables[0].Rows.Count);

DataTable

将DataTable封装成List集合

C#
using SqlConnection connection = new ("server=10.10.11.248;uid=sa;pwd=password;database=LearnADO");
// 1. 此处并未 手动打开连接
// 2. 适配器       数据一旦拿到了就会把连接断开 故名断开模式
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter();
// 3. 创建命令对象
sqlDataAdapter.SelectCommand=new SqlCommand("select * from users",connection);

// 4. 读取数据
//DataSet dataSet = new DataSet();  //由多个DataTable组成
DataTable dataTable = new DataTable();// 只有一张表 所以使用DataTable
// 5. 填充数据到Datatable
sqlDataAdapter.Fill(dataTable);


//在面向对象中 DataTable使用率并不高,因为它不符合面向对象思想
//更多使用的是List<T> 泛型集合
List<User> users = new List<User>();

//DBNull与C#中的Null不一样  DBNull 数据库中为空值会报错
foreach (DataRow row in dataTable.Rows) {
    //使用List封装
    User user = new();
    user.Uid = Convert.ToInt32(row["uid"]);
    user.Username = row["username"].ToString();
    //数字类型一定要记得 非空判断
    if (row["password"] is not DBNull) {
        //非空判断
        user.Password=row["password"].ToString();
    }
}


//输出
//1. List更符合面向对象思想
//2. row 在操作列名的时候 有可能会拼写错误
//3. List 集合操作速度比DataTable快,并且属于类型安全
foreach (User user in users) {
    Console.WriteLine($"id={user.uid}\t username={user.username}\t password={user.password}");
}
//封装
record User(int uid, string username, string password);

6.配置文件

Xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--控制台的配置文件名称一定要叫 App.Config-->
    
    <!--连接字符串-->
    <connectionStrings>
        <!--10.10.11.248 服务器的连接字符串-->
        <add name="248ConnString" connectionString="server=10.10.11.248;uid=sa;pwd=password;database=LearnADO"/>
    </connectionStrings>
        
    <appSettings>
        <add key="databaseIP" value="10.10.11.248"/>
    </appSettings>
</configuration>
C#
string connectionString = ConfigurationManager.ConnectionStrings["248ConnString"].ConnectionString;
var s = ConfigurationManager.AppSettings["databaseIP"].ToString();

7.数据连接池

知道现在编程什么是最宝贵的?数据库连接,它其实是很耗费资源的。这里我们就连接池这个问题我们来一起讨论讨论吧。数据库连接池是为了更有效地利用数据库连接的最重要措施。它对于一个大型的应用系统的性能至关重要,特别是Web应用。ADO.NETData Provider(以下简称Data Provider))会帮我们管理连接池,所以有人说使用连接池就像游儿童池一样轻松。但并不是说有了Data Provider程序员就万事无忧的,不正确地使用连接池可能导致你的应用在池里淹死。

什么是ADO.NET连接池?

ADO.Net连接池是Data Provider提供的一个机制,使得应用程序使用的连接保存在连接池里而避免每次都要完成建立/关闭连接的完整过程。要理解连接池,先要理解程序里SqlConnection.Open()、SqlConnection.Close()和打开/关闭一个"物理连接”的关系。

Data Provider在收到连接请求时建立连接的完整过程是:先连接池里建立新的连接(即"逻辑连接") ,然后建立该"逻辑连接"对应的"物理连接"。建立"逻辑连接"一定伴随着建立"物理连接"。 Data Provider关闭一个连接的完整过程是先关闭"逻辑连接"对应的"物理连接"然后销毁"逻辑连接"。销毁"逻辑连接"一定伴随着关闭"物理连接"。SqlConnection.Open)是向Data Provider请求一个连接, Data Provider不一定需要完成建立连接的完整过程,可能只需要从连接池里取出一个可用的连接就可以;SqlConnection.Close()是请求关闭一个连接, Data Provider不一定需要完成关闭连接的完整过程,可能只需要把连接释放回连接池就可以。

8.参数化查询

解决sql注入问题

  1. 存储过程
  2. 参数化

参数化:将要传递的sql参数不通过拼接的方式进行组装sql语句,而是通过sql变量的形式进行传递

SQL
select * from users where Account=@account and Pwd=@pwd;

SqlParameter对象

表示SqlCommand参数,参数名称不区分大小写。

构造函数描述
SqlParameter()使用参数名称和SqlParameter初始化
SqlParameter(String,Object)使用提供的参数名称和数据类型初始化SqlParameter类的新实例
SqlParameter(String,SqlDbType,Int32)使用参数名称、SqlDbType和大小初始化SqlParamater类的新实例
SqlParameter(String,SqlDbType,Int32,ParameterDirection,Boolean,Byte,Byte,String,DataRowVersion)使用提供的参数名称、参数类型、参数大小、ParameterDirection、参数精度、参数小数位数、源列、要使用的DataRowVersion以及参数值初始化SqlParameter类的新实例
......

一次登陆操作

C#
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;


//读取配置文件
string connectionString = ConfigurationManager.ConnectionStrings["248ConnString"].ConnectionString;
using SqlConnection conn=new(connectionString);

conn.Open();
SqlCommand cmd = new SqlCommand("select count(*) from users where username=@username and password=@password",conn);

SqlParameter parameter1=new SqlParameter();
parameter1.ParameterName="username";// 参数名
parameter1.Value="Aealen"; //参数值
parameter1.Size=20; //参数大小
parameter1.SqlDbType=SqlDbType.VarChar; //参数类型

SqlParameter parameter2=new ();
parameter2.ParameterName="password";
parameter2.Value="1221222";
parameter2.Size=20;
parameter2.SqlDbType=SqlDbType.VarChar;

cmd.Parameters.Add(parameter1);//添加参数到命令对象中
cmd.Parameters.Add(parameter2);

int cmdRes = Convert.ToInt32(cmd.ExecuteScalar());

if (cmdRes > 0) {
    Console.WriteLine("登陆成功");
}
else {
    Console.WriteLine("登陆失败");
}

插入操作

Q:指定参数名与参数值 ,那么参数类型与大小呢?
A:如果类型合适,会自动推断类型和大小

C#
//读取配置文件
string connectionString = ConfigurationManager.ConnectionStrings["248ConnString"].ConnectionString;
using SqlConnection conn=new(connectionString);

conn.Open();
using SqlCommand cmd = new SqlCommand("Insert into users values(@username,@password)",conn);


SqlParameter[] parameters = {
    new SqlParameter("username","AowuBaka"), //指定参数名与参数值  ,那么参数类型与大小呢?  A:如果类型合适,会自动推断类型和大小
    new SqlParameter("password","2212331"),  //如果要使用此重载传入 0  需要使用 Object(0)
};

//创建参数对象
cmd.Parameters.AddRange(parameters);

修改操作

C#
using SqlCommand cmd = new SqlCommand("update users set password=@newPwd where uid=@uid",conn);

SqlParameter[] parameters= {
    new ("newPwd",SqlDbType.VarChar,20),
    new SqlParameter("uid",SqlDbType.Int,4)
};

parameters[0].Value = "newpassword";
parameters[1].Value = 3;

cmd.Parameters.AddRange(parameters);

cmd.ExecuteNonQuery();

查询操作!

C#
string connectionString = ConfigurationManager.ConnectionStrings["248ConnString"].ConnectionString;

using SqlDataAdapter sqlDataAdapter = new("select * from users where username like @username", new SqlConnection(connectionString));

SqlParameter sqlParameter = new("username", SqlDbType.VarChar);
sqlParameter.Value = "%" + "e" + "%";
sqlDataAdapter.SelectCommand.Parameters.Add(sqlParameter);

DataTable dataTable = new DataTable();

sqlDataAdapter.Fill(dataTable);

批量删除

循环单个删除

事务相关

如批量删除, 要么全部成功,要么全部失败。

C#
//读取配置文件
string connectionString = ConfigurationManager.ConnectionStrings["248ConnString"].ConnectionString;

SqlConnection connection=new (connectionString);
connection.Open();
//打开事务    并设置隔离级别
SqlTransaction sqlTransaction = connection.BeginTransaction();
try {

    var sqlCommand = new SqlCommand("delete  from users where uid=@uid", connection, sqlTransaction); //以事务方式执行
    int[] arr = { 3, 7, 8, 9, 10, 11, 12 };
    foreach (int item in arr) {
        sqlCommand.Parameters.Add(new("uid", item));
        sqlCommand.ExecuteNonQuery();
        sqlCommand.Parameters.Clear(); //清除已经执行过的参数。
    }

    sqlTransaction.Commit(); //提交事务
}
catch (Exception e) {
    Console.WriteLine(e);
    sqlTransaction.Rollback();
}

存储过程相关

存储过程

SQL
create proc p_users_insert(
    @username varchar(20),
    @password varchar(20),
    @code int output,
    @msg varchar(20) output
)as 
begin 
    insert into users values(@username,@password);
    set @msg='插入成功';
    set @code=200;
endxxxxxxxxxx18 1alter proc p_users_insert(2    @username varchar(20),3    @password varchar(20),4    @code int output,5    @msg varchar(20) output6)as 7begin 8    insert into users values(@username,@password);9    set @msg='插入成功';10    set @code=200;11endcreate proc p_users_insert12(13    @username varchar(20),14    @password varchar(20)15) as 16begin 17    insert into users values(@username,@password) 18end
C#
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;


//读取配置文件
string connectionString = ConfigurationManager.ConnectionStrings["248ConnString"].ConnectionString;

SqlConnection connection=new (connectionString);
connection.Open();

//填写存储过程名称
SqlCommand cmd = new SqlCommand("p_users_insert", connection);
cmd.CommandType = CommandType.StoredProcedure;//一定要设置 cmd类型  默认为Text

SqlParameter[] parameters = {
    new("username", SqlDbType.VarChar, 20),
    new("password", SqlDbType.VarChar, 20),
    new("code",SqlDbType.Int,4),  //输出参数 int
    new("msg",SqlDbType.VarChar,20) //输出参数 string
};
parameters[0].Value = "ProcOut2";
parameters[1].Value = "ssss";

parameters[2].Direction = ParameterDirection.Output;//设置为输出参数
parameters[3].Direction = ParameterDirection.Output;

cmd.Parameters.AddRange(parameters);
cmd.ExecuteNonQuery();

//一定要在执行完存储过程之后才能获取输出参数的值
Console.WriteLine(parameters[2].Value);
Console.WriteLine(parameters[3].Value);
  • 输出参数一定要在存储过程执行之后 才能获取。!!

9.DbHelper封装

在上述案例中,存在太多的重复代码了,开发过程是绝不能容忍如此多的重复代码。上述代码中,除了sql 与 字段参数有可能不一样以外,其他创建连接等代码都是重复的,我们可以将上述操作分为3类。

  1. 读取
  2. 写入
  3. Count计数

如此,我们可以将上述3类封装为三个方法,将sq语句与字段参数当作方法参数传入,达到重用作用。

总结:emmm,就是封装一个类似JDBCTemplate的工具类!