8.1、ADO.NET的命名空间和数据库访问
ADO.NET是.NET平台提供的一种数据库访问技术。下面按照命名空间的分类来说明ADO.NET的各类数据库访问:
8.1.1、System.Data
定义和部分实现了ADO.NET体系结构的类、接口、委托和枚举。
8.1.2、System.Data.SqlClient
Sql Server的.NET Framework数据提供程序。这个是访问sql server数据库的最佳方式,外界程序连接Sql server数据库的连接字符串:
A、“Server = 172.25.112.136;database = vips;uid = vips_user;pwd = vips_user”
B、“Provider = SQLOLEDB.1;User ID = vips_user;password = vips_user;Initial Catalog = vips;Data Source = 172.25.112.136”
注意:
(1)、以上是可以不加Provider属性的,因为SqlClient就是访问sql server的提供程序。
(2)、一般在一个服务器上,只会安装一个版本的sql server数据库,比如sql server2005、sql server2008等,安装一个sql server数据库的过程中,会产生一个数据库实例,所以一般的数据库服务器上只会有一个数据库实例,正因为如此,上面的Server或者Data Source都只赋予了IP地址,它们连接的就是那个数据库实例。
但是如果一个服务器上由于安装了多个版本的sql server数据库而产生了多个数据库实例,或者同一个版本的数据库安装了多个数据库实例,那么在连接数据库的时候,上面的Server或者Data Source就会被赋予IP实例名。这个IP实例名可以通过打开SQL Server Management Studio,登录的时候,按照下面的图示来找到实例名:
(3)、对于sql server,一个实例下面可以对应很多用户,但是只会有一套表、视图、函数、存储过程等数据库元素,这些元素是被所有用户所共享的,只是用户操作这些元素的权限不一样。
8.1.3、System.Data.OracleClient
因为oracle不是微软的东西,所以在using这个命名空间之前要添加引用,引用名字是:System.Data.OracleClient。
Oracle的.NET Framework数据提供程序,这个是访问oracle数据库的最佳方式,外界程序连接Oracle 9i或者10g数据库的连接字符串:
A、“Server = net服务名;uid = scott;pwd = 123”
B、“User ID=pvpt;Password=pvpt;Data Source=PTJGZZ”
C、DataSource=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=172.25.112.50)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=orcl)));User ID=elias;password=elias;
Max Pool Size=512;Connection lifetime=12
注意:
(1)、A、B两种方式连接oracle的时候先要在机器上安装一个oracle客户端去配置一个网络服务名;C方式则不需要。以上是可以不加Provider属性的,因为OracleClient就是访问Oracle的提供程序。
(2)、对于oracle,在一个服务器上由于安装了多个版本的oracle数据库而产生了多个数据库实例,或者同一个版本的数据库安装了多个数据库实例,(??????待确认)所以在这种情况下,连接字符串也要指定实例名(???????????待确认)
(3)、对于oracle,一个实例下面可以对应很多用户,每个用户都会有自己的一套表、视图、函数、存储过程等数据库元素,其中一个用户能够访问另外一个用户里面的元素,但是这个访问有可能受到权限的控制。
8.1.4、System.Data.OleDb
OLEDB的.NET Framework数据提供程序。在这个命名空间中,提供了访问DB2、SQL
SERVER、ACESS、ORACLE、ODBC等数据的程序。他们对应的连接字符串中的provider
属性是:DB2OLEDB、SQLOLEDB、Microsoft.jet.oledb.4.0、MSDAORA、MSDASQL,很显然,OleDb可以访问以上提到的各种数据库,但是对于sql server和oracle这两种最为主流的数据库来说,oledb并不是效率最高的,所以一般不用这个。
OleDb连接Acess数据库的连接字符串(access一般在本地):
“provider = Microsoft.jet.oledb.4.0;data source = c:mdbback orthwind.mdb”
注意:
这个只是连接access数据库的,它一般在本地,而且不需要用户名和密码,针对其他的数据库,比如DB2、ODBC等可能不仅需要provider和data source属性,还需要database、uid、pwd等属性;此处的Provider属性是必不可少的,因为oledb有很多的数据库提供程序,要选择一个具体的才可以;连接字符串是在每一种数据库连接对象中都存在的,而不是某一种数据库连接对象所特有的,每一种数据库连接对象所对应的连接字符串都由若干项组成,只要满足各种连接对象的需求即可,并不需要连接字符串中的属性项数完全相同,但是属性名对于各种连接对象都是通用的。
8.1.5、System.Data.Odbc
其实在OleDb中,对ODBC数据库的访问提供程序MSDASQL是无法使用的,因为在ADO.NET中有System.Data.Odbc专门提供了访问ODBC数据库的程序。
8.2、ADO.NET的架构-----常用类
以sql server的数据库访问提供者System.Data.SqlClient为例来讨论常用的类,对于其他数据库访问的提供者来说,都有这些类,而且用法都是一样的,只是前缀名不同而已。
8.2.1、SqlConnection
SqlConnection是一个连接对象,它的主要功能就是建立与数据库服务器的连接的,或者关闭这些连接,使用方法如下:
(1)、连接字符串的构成
A、Data Source(Server、Address、Addr、NetworkAddress)
这个属性表示的是数据源,在sql server中就是服务器的名称+实例名或者IP地址+实例名,在oracle中是网络服务名,在ACESS就是包括mdb数据库文件名在内的完整路径。
B、Initial Catalog(Database)
这个属性表示的是数据库名字,是针对sql server的。
C、UID(User ID)
这个表示的是用户ID。针对sql server和oracle的。
D、PWD(Password)
这个表示的是用户密码。针对sql server和oracle的。
E、Provider
这个表示程序提供者的名称,主要是针对Acess数据库的访问的。
注意:以上的属性都是不区分大小写的。
(2)、创建一个连接对象
SqlConnection sqlConn = new SqlConnection();
(3)、给连接对象的连接字符串属性赋值
sqlConn.ConnectionString="Server=172.25.112.21;database=vips;uid = vips_user;pwd = vips_user";
(4)、打开连接
sqlConn.Open();
注意:一个已经打开了的连接在关闭之前是不能重复打开的。
(5)、关闭连接
sqlConn.Close();
注意:一个已经关闭了的连接在打开之前还可以再次关闭,不会报错,所以可以在try ……catch……中两次使用close,在try中已经关闭了连接,但是在关闭连接后面的代码中出错了,然后就跳到catch中来了,这个时候连接已经关闭了,那么可以再次关闭。在catch中的close是很重要的,因为可能try中出异常的时候,连接还未关闭。
注意:
客户端与服务器的连接是非常昂贵的,它们占用了客户端与服务器各方的资源,让一个不工作的连接长期处于连接状态是不必要的,要及时的关闭这些不使用的连接。打开太多的与服务器的连接会降低服务器的速度,并且会阻碍建立新的连接。
连接池的概念:连接池是服务器上的内存中的用来存放连接的一块空间。数据库与服务器建立连接之后,这个连接就会在内存中的连接池中存放,即使连接关闭之后,这个连接池中的连接也不会马上就释放掉,而是被标记为未连接,当有新的连接请求发送过来的时候,服务器就会去判断连接池中是否有未使用的与请求相匹配的连接,如果有就直接使用这个。建立一个全新的连接大概要几秒钟,但是使用连接池中的连接只需要几毫秒。
8.2.2、SqlCommand
(1)、定义一个SqlCommand对象
SqlCommand sqlComm = new SqlCommand();
(2)、给SqlCommand对象的连接属性赋值
sqlComm.Connection = sqlConn;
(3)、给SqlCommand对象赋值要执行的命令
sqlComm.CommandText = "insert into t(id) values('1')";
(4)、SqlCommand对象的ExecuteNonQuery方法
这个函数是执行没有返回值命令的函数,只针对对数据库的非查询动作,但是函数本身会返回一个int值,表示命令执行后影响的数据库的表的行数。
(5)、SqlCommand对象的ExecuteScalar方法
这个函数是返回查询的结果集中,第一行第一列的值,如果有多行多列会忽略掉其他的行和其他的列,返回类型是object类型。这个函数是针对的select语句,一般是查询结果只有一行一列的最为适用。结果要经过转换才能得到想要的类型。
注意:
(1)、以上的两个函数的执行一定要在连接打开之后,否则会出错的。
(2)、CommandText是接受sql语句的,可以使增删查改语句,也可以是执行存储过程的命令,还可以是查询,也就是说所有的DML操作以及查询都是可以的,还可以是多条语句同时进行,语句之间用分号隔开即可。在一个连接打开后,可以通过改变CommandText的值来多次执行这两个函数,然后才关闭连接,所以可以通过这种方式在一个连接未关闭的情况下按照顺序执行多次sql语句。
(3)、对于ExecuteScalar,是针对那种查询只有一个值的的情况的,返回object类型,如果查询出来是多行多列,也只会取第一行第一列。如果这种查询查出来是行数为0,即什么也没查询出来,那么函数返回的是C#中的null,此时用Tostring转换就会出错;如果查询出来是一行一列,但是是数据库中的null,那么返回的就是Convert.DBNull,那么此时用Tostring转换之后就是长度为0的字符串;如果查询出来是一行一列,但是是数据库中的长度为0的字符串,那么返回的就是object类型,此时用Tostring转换之后就是长度为0的字符串。
8.2.3、SqlDataAdapter
数据适配器主要是针对查询语句的,只有当我们需要从数据库中查询出数据集的时候,才会用到适配器。
(1)、定义一个SqlDataAdapter对象
SqlDataAdapter sqlDA = new SqlDataAdapter();
(2)、给SqlDataAdapter对象的SelectCommand属性赋值
sqlDA.SelectCommand = sqlComm;
注意:这个前提是sqlComm的CommandText属性是一个查询语句。
(3)、填充到数据集
sqlDA.Fill(ds);
注意:
(1)、Fill方法是把从数据库中查询到的数据集填充到DataSet中。在DataSet中是以表的形式存在。
(2)、可以往一个DataSet中填充多张表:Fill常用的有两个函数重载,第一个是Fill(DataSet ds),这个只有一个参数,那么填充的时候会按照执行Fill填充的顺序把多长表填充到DataSet中,访问的时候就按整数照索引来访问就可以了;第二个函数是Fill(DataSet ds,string srcTable),这个是在填充表格到数据集中的时候还要对这个表命一个名字,以便以后可以通过这个名字来访问这个表,表名是任意的,但是不可以重复,这种方式仍然可以按照填充的顺序根据整数索引来访问,整数索引是从0开始的。
(3)、DataSet其实是C#中的一个概念,是独立于ADO.NET的,当把数据库中查询到的数据集合填充到DataSet之后,与数据库的连接就可以关闭了。
(4)、只要调用了sqlDA.Fill(ds)函数,不管查询的时候是否有结果,哪怕什么也没查询出来(结果是0行),ds里面也是有table的,可以用ds.Tables[0]来引用这张表,表结构与查询的结构一致;但是此时如果用ds.Tables[0].Rows[0]来引用第零行是错误的,因为根本就没有行,此时要用ds.Tables[0].Rows.Count来判断是否为0;但是如果有行,但是某行中的某个列是数据库的null或者数据库中的长度为0的字符串,那么此时用Tostring转换之后就是长度为0 的字符串。-----这个是DataSet的情况;
8.2.4、DataSet
数据集是一个容器,是用来存放表的,是表的集合,而表又是由行列共同构成的。
(1)、定义一个数据集对象
DataSet ds = new DataSet();
数据集有个表的集合:ds.Tables
表集合有个重要的属性:表的数量:ds.Tables.Count
对表的引用可以根据Fill填充的时候命的表名,也可以用数字索引,一般都只会填充一张表,而且也不会去命名,所以一般就直接:ds.Tables[0]来引用这张表。
注意:DataSet其实是C#中的一个概念,是独立于ADO.NET的
8.2.4.1、数据表DataTable
定义一个数据表对象:DataTable dt = new DataTable();
-----------------------------------------------------------
数据表有行的集合:dt.Rows
行集合有个重要的属性:行数:dt.Rows.Count
对行的引用只能根据整数索引来引用:dt.Rows[0]
-----------------------------------------------------------
数据表也有列的集合:dt.Columns
列集合有个重要的属性:列数:dt.Columns.Count
对列的引用可以根据索引:dt.Columns[0],也可以根据列名字符串:dt.Columns["vid"],表如果是从数据库里面查出来的,此处的列名就是数据库表中的列名。
8.2.4.1.1、数据行DataRow
定义一个具体数据行对象:DataRow dr = new DataRow();
对该行中列值的引用:可以根据数值索引:dr[0],也可以根据列名字符串索引:dr["vid"]
8.2.4.1.2、数据列DataColumn
定义一个具体数据列对象:DataColumn dc = new DataColumn();
列有一个ColumnName属性很重要,通过该属性可以取得列名:dc.ColumnName;
对该列中行值的引用:只能根据整数索引来引用:dc[0]---这种是错误的
8.2.4.2、在C#中创建Table并添加数据
DataTable table = new DataTable("ComboBoxDataSource");
table.Columns.Add("textFiled",typeof(string));
table.Columns.Add("valueField",typeof(string));
DataRow row;
row = table.NewRow();
row["textFiled"] = "a";
row["valueField"] ="A";
table.Rows.Add(row);
row = table.NewRow();
row["textFiled"] = "b";
row["valueField"] ="B";
table.Rows.Add(row);
row = table.NewRow();
row["textFiled"] = "c";
row["valueField"] ="C";
table.Rows.Add(row);
row = table.NewRow();
row["textFiled"] = "d";
row["valueField"] ="D";
table.Rows.Add(row);
注意:
(1)、以上所有的索引全部是从0开始的。
(2)、在C#语言中,数值类型的数据默认为0,引用类型的数据默认为null。
(3)、从数据库里面取出来的某行某列的一个值的类型是object类型的,要转换成自己需要的类性。
(4)、数据库里面的NULL取出来后是object类型的,不等于C#中的null,用Tostring转换之后就是长度为0 的字符串。数据库里面长度为0的字符串取出来后也不等C#中的null,用Tostring转换之后就是长度为0 的字符串。C#中的NULL用Tostring转换会出错。
(5)、String.IsNullOrEmpty (string value)
这个是在C#中判断字符串value是否为null或者长度为零的字符串,字符串中长度为0的字符串不是null,而是empty,没有被创建的才是null。
(6)、Convert.DBNull:一个常数,表示数据库中的null,这个是在C#中用来对比数据库里面取出来的null值的。 Convert.DBNull != C#中的null
Convert .IsDBNull (Object value)
返回有关指定对象是否为 DBNull类型的指示
8.2.5、SqlTransaction
ADO.NET中也有事务机制,规则如下:
publicstatic void test1()
{
SqlConnection sqlConn =new SqlConnection();
sqlConn.ConnectionString = SqlConnectionString;
sqlConn.Open();
SqlTransaction sqlTrans = sqlConn.BeginTransaction();
SqlCommand sqlComm = new SqlCommand();
sqlComm.Connection = sqlConn;
sqlComm.Transaction = sqlTrans;
try
{
sqlComm.CommandText = "insert into t values('111','111')";
sqlComm.ExecuteNonQuery();
sqlComm.CommandText = "insert into t values('122','122')";
sqlComm.ExecuteNonQuery();
sqlTrans.Commit();
sqlConn.Close();
}
catch
{
//如果在执行的过程中,网络断开了,进来之后,直接执行sqlTrans.Rollback()就会出错
//因为此时该事务不再有效,那么sqlTrans.Connection==null
if (sqlTrans.Connection !=null)
{
sqlTrans.Rollback();
}
sqlConn.Close();
}
}
注意:
(1)、事物是在同一个连接上面发生的。
(2)、在SQL SERVER 2005中,普通的sql语句执行一句,那么就会直接往硬盘上写一个结果而非事务;对于存储过程没使用事务的时候,也是执行一句就往硬盘上写一个结果,但是如果使用了事务,那么所有的执行全部在内存中,直到正常执行完成,commit提交才能写到硬盘上。如果在正常提交之前出现异常,程序直接终止了,那么内存中的数据就会丢失而不会写到硬盘上,如果正常提交之前,出现异常,执行了rollback语句,那么就回滚了,不会写到硬盘上面了。
(3)、ADO.NET中,普通的executenonquery函数执行之后,直接写到硬盘上面,但是如果使用了事务的话,就不会了,会先写到内存中,直到正常执行完成,commit提交才能写到硬盘上。如果在正常提交之前出现异常,程序直接终止了,那么内存中的数据就会丢失而不会写到硬盘上,如果正常提交之前,出现异常,执行了rollback语句,那么就回滚了,不会写到硬盘上面了。
(4)、可以使用try……catch……来捕获异常,使用事务的时候一定要使用这个,异常是可以向外层传递的。
8.2.6、SqlParameter
在8.2.2节中,已经说过,利用SqlCommand可以执行DML操作以及查询,但是这些执行对sql命令是有限制的。对于一般的增删查改以及存储过程的执行都没有问题,只要那些增删改查操作以及存储过程的命令在C#里面就能够拼凑成sqlserver能够识别的语句即可;但是对于一些特殊的情况,在C#里面是没有办法拼凑出能够让sql server识别的命令的,比如:
(1)、新增一条数据,但是数据库中有一个字段是image类型,这个类型将会存放一张图片,值在数据库中是以字节数组的形式存放的,在C#中,我们首先会将图片转换成为字节数组,但是字节数组没法与sql语句结合去拼凑成sql server识别的命令。
(2)、存储过程中有些参数是output类型的,此时,也没法通过一个C#变量与sql语句结合去拼凑成sql server识别的命令,来让存储过程的output类型的变量返回值被C#变量获取。
所以,在这些特殊的情况下,就必须使用到SqlParameter,这个是对C#里面的即将要传给sql server的变量的封装,下面以ADO.NET调用存储过程的另外一种方式来讲解SqlParameter的应用:
public staticvoid test()
{
SqlConnection sqlConn =new SqlConnection();
sqlConn.ConnectionString = SqlConnectionString;
sqlConn.Open();
SqlCommand sqlComm = new SqlCommand();
sqlComm.Connection = sqlConn;
sqlComm.CommandType =CommandType.StoredProcedure; //表示要执行的命令是存储过程
sqlComm.CommandText ="zwjtest";//表示要执行存储过程的名字
SqlParameter p1 = new SqlParameter("@p1",SqlDbType.Int);
p1.Direction = ParameterDirection.Input;
p1.Value = 5;
//存储过程的形参有三个要素:名字、数据类型、输出类型(input、Output
),在C#中,"@p1"对应数据库形参的名字,注意前面要加@符号;SqlDbType.Int对应数据库形参的数据类型;ParameterDirection.Input对应数据库形参的输出类型;p1.Value表示将要传递给数据库形参的的值。
SqlParameter p2 = new SqlParameter("@p2",SqlDbType.Int);
p2.Direction = ParameterDirection.InputOutput;
p2.Value = 10;
SqlParameter p3 = new SqlParameter("returnValue",SqlDbType.Int);
p3.Direction = ParameterDirection.ReturnValue;
sqlComm.Parameters.Add(p1);
sqlComm.Parameters.Add(p2);
sqlComm.Parameters.Add(p3);
try
{
sqlComm.ExecuteNonQuery();
int a = int.Parse(p2.Value.ToString());
}
catch
{
sqlConn.Close();
}
}
注意:
(1)、在sql server 2005中,Output形参不仅具有输出也具有输入的功能,形参不会去屏蔽实参原来的值,会被初始化为原来的值,相当与oracle中的inout;但是input就只具有接收实参值的输入功能;存储过程是有默认的返回值的(整型的),即使没有return语句,它也会返回一个值,但是当有return语句的时候,就以return返回的整数值为主了。返回值一定是int类型,如果自己去写个字符串就错了。
(2)、在C#中ParameterDirection主要有input、Output、InputOutput三种类型。
当只输入的时候,C#中ParameterDirection要用input,存储过程的参数就要用input;当只需要输出的时候,C#中ParameterDirection也要用Output,存储过程的参数就要
用Output,此时在C#中,不会对参数变量赋值,在存储过程中,我们也不会去写代码引用该参数的值,只会给它赋值;
当既需要输入又需要输出的时候,C#中ParameterDirection要用InputOutput,存储过程的参数要用Output,此时在C#中会对参数变量赋值,在存储过程中,也会对实参赋值,最后C#对应的变量会获取到这个返回值;
要想取得存储过程的返回值,还是要定义一个参数,添加到sqlComm.Parameters中,与存储过程对应的形参是不存在的,所以参数名为"returnValue"数据类型是SqlDbType.Int,ParameterDirection是ReturnValue。
(3)、执行存储过程可以用ExecuteNonQuery(),也可以用ExecuteScalar(),这两个函数都可以,它们与存储过程是否会return值,return什么值没有任何的关系,只是去执行了存储过程而已。执行ExecuteScalar的时候,不管有没有return,这个的返回值都是null。
(4)、所有C#的SqlParameter参数的value都是object类型的。
8.3、ADO.NET操作Oracle
ADO.N ET操作Oracle与ADO.N ET操作sql server是基本一致的,在此只说明一下不同点:
8.3.1、Oracle执行语句的机制(与sql server不同)
在Oracle中,普通的sql语句执行的时候,结果先写到内存中,如果没有提交,结果就在内存中,那么之后本连接才可以看到结果,别的连接是无法看到结果的,除非提交了,才真正写到硬盘上,别的连接才可以看到。提交的方式有很多,有commit,disconnect等。在存贮过程里面也是这样的,任何一个语句的执行都是在内存中,除非提交才会写到硬盘上。
8.3.2、ADO.N ET执行Oracle语句
ADO.NET连接oracle的时候,普通的executenonquery函数执行之后,即使连接还没有关闭,结果也直接写到硬盘上面,本来理论上应该是在内存中的,但是因为ADO.NET是微软做的,他就是为了统一起来,在执行的时候隐含了提交,所以就写到硬盘中了。
8.3.3、ADO.N ET执行Oracle语句
如果使用了事务的话,就不会了,会先写到内存中,直到正常执行完成,commit提交才能写到硬盘上。如果在正常提交之前出现异常,程序直接终止了,那么内存中的数据就会丢失而不会写到硬盘上,如果正常提交之前,出现异常,执行了rollback语句,那么就回滚了,更不会写到硬盘上面了。
8.3.4、ADO.N ET执行Oracle存储过程
ADO.NET还可操作存储过程,这个就是传参数而已,执行全部在存储过程里面,那就按照oracle的存储过程方式了而不会按照sqlserver方式了。
ADO.NET调用oracle存储过程:
(1)、在定义OracleParameter(与oracle存储过程的形参对应的实参)的时候,先要指定参数名:p1、p2、p3......等。
(2)、指定参数的数据类型:
在存储过程参数传递的时候,会把实参的对参数的限制(长度、精度、小数位数等)一起传递给形参,之后形参的范围就会按照实参的来约束。而形参本身是不能自己来定义约束条件的,否则就会出错。但是对于表中定义字段或者存储过程中定义变量,字符串的长度是一定要指定的,数值可以指定精度,也可以默认。
OracleType.Number与oracle存储过程的Number对应,number不需要指定size。
OracleType.DateTime与oracle存储过程的date对应,datetime不需要指定size。
OracleType.VarChar与oracle存储过程的VarChar2对应:
对于in类型,形参只是接受实参的值,在存储过程中对形参只能读不可写,这种情况下,实参不指定size或者指定为0,形参可以全部接收,但是如果指定了size,如果size小于实参的实际长度,那么传入的实参以size为主,否则以实际长度为主,一般不去指定长度;
对于out,实参赋值也没意义,但是形参是要赋值,然后返还给实参的,这种情况,实参不指定size或者指定为0,那么形参的最大的大小就是实参的实际长度,如果对实参的size赋值,而且它的size就是实参最终的最大长度,实参不会接受形参的值,只会将自己最终的值给形参,可以是null;一般都会指定。
对于in out,如果没有指定size或者指定为0,那么形参默认最大的大小是传入的实参的字节数目,实参会传入全部的值,如果指定了size,如果size小于实参的实际长度,那么传入值以size为主,否则传入值以实际长度为主,形参的最大长度以指定的size为主了。一般都会指定。
(3)、指定参数的ParameterDirection:
ParameterDirection.Input对应着oracle中的in:那么实参只是把值传递给形参,形参获得实参传递的值之后,是只读的,不可写,不能改变外面的值。
ParameterDirection. Output对应着oracle中的out:这种模式下面,不论实参是什么值,都会被忽略不计,形参就像新定义的没有被初始化的变量一样,初值为null,这个变量具有读写属性,存储过程结束后,形参最终的值会赋给对应的实参。
ParameterDirection. InputOutput对应着oracle中的in out:这种模式下面,形参就像新定义的变量一样,而且被初始化为实参的值了,这个变量具有读写属性,存储过程结束后,形参最终的值会赋给对应的实参。
(4)、指定参数的值。
(5)、指定参数的与存储过程中对应的实参的名字。因为参数是与存储过程中的形参名对应的,所以即使添加参数的时候,不按照形参的顺序添加也没关系。
(6)、对于存储过程的异常,如果存储过程中没有异常捕获而出了异常,那么肯定会向外抛出异常的,这种事务是不会提交的;如果有捕获了,但是在捕获后,没有抛出RAISE_APPLICATION_ERROR(-20101,flag || '³Ìzwj_prostudyÖ´ÐÐÒì³££º'|| SQLERRM); 那么就认为存储过程把异常处理了,外面就不会再次捕获了,除非存储捕获异常之后抛出了才可以,捕获异常之后如果回滚就回滚,提交就把之前成功执行的全部提交。
(7)、在oracle的存储过程中,可以有return,但是不能添加返回值,这个只是用来结束存储过程的。所以在C#里面调用的时候,是无法通过OracleType. ReturnValue来获得返回值。
(8)、对于oracle的number类型的数据,在表定义、变量定义、存储过程参数定义的时候都不需要指定精度,指定精度也可以(存储过程参数除外);oracle的date类型任何时候都不要指定大小,因为它没大小;oracle的varchar2类型的在表定义、变量定义的时候一定要加长度,在存储过程的参数定义的时候一定不能加,存储过程的参数长度是由实参决定的。
(9)、oracle定义变量的类型的时候,可以用一般的数据类型,还可以将类型指定为某个表的某个字段的类型:表名.列名%TYPE.这种对于普通的变量定义,那么数据类型就跟指定的表名.列名完全一样了,但是对于存储过程的参数,则只能通过这个指定类型,而不能指定长度,长度由外面的形参决定。
8.3.5、Oracle重号问题
在oracle中,如果有一张表的某个列是不能重复的,每次用户取值的时候都是取出最大值加1,然后做一些别的计算,然后再次插入。如果是多个连接来取值,那么可能出现多个连接取的最大值相同,然后都加1,最后导致别的连接插入失败。解决办法:
(1)、让这个列作为整数的自增长,插入的时候不用去管这个列,系统自动的+1,那么就可以解决这个问题,这个是最理想的。
(2)、在oracle中,如果某个连接对某个表的某个列求出了最大值,然后就去做其他的很多的操作,最后做完了,及那个最大值加1然后构成新的行去插入到表中,那么从求出最大值之后到插入到表中有一系列的操作,耗费了很多的时间,只要在这个时间里面有别的连接来操作,那么就会取到相同的最大值,从而导致最后出现最大值重复。针对这种情况,我们可以缩小取得最大值到最终更新数据库的时间,那么可以对这个流水号单独建立一张表,这个表永远只记录流水号的最大值,那么取号的时候,立马去更新这个号,最终某个连接取出最大值的时候,数据库已经就是这个最大值了,那么这个时间就很短,重复的几率很小。这种方式有一个问题,因为在oracle中,取得号并且去更新这个号后,要提交,如果没有提交,那么别的连接取的号就是前面连接更新之前的号,虽然在前一个连接提交之前,现在的连接是无法去更新,但是它已经取得号了,这样一定会重复的,所以更新之后要立即提交,那么才能保证别的连接取得号是之前连接更新完了的号。
(3)、查询的时候就锁住表,然后取出最大值,加1,然后进行各种操作,完成之后再释放。在锁住后,让外界连查询都无法查询。只有做完了所有的操作释放了表才可以让别的连接来查询。这种是在某一时刻只能让一个用户来操作这个表,效率也不是很高。而且目前也不知道怎么锁住查询。
(4)、可以用一个循环来操作失败的连接,即让失败的连接的号再取一次,可以限制循环的次数。这种方式,当用户很多的时候,有可能导致最后的用户循环多次才能成功,所以这个效率是很低级的。这个只能是在用户数比较少(循环次数比较少),多个用户同时操作的几率比较小,是可行的。为了更进一步的降低风险,防止死锁,还可以让当前线程停留几秒钟。
本文转自:http://blog.csdn.net/zwj7612356/article/details/8163158