ADO.NET

  • ADO.NET是.net操作数据库的一个模块的总称
  • ADO,NET拥有自己的接口,基本.net体系结构
  • 我们通过他来操作数据库,对数据库进行增删改查的操作
  • 还可以执行sql调用SQL里面的函数,执行存储过程等等
  • 我们很多大型的业务系统都是离不开数据库,我们需要把一些数据固化到本地,并且想要方便查找的话,那么就需要数据库了
  • ADO主要就是提供一些对象,这些对象就是封装了一些操作数据库的一些方法,比如Connection,Command,DataSet,DataReader,他们每个都有自己的功能。
    Connection是用来对数据库进行连接,Command主要是用来执行SQL的命令等,DataSet他其实是一个数据的集合,它是对数据库进行一个抽象,我们操作这个DataSet就好像是操作数据一样,DataSet里面就包含DataTable(表) DataRow对应表中的一条记录,DataAdapter(适配器),有了ADO.NET基础后,我们才能开发一些更强大的业务系统,包括我们的网站都是离不开数据库的

    DataSet:非连接核心组件,独立于任何数据源的数据访问
    DataProvider(数据提供程序):用于连接数据库,执行命令,检索结果
    SQLServer数据提供程序: System.Data.SqlClient命名空间
    OLEDB数据提供程序:System.Data.oledb命名空间
    ODBC数据提供程序:Systen.Data.odbc命名空间
    Oracle数据提供程序:System.Data.OraceClient命名空间
    Connection:提供与数据源的连接 SqlConnection
    Command:执行数据库命令的对象 SqlCommand
    DataReader:从数据源中快速的,只读的数据流 SqlDataReader
    DataAdapter:提供DataSet对象与数据源的桥梁,它可以从数据源中执行我们的T-SQL命令,将数据填充到DataSet中去,它的基本操作也是基于Command对象的,比如说Fill方法,就是把数据填充到DataSet当中去,比如说UPDATE方法,可以更改DataSet中的数据等等
  • ORM框架:EF,Mybatis等 linq to entity等
数据库连接Connections ADO.NET
文章图片
ADO.NET.png
  • ADO.NET访问数据的步骤:
    1.连接数据库
    2.打开连接
    3.创建执行命令对象(创建命令)
    4.执行命令
    5.关闭连接
SqlConnection DbConnection 抽象基类,不能被实例化
  • 抽象基类:抽象类(abstract base class,ABC)就是类里定义了纯虚成员函数的类。纯虚函数一般只提供了接口,并不会做具体实现(虽然可以),实现由它的派生类去重写。抽象类不能被实例化(不能创建对象),通常是作为基类供子类继承,子类中重写虚函数,实现具体的接口。简言之,ABC描述的是至少使用一个纯虚函数的接口,从ABC派生出的类将根据派生类的具体特征,使用常规虚函数来实现这种接口。
  • 为什么使用连接池?
    非常耗时耗力的一件事。需要经历几个阶段,建立物理通过,与服务器初次握手,分析连接字符串,身份验证,运行检查等等。。。
    相当的漫长复杂。改进:是否重复利用已经有的连接?这就是使用连接池的原因。
  • 连接池是什么?
    是一个存放了一定数量的与数据库服务器的物理连接的容器
    在需要用的时候,在容器里面取出一条空闲的连接,而不是创建一条新的连接。
  • 作用:减少了连接数据库的开销,从而提高应用程序的性能
  • 分类:类别区分,同一时刻统一应用程序域可以有不同类型的连接池。
    什么来标识区分的呢:-------- 进程、应用程序域、连接字符串、window标识 共同组成的签名来标识区分,
    对同一程序域来说,由“连接字符串”来区分。打开一条连接,如果这条连接类型签名与现有的连接不匹配,就会创建一个新的连接池,反之,则不会和创建,他们会共用同一个连接池。
  • 如何分配连接
    根据连接请求的类型,找到与它相匹配的连接池,尽力的分配一条空闲的连接。
    有空闲的连接时,返回这条连接
    如果已经用完,就会创建一个新的连接添加到连接池中,如果已达到最大连接数,等待,直达有空闲的连接可用
  • 移除无效连接
    不能正确的连接上数据库的连接。而连接池存储的与数据库的连接数量是有限的
    无效连接应当移除。不移除会浪费连接池空间,连接池管理器会处理无效连接的移除问题
  • 回收连接
    使用完的连接是如何释放的?应当立即关闭或释放,conn对象的Close方法或Diospose方法,连接回到连接池
  • 测试连接池的存在
Ado.Net默认是启用连接池的
连接字符串是可以控制连接池的行为
1.三个属性
Max Pool Size:最大连接数 100
Min Pool Size: 最小连接数 0
Pooling :是否启用连接池 true false
  • 启用连接池与不启用连接池性能差很多

    ADO.NET
    文章图片
    image.png
    循环5000次连接,不启用连接池韩式10000多ms,启用连接池耗时30ms!这就是差距
  • SqlCommand介绍
    是一个执行数据库命令的对象
  • 重要属性
    - Connection:SqlCommand对象使用的SqlConnection - CommandText:获取或设置要执行的T-SQL语句或存储过程 - CommandType:CommandType.Text--执行的是一个Sql语句 CommandType.StoredProcedure --执行的是一个存储过程 - Parameters:SqlCommand对象的命令参数集合 空集合 - Transaction:获取或设置要在其中执行的事务

  • Command对象的创建
ADO.NET
文章图片
image.png
  • Command执行方法
    cmd.ExecuteNonQuery(); cmd.ExecuteScalar(); cmd.ExecuteReader();

  • ExecuteNonQuery方法介绍
    执行T-SQL语句或存储过程,并返回受影响的行数 执行的命令类型:插入、更新、删除操作----DML数据操作语言

  • ExcuteScalar方法介绍 查询------DQL 数据查询语言 ,返回的是一个值 object
    //执行的是查询语句或者存储过程,返回的是查询结果集中的第一行第一列的值,忽略其他行或列 //适用:作查询,返回一个值,记录数 数据运算而出的结果 //命令类型:查询------DQL 数据查询语言 //共有的条件:conn状态 必须是open //连接使用原则:最晚打开,最早关闭 //统计有多少条记录 SELECT COUNT(0) FROM dbo.Info; 无论count的值是多少,0还是1,返回的都是数据量一共有多少条数据 insert into (DeptInfos) values (' 采购部');select @@identity;

  • ExcuteReader() 查询 返回的是一个对象:SqlDataReader
    返回的是数据阅读器,返回的是一种数据流的方式,适用于数据量比较少的情况下,他读取的速度比较快,比较高效,而且耗用内存小 SqlDataReader是一个数据流,是一种实时读取的方式。理解成数据库中的游标,就像指针一样。比如说就读第一条,指针就读到第一条,把第一条数据以流的形式发送到调用端,然后读取出来,读取完过后,指针会前进到下一条数据。读取方式比较固定,不灵活。是一种只能往下继续读取,不能往后读取。 //适用:只是读取数据,不做修改的情况下,适用于数据量比较小的情况下,因为是会一直占着连接的

  • SqlParamter
    • 定义:表示SqlComand对象的参数,或与DataSet中列的映射
    • 常用属性:
      DbType参数的SqlType(数据类型 数据库的类型而言)
      Direction 参数的类型:输入参数、输出参数、输入输出参数、返回值参数
      ParamterName 参数的名称
      Size 最大大小
      Value 参数的值
      SqlValue 作为SQL类型的值的参数的值
    • 参数的构造方法
      { //参数的构造方法 //1.参数 SqlParameter para1 = new SqlParameter(); para1.ParameterName = "@Name"; //参数名 para1.SqlDbType = SqlDbType.VarChar; //数据类型 para1.Value = "https://www.it610.com/article/admin"; //参数值 para1.Size = 20; //大小 //2.参数名,值 SqlParameter para2 = new SqlParameter("@Sex", "男"); //3.参数名,SqlDbType SqlParameter para3 = new SqlParameter("@DeptId", SqlDbType.Int); para3.Size = 4; para3.Value = https://www.it610.com/article/3; //4.参数名 类型 大小 SqlParameter para4 = new SqlParameter("@Pwd", SqlDbType.VarChar, 50); para4.Value = "https://www.it610.com/article/123456"; //5.参数名 类型 大小 源列名(对应DataTable中的列名) SqlParameter para5 = new SqlParameter("@UserName", SqlDbType.VarChar, 20, "Name"); }

  • 参数的作用
    不带任何条件,不是通过参数传递,是拼接的sql语句,生成sql语句,具体的值 拼接sql:容易被sql注入----钻空子 //--把sql语句给注释掉了,所以就会查询出此表中所有的数据,后果很严重 string sql = "select Name,Sex from Info where 1=1 -- Id="+userId+""; 单引号转义的问题:比如说 linqin 和 lin‘qin‘linqin’不会报错 ‘lin‘qin’就会报错static void Main(string[] args) { string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; using (SqlConnection conn = new SqlConnection(connStr)) { //int userId = int.Parse(Console.ReadLine()); //--把sql语句给注释掉了,所以就会查询出此表中所有的数据,后果很严重 //string sql = "select Name,Sex from Info where 1=1 -- Id="+userId+""; //SqlCommand cmd = new SqlCommand("", conn); //参数:不仅仅可以解决SQL注入,还可以解决转义的问题 string sql = "select Sex from Info where Name = @userName"; SqlCommand cmd = new SqlCommand(sql, conn); ////参数化sql语句中使用的是输入参数 ////cmd.Parameters.Add(new SqlParameter("@userName","lin'qin")); SqlParameter spm = new SqlParameter("@userName", "lin'qin"); cmd.Parameters.Add(spm); //添加单个参数 //cmd.Parameters.Add("@userName", "lin'qin"); //直接这种方式没有错,但是已经过时了,弃用了 //cmd.Parameters.AddWithValue("@userName", "lin'qin"); //单个参数的添加,推荐用这种方法 //SqlParameter pra = cmd.Parameters.Add("@userName",SqlDbType.VarChar,20); //pra.Value = "https://www.it610.com/article/lin'qin"; //添加多个参数时使用数组 //SqlParameter[] paras = //{ //new SqlParameter("userName", "lin'qin") //}; //cmd.Parameters.AddRange(paras); conn.Open(); Object o = cmd.ExecuteScalar(); conn.Close(); Console.WriteLine(o.ToString()); } Console.ReadKey(); }

  • 参数使用的几种方法
    cmd.Parameter.Add();
    添加单个参数
    cmd.Parameters.Add("@userName", "lin'qin"); //直接这种方式没有错,但是已经过时了,弃用了
    cmd.Parameter.AddWithValue();
    cmd.Parameters.AddWithValue("@userName", "lin'qin"); //单个参数的添加,推荐用这种方法
    cmd.Parameter.AddRange();
    //添加多个参数时使用数组
    //SqlParameter[] paras =
    // {
    // new SqlParameter("userName", "lin'qin")
    //};
    //cmd.Parameters.AddRange(paras);
  • 参数的使用
//这个Direction就代表着说这个参数是输入参数还是输出参数还是双向参数还是返回值参数 spm.Direction = ParameterDirection.Input; //默认就是input参数。

  • 输入参数
    参数化SQL语句或存储过程
  • 输出参数
    在存储过程中以output标识,程序中是可以接受到存储过程中需要返回的值,而不用return的
    在代码中Direction:Output
string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; using (SqlConnection conn = new SqlConnection(connStr)) {SqlCommand cmd = new SqlCommand("GetName1", conn); //调用存储过程的方法 cmd.CommandType = CommandType.StoredProcedure; //参数化sql语句中使用的是输入参数 SqlParameter paraId = new SqlParameter("@Id",2); cmd.Parameters.Add(paraId); //添加单个参数 //输出参数 SqlParameter paraName = new SqlParameter("@Name",SqlDbType.NVarChar,10); paraName.Direction = ParameterDirection.Output; cmd.Parameters.Add(paraName); //添加单个参数//这里遵循一个原则,最迟打开,最早关闭 //要使用连接时才打开,使用完就关闭 conn.Open(); cmd.ExecuteScalar(); conn.Close(); Console.WriteLine(paraName.Value.ToString()); }Console.ReadKey(); }

  • 输入输出参数
    在存储过程中也是用output标识 他需要传入值,也输出值,不用return
    在代码中Direction:InoutOutput,除了Direction的标识不同外,还有需要插入Value值
string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; //输入输出参数--双向参数 using (SqlConnection conn = new SqlConnection(connStr)) {SqlCommand cmd = new SqlCommand("GetName2", conn); //调用存储过程的方法 cmd.CommandType = CommandType.StoredProcedure; //输入输出参数,既传入值也输出值 SqlParameter paraName = new SqlParameter("@Name",SqlDbType.NVarChar,10); paraName.Value = "https://www.it610.com/article/里"; paraName.Direction = ParameterDirection.InputOutput; cmd.Parameters.Add(paraName); //添加单个参数//这里遵循一个原则,最迟打开,最早关闭 //要使用连接时才打开,使用完就关闭 conn.Open(); cmd.ExecuteScalar(); conn.Close(); Console.WriteLine(paraName.Value.ToString()); }Console.ReadKey();

  • 返回值参数
    • 只可以返回的int类型的,其他的类型会报错,return进行返回
    • 返回字符串类型是不可以的,如果要返回一个字符串类型,存储过程会强制转换成一个int类型,如果转换不了,就会报错
  • SqlDataReader
    • 定义:提供一种从sqlserver数据库读取只进的行流的方式
    • 特点:快速的、轻量级的、只读的、遍历访问每一行数据的数据流、向一个方向,一行一行的读取,不能向后读取,读取功能弱但是读取速度快,不能修改数据
    • 缺点: 不灵活,只适合数据量小的情况,只限于读取数据,一直占着连接
    • 读取方式:Read(); 获取到第一行数据,指针指向第二行的数据。要读下一行的数据时,再次调用Read()方法,获取第二行的数据;当调用Read()方法返回false时,就表示不再有数据行。
    • 注意:连接对象要一直保持open()状态,如果连接关闭,是不能读取数据的。
      使用完成后,应该马上调用Close()方法关闭,不然Reader()对象是会一直占着连接的
    • 创建方式:
      • 是不能直接构造的,cmd.ExcuteReader()来创建。cmd.ExcuteReader(CommandBehavior. CloseConnection)传入的这个值,传入的这个值的DataReader的好处是:关闭Reader对象是就会自动关闭数据库连接
      • 读取时尽量使用与数据库字段类型相匹配的方法来取得对应的值,会减少因类型不一致而增加类型转换操作的性能损耗。
      • 没有读取到末尾时就要关闭reader对象时,先调用cmd.Cancel(),然后再调用reader.Close();
      • cmd.ExcuteReader()来获取存储过程的返回值或者输出参数,先调用reader.Close(),然后才能获取到参数的值
  • 常用属性:
    • Connection:获取与我们Reader对象相关的SqlConnection
    • FiedCount:可以当前行中的列数
    • HasRows:reader对象是否包含一行或者多行
    • IsClosed:reader对象是否已关闭,关闭:true,打开:false
    • Item[int]:列序号,给定列序号的情况下,获取指定列的值
    • Item[String]:列名。获取指定列的值
  • SqlDataReader读取数据
    • 常用方法
      • close()方法 关闭dataReader数据阅读器对象的
      • GetInt32(列序号i)--根据数据类型相匹配的方法
      • GetFiedType(列序号i)--获取数据类型的type对象
      • GetName(列序号i) -- 获取指定列的列名
      • GetOrdinal(列名) -- 获取指定列的列序号
      • Read() -- 使datareader对象前进到下一条记录
      • NextResult() -- (如果dr读取的是多结果集的数据)使dr前进到下一个结果
    • 读取
      • Read():数据量小的情况,高效的
  • DataTable详解
    • 概念:DataSet (数据在内存中的一个缓存,理解成内存中的数据库):是Ado.Net核心组件;DataTable是DataSet的重要成员(DataTable内存中的一个表,可以理解成DataSet数据库中的表):DataSet和DataTable就类似于数据库与数据库中的表一样
    • 应用:一般是作为DataSet的成员使用,它也可以独立创建与使用,结合DataAdapter适配器使用。
    • 架构:DataTable(类似于数据库中的表,他的架构也就是结构),列+约束来表示
      • DataColumn 列对象,定义好后要添加到DataTable里面
    • 成员:行 DataRow
    • 构造:
      • 无参构造:DataTable() DataTable(表名)
    • 常用属性:
      • columns 列集合;Rows 行集合;Contrains 约束的集合;DataSet :DataTable所属的DataSet
        PrimaryKey:主键(列的数组) TableName(指定一个表名)
    • 方法
      • AcceptChanges() 对DataTable所做的更改的提交
      • RejectChanges() 回滚,取消更改
      • Clear() 清空数据
      • Copy() 复制架构和数据
      • Clone() 只复制架构,不包含数据
      • Load(IDataReader) 通过提供的IDataReader,用某个数据源的值来填充DataTable
      • Merge(DataTable)/Merge() 合并,将指定的DataTable合并到当前的DataTable中来 ,前提是是结构相同的情况,结构不同的话需要特别的处理
      • NewRow() 创建一个DataRow,与DataTable具有相同的架构
      • Reset() 将DataTable重置到最初始的状态
      • Select() 获取到DataTable所有的行的数组,返回的是一个数组,参数可以带筛选条件(可以排序)
DataRow[] row = dt1.Select(); //获取所有行 DataRow[] row1 = dt1.Select("Id>1""Id desc"); //条件筛选,排序

  • DataSet详解
    • 概念:DataSet是数据在内存中的缓存,可以理解成内存中的数据库,DataTable内存数据库中的一个表,是Ado.net中的核心组件,数据库中的数据加载到内存中
    • 成员:一组DataTable组成,DataRelation(表与表之间的关系)和DataTable是相互关联的,他们一起实施的数据的完整性
    • 应用 :三种
      • 1.结合DataAdapter使用,DataAdapter将数据填充到我们的DataSet中
      • 2.DataAdapter将DataSet中的更改提交到数据库,是数据库中的数据和我们的更改保持一致
      • 3.将XML文档或文本加载到DataSet中去
    • 作用:DataSet将数据加载到内存中执行的,是内存中的缓存。
      • 1.提高了数据访问的速度
      • 2.提高了硬盘数据的安全性,因为DataTable的数据都是在内存中,没有提交到数据库,就算不正确是错误的也不会影响到数据库
      • 3.因为前两点,也影响了程序运行的速度和稳定性
    • 特性:独立性,不依赖与任何数据库,因为是加载在内存中,所以无论是什么数据源,都可以断开与数据库的连接,可以独立存在进行处理,可以离线和连接。DataSet里面的数据时通过Xml来描述的,所以是可以用xml来表示的数据视图,数据操作灵活性强
    • 创建:DataSet() 无参构造 DataSet(名称) 带参数的构造
    • 常用属性:
    • 方法:
  • 约束与关系
    • 创建约束:
dt1.PrimaryKey = new DataColumn[] { dt1.Columns[0] }; //主键 ---自动创建一个主键约束 dt2.Constraints.Add(new UniqueConstraint("uc", dt2.Columns[1])); //添加一个唯一约束//外键约束,第一个是父列,然后是子列 dt1.Constraints.Add(new ForeignKeyConstraint("fk",dt2.Columns[0], dt1.Columns[3])); //默认情况:建立关系,就自动在父表中建立唯一约束,子表中外键列建立一个外键约束 DataRelation relation = new DataRelation("relation",dt2.Columns[0],dt1.Columns[3],true); //这样就会自动创建上面的约束,最后一个属性如果设置成false,就只会建立联系,而不会创建以上两种约束 //添加到ds中 ds.Relations.Add(relation); //添加到relations中

  • 创建关系:
  • 使用:
//使用关系 foreach (DataRow dr in dt2.Rows)//首先遍历父表中的所有行 { DataRow[] rows = dr.GetChildRows(relation); //通过dr.GetChildRows(relation)来获取子表有多少行 foreach (DataRow r in rows) { Console.WriteLine($"UserId:{r[0].ToString()},UserName:{r[1].ToString()},Age:{r[2].ToString()},DeptId:{r[3].ToString()}"); } }//通过父表读取子表中的数据 DataRow[] rows = dt2.Rows[0].GetChildRows(relation); foreach (DataRow r in rows) { Console.WriteLine($"UserId:{r[0].ToString()},UserName:{r[1].ToString()},Age:{r[2].ToString()},DeptId:{r[3].ToString()}"); }//可以通过子表来读取父表中的数据吗 DataRow rows = dt1.Rows[0].GetParentRow(relation); Console.WriteLine($"DeptId:{rows[0].ToString()},DeptName:{rows[1].ToString()}");

  • SqlDataAdapter
    • SqlDataAdapter是什么
      • 数据适配器(桥接器),DataSet与数据之间用于检索和保存数据的桥梁。
      • SqlDataAdapter类 填充DataSet以及更新数据源(也就是各种不同的数据库)的一组数据库执行命令和一个数据库连接。
      • SqlDataAdapter是我们DataSet和SQLServer之间的桥接器
    • 它是如何提供桥接的
      • Fill(); 这个方法就可以将我们的sql命令检索到的数据填充到DataSet里边
      • Update(); 将更改提交到数据库,让数据库中的数据和DataSet中的数据保持一致
      • 和SqlConnection和SqlCommand一起使用可以提高访问数据的速度
    • 4个重要属性
      • SelectCommand,查询; 这个属性生成一个对象SqlCommand
      • InsertCommand,插入一条记录
      • UpdateCommand,更新数据库中的记录
      • DeleteCommand,删除数据库中的记录
      • 从上面可知,SqlDataAdapter也是建立在SqlCommand基础之上的
    • 4种创建方式
string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; string sql = "select * from UserInfo"; SqlConnection conn = new SqlConnection(connStr); //1.单独设置SelectCommand SqlDataAdapter adapter = new SqlDataAdapter(); adapter.SelectCommand = new SqlCommand(sql,conn); //2.通过我们的SqlCommand对象来实例化一个adapter SqlCommand cmd = new SqlCommand(sql,conn); SqlDataAdapter adapter1 = new SqlDataAdapter(cmd); //3.查询语句和连接对象来实例化一个adapter SqlDataAdapter adapter2 = new SqlDataAdapter(sql,conn); //4.查询语句和连接字符串,也可以构建一个adapter?,很少用 SqlDataAdapter adapter3 = new SqlDataAdapter(sql, connStr); //如果执行的是T-SQL查询语句,不带参数的情况下,选择第三种方式 //带参数,添加参数,SqlCommand,选择第二种或者第一种 //不推荐使用第四种 Console.ReadKey();

  • DataAdapter填充DataSet和DataTable
    • Fill(); 填充到ds或dt中
    • 连接与断开两种方式填充数据的执行机制
      • Fill(); 断开式;没有显示打开conn对象---由DataAdapter来做这件事--获取数据--填充到ds\dt中--自动关闭conn;断开式给我们的错觉是:断开的,没有与数据库连接 ,其实只是这个过程都是由da来完成
      • 连接式,显示打开conn,显示打开和关闭conn时,Fill()前后conn状态都是open
    • 区别:速度上,连接式速度比断开式快(只是在填充数据上,因为打开式不用做打开连接关闭连接的过程)
    • 尽量选择连接式,因为效率高。一次性加载到内存中,加载到内存后就可以断开conn连接
  • Adapter更新数据库
    • 更新方式:Update(ds/dt/Rows); 放数据的是DataRow对象
      - RowState:Added:执行插入; -RowState:Modified:执行修改; -RowState:Deleted:执行删除; 更新时,Modified--已修改的--da.UpdateCommand; Added -- 已添加 ---da.InsertCommand; Deleted -- 已删除 -- da.DeleteCommand;

    • 配置对应的命令属性的方式
      • SqlCommandBuilder:自动为DataAdapter配置相关的命令
      根据行状态自动配置相应的Command命令,通过Adapter对象来进行实例化 SqlCommandBuilder cmdBuilder = new SqlCommandBuilder(da);

      • 手动配置SqlCommand:
      //为da手动配置命令 SqlCommand insertCmd = new SqlCommand("insert into UserInfo (UserName,Age,DeptId) values (@UserName,@Age,@DeptId)", conn); SqlParameter[] paras = { new SqlParameter("UserName",SqlDbType.VarChar,10,"UserName"), new SqlParameter("Age",SqlDbType.Int,4,"Age"), new SqlParameter("DeptId",SqlDbType.Int,4,"DeptId") }; insertCmd.Parameters.Clear(); insertCmd.Parameters.AddRange(paras); da.InsertCommand = insertCmd;

  • Adapter和Reader的比较
    • 相同:都是执行查询,将数据读取出来,一个或多个
      - SqlDataReader :提供一种从数据库中读取数据的流的方式
      - SqlDataAdapter:是数据库与DataSet之间的一个桥接器,通过Fill(); 方法填充到DataSet中,通过Update();方法更新到数据库
    • 性能对比:速度、占用内存、连接状态、适用数据量、读取方式、是否可读等方面进行对比
      速度:DataReader读取速度比较快,DataAdapter读取速度比较慢;(数据量小的情况下,速度快慢不明显)
      数据量:DataReader适用于数据量小;DataAdapter是用于数据量大
      占用内存:DataReader一次性只加载一条数据,一条一条的读取,占用内存小;DataAdapter是一次性加载,占用内存比较大
      连接:DataReader,一直占用连接;DataAdapter一次性读取到内存中(支持断开与连接两种方式)
      读取方式:DataReader,从头读到尾,一条一条的读,读一条丢一条,只能向前不能后退(即时存储),功能弱,不灵活,只读的不可修改; DataAdapter,一次性加载到内存中(将一个或多个),可以任意读取,灵活,可读可写;
  • 不要求随意读取、不修改、数据量小的情况下,选用SqlDataReader --速度快,占用内存小
  • 可以随意读取,可以修改,数据量大的情况下,选择SqlDataAdapter --灵活性,占用内存大,但是不会一直占用连接
  • Ado.Net调用数据库事务
    • 回顾事务知识:一组操作--一个操作来执行,是一致性提交的,否则则回滚回最初的状态。结果:成功or失败,事务的特征:原子性,一致性,独立性,永久性
      • 事务执行过程:
        • 开启事务,成功-提交 ,失败-回滚;
        • 例如:插入操作时,发生故障,并未提交到数据库,可以维护数据的完整性
          //数据库存储过程 -- 事务:一致性提交,回滚 --程序如何来调用事务?事务放在存储过程里 ALTER PROC [dbo].[AddUserByTran] @UserName VARCHAR(10), @Age INT, @DeptName VARCHAR(10) AS BEGIN begin TRAN begin TRY -- 插入部门信息 INSERT INTO DeptInfo(DeptName) VALUES (@DeptName); DECLARE @deptId INT SELECT @deptId = @@IDENTITY; -- 插入用户信息 INSERT INTO dbo.UserInfo ( UserName, Age, DeptId ) VALUES( @UserName,@Age,@deptId)COMMIT TRAN--提交 RETURN 1; end TRY begin CATCH ROLLBACK RETURN 0; END CATCH END //c#代码执行数据库中的存储过程 string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; using (SqlConnection conn = new SqlConnection(connStr)) { //调用存储过程 SqlCommand cmd = new SqlCommand("AddUserByTran", conn); cmd.CommandType = CommandType.StoredProcedure; //调用存储过程必要的 cmd.Parameters.Clear(); SqlParameter[] paras ={ newSqlParameter("@UserName","lily"), newSqlParameter("@Age",26), newSqlParameter("@DeptName","销售部"), newSqlParameter ("@reValue",SqlDbType.Int,4) }; paras[3].Direction = ParameterDirection.ReturnValue; //返回值参数 cmd.Parameters.AddRange(paras); //执行 conn.Open(); cmd.ExecuteNonQuery(); int state = int.Parse(paras[3].Value.ToString()); conn.Close(); if (state == 1) { Console.WriteLine("用户信息添加成功!"); } else { Console.WriteLine("用户信息添加失败"); } }

      • Ado.Net如何调用事务 --实质:调用一个普通的存储过程---里面的逻辑是一段事务代码
        • 批量导入 --- 如果使用数据库事务,要传入一千组参数的情况,太复杂--- 这种方法不具有可行性
    • Ado.Net.SqlTransaction 开启事务---循环--- 操作定义,传入不同的参数值
      • SqlTransaction 介绍:SQLServer数据库中执行T-SQL事务
      • 与数据库事务有何不同?
        string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; using (SqlConnection conn = new SqlConnection(connStr)) { SqlTransaction tran = null; try { //先决条件:连接打开 conn.Open(); //IsolationLevel.ReadCommitted,可以设置隔离级别,ReadCommitted是默认的隔离级别 tran = conn.BeginTransaction(); //开启一个事务 SqlCommand cmd = conn.CreateCommand(); cmd.Transaction = tran; //设置在Command中要执行的事务//定义要执行的操作 cmd.CommandText = "insert into DeptInfo (DeptName) values (@DeptName); select @@identity"; cmd.Parameters.Add(new SqlParameter("@DeptName", "采购部")); Object oId = cmd.ExecuteScalar(); cmd.Parameters.Clear(); int DeptId = 0; if (oId != null) { DeptId = int.Parse(oId.ToString()); } cmd.CommandText = "INSERT INTO dbo.UserInfo(UserName, Age, DeptId) VALUES (@UserName, @Age, @DeptId)"; SqlParameter[] paras = { new SqlParameter ("@UserName","luhan"), new SqlParameter ("@Age",29), new SqlParameter ("@DeptId",DeptId) }; cmd.Parameters.AddRange(paras); cmd.ExecuteNonQuery(); cmd.Parameters.Clear(); //下一步操作 tran.Commit(); //提交事务 Console.WriteLine("用户提交成功"); } catch(SqlException ex) { tran.Rollback(); //出异常回滚 } finally { tran.Dispose(); conn.Close(); } } Console.ReadKey();

      • SqlTransaction 的使用 :开启事务,执行一组操作,更灵活,不管是不是批量,他都可以驾驭
  • 封装的数据访问通用类DbHelp
    • 为什么封装这个类
      • 只要与数据库发生数据交互:增 删 改 查 --- 都要与数据库通信,做一些相同的事 --- 建立连接,T-SQL命令,执行命令的对象Command,选择执行方式,得到相应的结果,关闭连接
      • 如果把整个过程写在一起,整个代码就会显得杂乱无章,不清晰
      • 做一些改善 -- 把一些相同的逻辑写在一起封装起来,达到通用的目的
      • 产生什么作用 -- 代码重利用,提高开发效率,不再考虑交互过程,逻辑很清晰。不管项目架构是否是分层的,都很重要。
    • DbHelp类的内容规划
      • 1.连接钥匙:-- 连接字符串 -- 写在配置文件里 -- 建立连接 -- 连接释放 -- using()语句块(如果忘了释放,using语句块会帮我们进行释放)
      • 2.SqlCommand三种执行方法,ExcuteNonQuery(); 这个方法就是针对增删改的操作;ExcuteScalar(); 这个方法针对查询,返回一个值; ExcuteReader(); 这个方法是执行一个T-SQL命令,生成一个数据流(SqlDataReader()) ,这种方法读取起来比较快速,适用于只是读取数据,不需要进行二次加工的数据。
      • 3.SqlDataAdapter填充DataSet(针对一个或多个结果集)或DataTable(针对单个结果集)
      • 4.SqlTransation(执行事务的方法):每一个操作定义成一个对象CommandInfo,每一个操作执行一个T-SQL命令,传入参数(参数数组),CommandType(执行的参数类型)
    • 具体的实现过程
      • SqlCommand三种执行方法:conn是open状态
        • ExcuteNorQuery(); 执行T-SQL,返回受影响的行数,是一个int类型,执行的可能是T-SQL语句或者存储过程,所以要考虑CommandType
          /// /// 执行T-SQL,返回受影响的行数 Insert,Update,Delete /// /// sql语句或存储过程名 /// 类型或传入一个int cmdType,1代表sql语句,2代表存储过程 /// 参数列表,分两种,传值或不传值的情况 /// public static int ExcuteNonQuery(string sql,int cmdType,params SqlParameter[] paras) { int count = 0; using (SqlConnection conn = new SqlConnection(connStr)) { SqlCommand cmd = new SqlCommand(sql, conn); if (cmdType == 2) cmd.CommandType = CommandType.StoredProcedure; if(paras!=null&& paras.Length>0) cmd.Parameters.AddRange(paras); conn.Open(); count = cmd.ExecuteNonQuery(); conn.Close(); } return count; }

        • ExcuteScalar(); 执行查询,返回查询结果集第一行第一列的值object,忽略其他行或列
          public static object ExcuteScalar(string sql, int cmdType, params SqlParameter[] paras) { object o = null; using (SqlConnection conn = new SqlConnection(connStr)) { SqlCommand cmd = new SqlCommand(sql, conn); if (cmdType == 2) cmd.CommandType = CommandType.StoredProcedure; if (paras != null && paras.Length > 0) cmd.Parameters.AddRange(paras); conn.Open(); o = cmd.ExecuteScalar(); conn.Close(); } return o; }

        • ExcuteReader(); 执行查询,生成SqlDataReader,可以对结果集的数据进行一行一行的即时读取
        /// /// 执行查询,生成SqlDataReader,可以对结果集的数据进行一行一行的即时读取 /// /// /// /// /// public static SqlDataReader ExcuteReader(string sql, int cmdType, params SqlParameter[] paras) { SqlDataReader dr = null; SqlConnection conn = new SqlConnection(connStr); SqlCommand cmd = new SqlCommand(sql, conn); if (cmdType == 2) cmd.CommandType = CommandType.StoredProcedure; if (paras != null && paras.Length > 0) cmd.Parameters.AddRange(paras); try { conn.Open(); //CommandBehavior.CloseConnection,作用是释放Reader(),可以一起释放连接 dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); } catch(SqlException ex) { conn.Close(); //把这里的异常包裹成新的异常往上抛 throw new Exception("执行查询异常", ex); }return dr; }

  • 封装DbHelper
class DbHelper { //连接字符串 private static readonly string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; SqlConnection conn = null; //private SqlConnection GetConn() //{ //conn = new SqlConnection(connStr); //return conn; //} /// /// 执行T-SQL,返回受影响的行数 Insert,Update,Delete /// /// sql语句或存储过程名 /// 类型或传入一个int cmdType,1代表sql语句,2代表存储过程 /// 参数列表,分两种,传值或不传值的情况 /// public static int ExcuteNonQuery(string sql,int cmdType,params SqlParameter[] paras) { int count = 0; using (SqlConnection conn = new SqlConnection(connStr)) { //SqlCommand cmd = new SqlCommand(sql, conn); //if (cmdType == 2) //cmd.CommandType = CommandType.StoredProcedure; //if(paras!=null&& paras.Length>0) //cmd.Parameters.AddRange(paras); //conn.Open(); SqlCommand cmd = BuildCommand(conn, sql, cmdType, null, paras); count = cmd.ExecuteNonQuery(); cmd.Parameters.Clear(); conn.Close(); } return count; }public static object ExcuteScalar(string sql, int cmdType, params SqlParameter[] paras) { object o = null; using (SqlConnection conn = new SqlConnection(connStr)) { //SqlCommand cmd = new SqlCommand(sql, conn); //if (cmdType == 2) //cmd.CommandType = CommandType.StoredProcedure; //if (paras != null && paras.Length > 0) //cmd.Parameters.AddRange(paras); //conn.Open(); SqlCommand cmd = BuildCommand(conn, sql, cmdType, null, paras); o = cmd.ExecuteScalar(); cmd.Parameters.Clear(); conn.Close(); } return o; }/// /// 执行查询,生成SqlDataReader,可以对结果集的数据进行一行一行的即时读取 /// /// /// /// /// public static SqlDataReader ExcuteReader(string sql, int cmdType, params SqlParameter[] paras) { SqlDataReader dr = null; SqlConnection conn = new SqlConnection(connStr); //SqlCommand cmd = new SqlCommand(sql, conn); //if (cmdType == 2) //cmd.CommandType = CommandType.StoredProcedure; //if (paras != null && paras.Length > 0) //cmd.Parameters.AddRange(paras); SqlCommand cmd = BuildCommand(conn, sql, cmdType, null, paras); try { //conn.Open(); //CommandBehavior.CloseConnection,作用是释放Reader(),可以一起释放连接 dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); cmd.Parameters.Clear(); } catch(SqlException ex) { conn.Close(); //把这里的异常包裹成新的异常往上抛 throw new Exception("执行查询异常", ex); }return dr; }/// /// 填充DataSet,对一个或多个结果集都有效 /// /// /// /// /// public static DataSet GetDataSet(string sql, int cmdType, params SqlParameter[] paras) { DataSet ds = new DataSet(); using (SqlConnection conn = new SqlConnection(connStr)) { //SqlDataAdapter da = new SqlDataAdapter(sql, conn); 这是不传参的情况下,如果要传参数,这种情况我们就要抛弃了 //SqlCommand cmd = new SqlCommand(sql,conn); //if (cmdType == 2) //cmd.CommandType = CommandType.StoredProcedure; //if (paras != null && paras.Length > 0) //cmd.Parameters.AddRange(paras); SqlCommand cmd = BuildCommand(conn, sql, cmdType, null, paras); SqlDataAdapter da = new SqlDataAdapter(cmd); //conn.Open(); da.Fill(ds); conn.Close(); } return ds; }/// /// 填充DataTable,一个结果集,只针对单个表 /// /// /// /// /// public static DataTable GetDataTable(string sql, int cmdType, params SqlParameter[] paras) { DataTable dt = new DataTable(); using (SqlConnection conn = new SqlConnection(connStr)) { //SqlDataAdapter da = new SqlDataAdapter(sql, conn); 这是不传参的情况下,如果要传参数,这种情况我们就要抛弃了 //SqlCommand cmd = new SqlCommand(sql, conn); //if (cmdType == 2) //cmd.CommandType = CommandType.StoredProcedure; //if (paras != null && paras.Length > 0) //cmd.Parameters.AddRange(paras); SqlCommand cmd = BuildCommand(conn, sql, cmdType, null, paras); SqlDataAdapter da = new SqlDataAdapter(cmd); //conn.Open(); da.Fill(dt); conn.Close(); } return dt; }/// /// 事务,一系列的SQL语句 针对的是我们的增删改,insert update delete /// /// /// public static bool ExcuteTrans(List listSQL) { //using语句块是不存在异常处理机制的,只是相当于try,finally; using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); //conn.BeginTransaction(); 开启事务,要求conn必须是open才可以开启事务 SqlTransaction trans = conn.BeginTransaction(); //SqlCommand cmd = conn.CreateCommand(); ////设置Command的Transaction为我们的Command开启事务 //cmd.Transaction = trans; SqlCommand cmd = BuildCommand(conn, "", 1, trans); try { for (int i = 0; i < listSQL.Count; i++) { cmd.CommandText = listSQL[i]; //事务里做的操作一般都是增删改操作,所以直接调用ExecuteNonQuery(); cmd.ExecuteNonQuery(); } trans.Commit(); return true; } catch(SqlException ex) { trans.Rollback(); throw new Exception("执行出现异常"); } finally { trans.Dispose(); cmd.Dispose(); conn.Close(); } } }public static bool ExcuteTrans(List listCmd) { //using语句块是不存在异常处理机制的,只是相当于try,finally; using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); //conn.BeginTransaction(); 开启事务,要求conn必须是open才可以开启事务 SqlTransaction trans = conn.BeginTransaction(); //SqlCommand cmd = conn.CreateCommand(); ////设置Command的Transaction为我们的Command开启事务 //cmd.Transaction = trans; SqlCommand cmd = BuildCommand(conn, "", 1, trans); try { for (int i = 0; i < listCmd.Count; i++) { cmd.CommandText = listCmd[i].CommandText; if (listCmd[i].CmdType == 2) cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Clear(); if(listCmd[i].Parameters!=null && listCmd[i].Parameters.Length >0) cmd.Parameters.AddRange(listCmd[i].Parameters); cmd.ExecuteNonQuery(); cmd.Parameters.Clear(); //很重要 } trans.Commit(); return true; } catch (SqlException ex) { trans.Rollback(); throw new Exception("执行出现异常"); } finally { trans.Dispose(); cmd.Dispose(); conn.Close(); } } }/// /// 构造SqlCommand /// /// /// /// /// /// 【ADO.NET】 /// private static SqlCommand BuildCommand(SqlConnection conn,string sql,int cmdType,SqlTransaction trans,params SqlParameter[] paras) { SqlCommand cmd = new SqlCommand(sql, conn); if (cmdType == 2) cmd.CommandType = CommandType.StoredProcedure; if (paras != null && paras.Length > 0) cmd.Parameters.AddRange(paras); if (conn.State == ConnectionState.Closed) conn.Open(); if (trans != null) cmd.Transaction = trans; return cmd; } }

    推荐阅读