核心ADO.NET

下面所描述的皆是一些编程相关的知识加上我自己的理解,如果有误,欢迎指正,但拒绝不文明用语。

晚上刚好看书看到了这部分的知识,连章节名都没变,记录下来,供自己以后查阅。当然要是能给大家提供一点思路就更好了。

其实,自从EF开始流行起来,这一部分用的就少了,或者说根本没用,但怎么说这也是较基础的部分,还是好好学一下,起码自己能独立写一个比较像样的SqlHelper(这货估计更加没人用了,就是学习一下,一步一步来吧)

概述

据说这货以前还有个ADO,不过实在是没了解过,是个COM组件库,也就是在ADO.Net出现前,访问数据库就靠它了。其实,从名称上来看,它们的关系应该很紧密,不过书上说法也不是太清晰,一方面说,它们也就只是名称类似,类和访问数据的方法就是完全不同的,另一方面,估计是为了用ADO的都能够换到新技术呢,又说他们两差不多,很容易迁移。反正,就是那么回事吧。

ADO.Net中附带了三个数据库客户端名称空间,分别用于SQL Server,ODBC数据源,OLEDB。(与SQL Server相关的命名空间有三个,不算一些三个客户端共用的命名空间,毕竟关系在那儿,另两个分别就只有一个命名空间与其对应),下面依次列出来:

  1. System.Data.Sql,System.Data.SqlClient,System.Data.SqlTypes
  2. System.Data.Odbc
  3. System.Data.OleDb

书中然后提到了共享类,也就是说无论使用哪一种数据库,这些类都是会用到的。在我看来,无论使用哪个数据库,最后获取到的数据都是一样的,所以不管采用何种方式访问数据库,到最后数据的格式应该是统一的。所以呢,看到的共享类,果不其然也就是DataSet,DataTable,DataRow.DataColumn这一类的。应该说无论访问方式有什么不同,最后获得的数据都是按一个格式处理的,总觉得这个有点类似接口编程,管你怎么实现最后给我结果就OK了。

仔细想想,数据库访问的流程是什么样子的呢?首先,我们需要连接到数据库,然后得有命令,得告诉数据库我们要干啥,接下来数据库根据我们的命令完成任务。但这还不算完,最后总得给个反馈呀,这里反馈在我看来又分两种:

  1. 像增加,更新,删除这类操作,不需要返回什么数据,你只需要告诉我这事你给我完成没有。用数据库的话说,你得告诉我因这条命令受影响的行数呀。
  2. 第二类 就是查询操作,这操作跟增删改不一样,这就不只是搞没到定的问题了,你得给我数据呀。个人觉得上面提到的共享类也就是用来承载这里的数据的。

这样一次数据库访问才算完成。当然,对于程序而言,你连接了获得了自己想要的,你还得断开连接,释放占用的资源。根据这一流程,我们可以知道数据库访问是需要一些类来给予支持的。也就有了数据库专用类的概念。数据库专用类就包括连接(DbConnection),命令(Command),命令构建(CommandBuilder),DataAdapter,DataReader,传递参数(parameter),事务(Transaction)。这里只是将一些较主要的内容做了一些理解。

另外,其实这三种数据库的操作方式都大同小异,学会一种,其他也就差不多了。就例如,连接类SqlConnection,如果要用OleDb,只需要将Sql换掉就是,也就是成了OleDbConnection,使用方法一模一样,究其原因,就是因为他们都派生于实现了IDbConnecton的DbConnection类(貌似这里有工厂的概念)。

连接

连接得需要连接字符串,一般来说,都是配置在程序的配置文件中。至于连接字符串张什么样,可以去www.connectionstrings.com看看(看这网址就知道是干啥的了)。下面提供一个例子,如何来在配置文件中定义连接字符串:

<configuration>
    ...
    <connectionStrings>
        <add name="connstr" connectionString="Server=.;intergrated security=SSPI;database=Northwind" providerName="System.Data.SqlClient" />
    </connectionStrings>
    ...
</configuration>

 数据库连接是一种稀缺资源,这一点必须要有一个清晰的认识,而对于稀缺资源,最好就是确保每个资源在使用结束后就立即释放。ADO.Net主要通过以下两种方法,这两种方法也应该是我们在代码中应该遵循并使用的。

  1. 利用try...catch...finally语句块
try
{
    conn.Open();
    //Do something useful
}
catch(SqlException ex)
{
    //Log the exception
} 
finally
{
    //Ensure that the connection is freed
    conn.Close()          
}

      2.使用using语句块

  using子句可以确保在退出块后立即释放实现IDisposable接口

微软的建议是我们应该组合使用这两种方法,示例如下

try
{
    using(SqlConnection conn=new SqlConnection(connstr))
    {
        //Open the connection
        conn.Open();

        //Do something useful
        //Close if myself
        conn.Close();
    }
}
catch(SqlException e)
{
    //Log the exception & rehtrow
    //throw;
}

最后,我们应该注意,在Framework2.0及以上版本,在System.Transactions中,添加了TransactionScope类,用来简化事务代码的编写,有兴趣的可以了解一下。

命令

命令在数据库访问中主要有三种形式,一是包含SQL语句的文本字符串,二是存储过程(StoredProcedure),三是TableDirct(这个好像只针对OleDb提供程序有效,用于其他命令程序则会抛出异常,具体是什么我也不太了解)。在我看来,我们最常用到的就是文本字符串,存储过程与之相比,就几乎是没有使用,至少,我也只是在分页这一块有机会见到,或许说是我没见到过逻辑复杂的大程序。

命令定义完成后,就需要执行命令。ADO.Net一共提供4种方式,ExecuteNonQuery()、ExecuteReader()、ExecuteScalar()、ExecuteXmlReader(),其中最后一种只用于SqlClient提供程序。对于Xml现在的了解也不是太深入,所以最后一种方法在这里就不做说明。

ExecuteNonQuery()

在我看来,理解这个方法主要看后面的描述“NonQuery",也就是说只要不是查询相关的,我都可以搞定,这也就对应前面所说的数据库给予的第一类反馈。我只要知道完成没有,不要给我数据,我不需要。那么如何知道命令的完成情况呢?我们可以通过该函数的返回值来判定,返回值是int类型的整数,所代表的含义就是命令所影响的行数,这个跟你用SSMS写sql语句(不是查询),当点击执行后,所返回的消息都是”(1行受影响)“是一个意思。所以,该方法都用于增删改这类不需要返回具体结果的操作,从元数据中,也可以看出,该方法只有两个版本,一个同步,一个异步,返回结果都是int。

ExecuteReader()

说到了不返回具体数据的方法,那么肯定就有返回的方法,总不能让改不让看吧,万一我还想看完再改呢。这就需要用到第二种方法ExecuteReader(),该方法执行命令后就会根据使用的提供程序返回一个类型化的DataReader对象,该对象可以遍历返回的记录。什么是类型化的对象呢?我觉得这个就是由所使用的数据库引起的,也就是说DataReader对象得有不同的实现从而达到从不同数据库中读取数据的目的,也就如同我所说的,你可以分别在三个数据库客户端名称空间中找到SqlDataReader,OdbcDataReader,OleDbDataReader这三个对象,他们都继承自DbDataReader这个实现了IDataReader接口的抽象类。那么这个对象可以用来干什么呢?DataReader,翻译过来也就是数据读取器,所以也就很好理解了,它就是用于读取根据你输入的sql返回的数据库记录。不过,该方法也是功能最弱的方法,这也很容易理解,功能弱,涉及到的内容也就十分简洁,所提供的功能十分清晰,单一,不容易出错,这也正是数据库访问需要做到的。而这个功能若是如何体现的呢?这个就体现在不能直接实例化数据阅读器,只能通过该方法返回一个数据阅读器的实例。也就是说,你没有任何变通的可能,要用?可以,但必须按照我的规定来,就是调用ExecuteReader()方法,我在这里规定了很多内容,你必须一一满足,这样,我才能给你返回DataReader对象,才能进行进一步的操作。另外,该读取器是一个只能向前的连接读取器,即只能从开始到结束沿着一个方向来浏览记录,并且在此过程中,所使用的数据库连接是一直打开的,只有关闭数据读取器,连接才可以关闭。这一特性就决定了我们不能在数据读取的过程中进行一些相关的数据操作,一方面,进行操作是要耗费时间的,这样就会延长数据库的连接时间,其他用户在此期间就什么都干不了,可以说这是一种很恶劣的用户体验。当然,还有并发什么的,我也不太懂就不讨论了;另一方面,你只能往一个方向读取,所以在这个时候压根不可能基于全局来做一些整体化的操作,前面的改不了,后面的又不知道是什么,所做的更改肯定有限。说到这儿,我才明白书上所说的这句话:“ADO.NET最重要的功能就是:它们是以断开连接的方式工作”。所有我们跟数据库的实际流程是这个样子的:连接数据库,检索数据,断开数据库,客户端处理数据,重新连接数据库,传递数据,断开数据库。

ExecuteScalar

该方法返回一个结果。如果查询数据多,可以使用ExecuteReader()方法,来一条一条的读取。可如果只需要返回一个结果呢,如给定表的记录个数,服务器的当前时间等,就可以使用ExecuteScalar。通过Reflector可以看出,该方法最终调用了RunExecuteReader()方法,而ExecuteReader也调用了RunExecuteReader()方法,所以说,这两个方法内部实现是一样的只是通过不同的封装,返回了不同的结果。简单说ExecuteScalar就是将ExecuteReader的第一条结果(如果使用一样sql的话,也只有一条数据)装箱成为Object对象并返回。

总的来说,访问数据库无非是两个目的,一 对数据做修改(增删改) 二 查询数据。

管理数据和关系

DataSet大家应该都很熟悉,只不过现在基本被Entity Framwork代替,用的越来越少了,这里就简单了解一下。

首先,看定义:DataSet类是数据的脱机容器。容器是用来装东西的,在这里装的就是数据(数据的关系也是一种数据),脱机,也就说明关于数据库连接呀以及相关的内容都没有,不包含,不关注。所以,DataSet类中的数据不只是会来源于数据库。只不过可以说,给ADO.NET提供了一个较好的数据承载容器,数据库可以把数据读取出来放在DataSet,然后就可以断开连接,以后不论发生什么就都跟数据库没有关系了。特别注意,DataSet类基本上是内存中的数据库,就是说在使用时就必须考虑到内存的使用情况,不要无节制的向其加入数据。至于具体操作,有兴趣的可以自己搜搜看。

这一部分还涉及到了存储过程,事务的处理,由于内容太多,就不再这里意义说明了。

原文地址:https://www.cnblogs.com/prayol/p/6024982.html