SQLServer联合C#
SQLServer联合C#(1)
当我们学会了C#编程,我们又懂得了数据库的数据操作,我们开发软件的目的其实多数情况是为了处理数据。所以,我们用的C#开发语言,第一个要解决的问题,就是如何和数据库交互的问题。因为数据库客户端是给我们开发者或者专业的数据库人员准备的,用户是不会用的。用户用的是C#开发的应用程序。
1.相关的概念和数据访问基础
-
ADO.NET概念:也就是在.NET平台上的ADO(数据访问对象)数据访问技术。都是通过.NET平台提供的各种数据访问对象的组合实现的。
-
ADO.NET包括:对数据库的各种CRUD(增删改查)。
-
ADO.NET对象:
【1】链接数据库:Connection对象
【2】操作数据库:Command对象(CRUD)
【3】数据读取对象:DataReader对象
【4】内存数据库:DataSet(用于数据缓存)
.NET平台为了更好的兼容各个数据库,使用了统一的接口,然后不同的数据类型操作的时候,使用的对象名称是不一样的,但是对象的操作方式完全一样。
比如:我们操作SQLServer数据库,我们使用SqlConnection、SqlCommand、SqlDataReader
2. 如何连接到数据库
【1】四个条件:服务器名称(或IP地址)、访问的数据库名称、服务器登录账号、密码
【2】条件封装:将以上四个条件按照格式封装到字符串中。
//如果是命名实例,必须按照如下方式写
“Server=AGOD21-123456\SQLExpress;DataBase=TestDB;User ID=sa;Password=a123456”
注意:关键字不要写错。不区分大小写,但是注意英文半角。
using System;
using System.Data.SqlClient;
using System.Data;
namespace SqlServerCRUD
{
class Program
{
static void Main(string[] args)
{
//封装连接字符串
string Common = @"Server=DESKTOP-JU5UICF\WINCCPLUSMIG2014;DataBase=TestDB;Uid=sa;Pwd=q123321";
SqlConnection conn = new SqlConnection(Common);
conn.Open();
//判断数据库当前状态
if (ConnectionState.Open== conn.State)
{
Console.WriteLine("打开成功");
}
conn.Close();
if (ConnectionState.Closed == conn.State)
{
Console.WriteLine("关闭成功");
}
Console.ReadKey();
}
}
}
2.1.链接时注意细节
在C#中进行连接SQLServer时要注意输入的服务器名称是否正确,访问的数据库名称是否在存在以及账号密码是否正确等细节
1>服务器名称含有特殊字符
当数据库用户名含有特殊符号时,如“\”,可在前面增加“@”或在后面追加“\”
@"Server=DESKTOP-JU5UICF\WINCCPLUSMIG2014;DataBase=TestDB;Uid=sa;Pwd=q123321"
"Server=DESKTOP-JU5UICF\\WINCCPLUSMIG2014;DataBase=TestDB;Uid=sa;Pwd=q123321"
2>错误的服务器名称
- 当使用了错误的服务名时,会报错:
System.Data.SqlClient.SqlException:“在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误。未找到或无法访问服务器。请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接。 (provider: SQL Network Interfaces, error: 26 - 定位指定的服务器/实例时出错)”
3> 使用了不存在的数据库
- 当使用了SQLServer内不存在的数据库时,会发生以下错误
System.Data.SqlClient.SqlException:“Cannot open database "TestDB1" requested by the login. The login failed.
Login failed for user 'sa'.”
无法打开登录请求的数据库“TestDB1”。登录失败。用户'sa'登录失败。“
4> 使用了错误的用户名和密码
- 当使用了错误的用户名和密码也会发生错误
System.Data.SqlClient.SqlException:“Login failed for user 'sa1'.”--错误的用户名
System.Data.SqlClient.SqlException:“Login failed for user 'sa'.”--错误的密码
3 操作数据库
为了方便项目的泛用性,一般连接数据库的操作会放在配置文件内,这样在后期项目移植的时候直接修改配置文件里的连接信息即可快速完成修改
<connectionStrings>
<add name="connString" connectionString="Server=DESKTOP-JU5UICF\WINCCPLUSMIG2014;DataBase=TestDB;Uid=sa;Pwd=q123321"/>
</connectionStrings>
- 在使用时需要添加引用和添加命名空间:System.Configuration;
在使用时只需以下代码进行调用
using System.Configuration;
namespace SqlServerCRUD
{
public class SQLHelper
{
public static string connString = ConfigurationManager.ConnectionStrings["connString"].ToString();
}
}
1>简单的封装插入SQL语句
- 在插入SQL语句时可以封装一个类去调用
using System;
using System.Configuration;
using System.Data.SqlClient;
namespace SqlServerCRUD
{
public class SQLHelper
{
public static string connString = ConfigurationManager.ConnectionStrings["connString"].ToString();
public static int ExecuteNonQuery(string sql, SqlParameter[] param)
{
//创建连接对象
SqlConnection conn = new SqlConnection(connString);
//创建一个命令执行对象
SqlCommand cmd = new SqlCommand(sql, conn);
try
{
conn.Open();
//判断如果为空就不添加参数
if (param != null)
{
//添加参数
cmd.Parameters.AddRange(param);
}
//返回受影响的行数
return cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
string errorString = "调用ExecuteNonQuery方法发生异常:" + ex.Message;
throw new Exception(errorString);
}
finally//标示前方不管是否发生异常都会执行的代码
{
conn.Close();
}
}
}
}
- 调用时
using System;
using System.Data.SqlClient;
using System.Data;
namespace SqlServerCRUD
{
class Program
{
static void Main(string[] args)
{
#endregion
InsertByParam();
Console.ReadKey();
}
private static void InsertByParam()
{
string sql = "insert into TestCrud(TestCrudId, TestCrudName,TestCrudNub, TestCrudMin, TestCrudMax, TestCrudPar)";
sql += "values(@TestCrudId, @TestCrudName,@TestCrudNub, @TestCrudMin, @TestCrudMax, @TestCrudPar)";
SqlParameter[] param = new SqlParameter[]
{
new SqlParameter("@TestCrudId","1003"),
new SqlParameter("@TestCrudName","小花"),
new SqlParameter("@TestCrudNub",1),
new SqlParameter("@TestCrudMin","1000"),
new SqlParameter("@TestCrudMax","2000"),
new SqlParameter("@TestCrudPar","NOK"),
};
int resule = SQLHelper.ExecuteNonQuery(sql, param);
Console.WriteLine("返回执行行数" +resule);
}
}
}
2>插入值为0的参数时注意事项
- 当数据库内参数类型为"int",但是插入值为0时,会发生报错,如下所示
System.Exception:“调用ExecuteNonQuery方法发生异常:参数化查询 '(@TestCrudId nvarchar(4),@TestCrudName nvarchar(2),@TestCrudNub ' 需要参数 '@TestCrudNub',但未提供该参数。”
- 这时需要在前面加上(object)
官方链接如下:
SqlParameter Constructor (System.Data.SqlClient) | Microsoft Learn
3> 调用含有存储过程的语句
在含有存储过程的语句时,可以做一个简单的封装,调用时可以去选择插入的时存储过程还是普通的SQL语句
using System;
using System.Data.SqlClient;
public static int ExecteNoQuery(string cmdText,SqlParameter[]param=null,bool isProcedure=false)
{
//创建连接对象
SqlConnection conn = new SqlConnection(connString);
//创建一个命令执行对象
SqlCommand cmd = new SqlCommand(cmdText, conn);
try
{
conn.Open();
//添加参数
if (param!=null)
{
cmd.Parameters.AddRange(param);
}
//判断是否为存储过程
if (isProcedure)
{
cmd.CommandType = CommandType.StoredProcedure;//这个枚举的设置表示当前cmdText是存储过程名称
}
return cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
string errorString = "调用ExecuteNonQuery方法发生异常:" + ex.Message;
throw new Exception(errorString);
}
finally
{
conn.Close();
}
}
储存过程如下所示:
- 调用
private static void InsertProcedure()
{
string str = "usp_AddProductNET";
SqlParameter[] param = new SqlParameter[]
{
new SqlParameter("@TestCrudId","1010"),
new SqlParameter("@TestCrudName","小蓝"),
new SqlParameter("@TestCrudNub",11),
new SqlParameter("@TestCrudMin","1000"),
new SqlParameter("@TestCrudMax","2000"),
new SqlParameter("@TestCrudPar","NOK"),
};
int result = SQLHelper.ExecteNoQuery(str, param, true);
Console.WriteLine("返回执行行数" + result);
}
4>调用含有默认值的储存过程
- 储存过程如下所示
private static void InsertProcedure2()
{
string str = "usp_AddProductNET2";
SqlParameter[] param = new SqlParameter[]
{
new SqlParameter("@TestCrudId","1011"),
new SqlParameter("@TestCrudName","小紫"),
new SqlParameter("@TestCrudNub",12),
new SqlParameter("@TestCrudMin","1000"),
new SqlParameter("@TestCrudPar","NOK"),
};
int result = SQLHelper.ExecteNoQuery(str, param, true);
Console.WriteLine("返回执行行数" + result);
}
5> 调用含有输入输出参数的储存过程
当有时候需要插入参数然后又需要确认参数是否插入重复时可以用以下方法,包含输入输出参数,在储存过程内进行逻辑判断
- 储存过程如下所示
--带有输入输出参数的存储过程
if exists(select*from sysobjects where name='usp_AddProductNET3')
drop procedure usp_AddProductNET3
go
create procedure usp_AddProductNET3
@TestCrudId int,
@TestCrudName char(20),
@TestCrudNub int,
@TestCrudMin char(20),
@TestCrudMax char(20),
@TestCrudPar char(3),
@TestNameFind varchar(50) output--定义输出参数
as
--判断当前表内的的对应参数是否和插入值相等
IF EXISTS( select * from TestCrud where [TestCrudName] = @TestCrudName)
BEGIN
--如果插入重复值则返回对应提示,并不进行插入参数
SET @TestNameFind = '插入重复名称:'+@TestCrudName
END
ELSE
BEGIN
--如果没有重复则插入参数
insert into TestCrud(TestCrudId, TestCrudName, TestCrudNub, TestCrudMin, TestCrudMax, TestCrudPar)
values (@TestCrudId, @TestCrudName, @TestCrudNub, @TestCrudMin, @TestCrudMax, @TestCrudPar)
SET @TestNameFind = '未重复'
END
go
--在SQLServer中,声明变量使用declare
declare @TestNameFind varchar(50)--定义输出参数
--------测试-------
exec usp_AddProductNET3 '1016','瓜7',7,'1000','2000','OK' , @TestNameFind output
--使用参数
select 查询到的总数=@TestNameFind
- 调用时如下所示:
- 在C#中调用时代码如下
private static void InsertProcedure3()
{
//创建输出参数
SqlParameter outParam = new SqlParameter("@TestNameFind", SqlDbType.VarChar,50);//设置输出参数的数据类型及长度
outParam.Direction = ParameterDirection.Output;//标示这个值时输出参数
string str = "usp_AddProductNET3";
SqlParameter[] param = new SqlParameter[]
{
new SqlParameter("@TestCrudId","1016"),
new SqlParameter("@TestCrudName","瓜7"),
new SqlParameter("@TestCrudNub",12),
new SqlParameter("@TestCrudMin","1000"),
new SqlParameter("@TestCrudMax","2000"),
new SqlParameter("@TestCrudPar","NOK"),
outParam,
};
int result = SQLHelper.ExecteNoQuery(str, param, true);
Console.WriteLine("返回执行行数" + result);
Console.WriteLine(outParam.Value.ToString());
}
- 在设置输出参数类型为"char","Varchar"等数据类型需要注意要设置长度,否则会报错,"INT"类型的不需要设置长度。如下所示,由于没有设置长度,会检测出参数为空
6>返回一个只读结果集的查询方法
- 当查询的时候,查询对象不一定会是单一的内容,有可能会是一个结果集,那么可以使用下面的方法
先封装成一个查询结果集方法
/// <summary>
/// 返回一个只读结果集的查询方法
/// </summary>
/// <param name="cmdText">Sql语句或者存储过程</param>
/// <param name="param">参数数组</param>
/// <param name="isProcedure">是否是存储过程</param>
/// <returns>返回受影响的行数</returns>
public static SqlDataReader ExecteReader(string cmdText, SqlParameter[] param=null, bool isProcedure = false)
{
//创建连接对象
SqlConnection conn = new SqlConnection(connString);
//创建一个命令执行对象
SqlCommand cmd = new SqlCommand(cmdText, conn);
try
{
//添加参数
if (param != null)
{
cmd.Parameters.AddRange(param);
}
//判断是否为存储过程
if (isProcedure)
{
cmd.CommandType = CommandType.StoredProcedure;//这个枚举的设置表示当前cmdText是存储过程名称
}
conn.Open();
// 执行命令时,关闭关联的 DataReader 对象时,关联的 Connection 对象也会关闭
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
catch (Exception ex)
{
string errorString = "调用ExecteReader方法发生异常:" + ex.Message;
throw new Exception(errorString);
//由于SqlDataReader这个对象并不是一个结果集,只是用来读取结果的一个对象,所以这时候,我们还没有把数据从数据库拿到客户端
//所以不能直接关闭连接
}
}
1.当返回的只是一个结果集时
static List<string> strA = new List<string>();
static List<string> strB = new List<string>();
private static void SelectQuery()
{
string sql = "select TestCrudId,TestCrudName from TestCrud where TestCrudId>1000";
SqlDataReader reader = SQLHelper.ExecteReader(sql);
while (reader.Read())
{
Console.WriteLine(reader["TestCrudId"] + "\t" + reader["TestCrudName"]);
//可以在这个地方封装到对象属性中
strA.Add(reader["TestCrudId"].ToString());
strB.Add(reader["TestCrudName"].ToString());
}
reader.Close();
}
2.当返回的只是多个结果集时
private static void SelectQuery2()
{
string sql = "select TestCrudId,TestCrudName from TestCrud where TestCrudId>@TestCrudId";
sql += ";" + "select top 3 TestCrudId,TestCrudName from TestCrud where TestCrudId>@TestCrudId";
SqlParameter[] parameter = new SqlParameter[] { new SqlParameter("@TestCrudId", 1000) };
SqlDataReader reader = SQLHelper.ExecteReader(sql, parameter);
while (reader.Read())
{
//可以在这个地方封装到对象属性中
Console.WriteLine(reader["TestCrudId"] + "\t" + reader["TestCrudName"]);
}
Console.WriteLine("----------------TOP3-----------------");
if (reader.NextResult())//判断下一个结果集是否存在,避免下一个结果集是空
{
while (reader.Read())
{
Console.WriteLine(reader["TestCrudId"] + "\t" + reader["TestCrudName"]);
}
}
//NextResult()当读取批处理 Transact-SQL 语句的结果时,使数据读取器前进到下一个结果。
reader.Close();
}
- 当还有其他结果集时,以此类推即可
if (reader.NextResult())//判断下一个结果集是否存在,避免下一个结果集是空
{
while (reader.Read()) //NextResult()当读取批处理 Transact-SQL 语句的结果时,使数据读取器前进到下一个结果。
{
//可以在这个地方封装到对象属性中
}
}
--------以此类推
3.当返回的只是包含输入输出的结果集时
- SQLServer代码如下所示
----------------------带有输入输出参数的存储过程----------------------
if exists(select*from sysobjects where name='usp_SelectProductNET5')
drop procedure usp_SelectProductNET5
go
create procedure usp_SelectProductNET5
@TestCrudId int,
@TestCount int output
as
select TestCrudId,TestCrudName from TestCrud where TestCrudId>@TestCrudId
select top 3 TestCrudId,TestCrudName from TestCrud where TestCrudId>@TestCrudId
select @TestCount=count(*)from TestCrud
go
- C#代码如下所示
获取输出参数需要注意:在使用输出参数的时候,必须要把读取器关闭才行,否则得不到
private static void SelectQuery3()
{
//创建输出参数
SqlParameter outParam = new SqlParameter("@TestCount", SqlDbType.Int);//设置输出参数的数据类型及长度
outParam.Direction = ParameterDirection.Output;//表示这个值时输出参数
string str = "usp_SelectProductNET5";
SqlParameter[] parameter = new SqlParameter[]
{
new SqlParameter("@TestCrudId", 1000),
outParam
};
SqlDataReader reader = SQLHelper.ExecteReader(str, parameter, true);
while (reader.Read())
{
//可以在这个地方封装到对象属性中
Console.WriteLine(reader["TestCrudId"] + "\t" + reader["TestCrudName"]);
}
Console.WriteLine("----------------TOP3-----------------");
if (reader.NextResult())//判断下一个结果集是否存在,避免下一个结果集是空
{
while (reader.Read())
{
Console.WriteLine(reader["TestCrudId"] + "\t" + reader["TestCrudName"]);
}
}
reader.Close();
//获取输出参数需要注意:在使用输出参数的时候,必须要把读取器关闭才行,否则得不到
Console.WriteLine("----------------总数-----------------");
int count = (int)outParam.Value;
Console.WriteLine(count.ToString());
}
7.返回单行单列的查询
-
当有时候查询只需要得到某一行某一列的时候可以使用 SqlCommand的ExecuteScalar 方法可以用以下方式进行查询
封装调用代码如下
public static object ExecuteScalar(string cmdText, SqlParameter[] param = null, bool isProcedure = false) { //创建连接对象 SqlConnection conn = new SqlConnection(connString); //创建一个命令执行对象 SqlCommand cmd = new SqlCommand(cmdText, conn); try { //添加参数 if (param != null) { cmd.Parameters.AddRange(param); } //判断是否为存储过程 if (isProcedure) { cmd.CommandType = CommandType.StoredProcedure;//这个枚举的设置表示当前cmdText是存储过程名称 } conn.Open(); // 执行查询,并返回由查询返回的结果集中的第一行的第一列。 其他列或行将被忽略 return cmd.ExecuteScalar(); } catch (Exception ex) { string errorString = "调用ExecuteScalar方法发生异常:" + ex.Message; throw new Exception(errorString); } finally { conn.Close();//关闭连接 } }
*调用时:
1.返回一行参数
public static void Query()
{
string str = "select count(*) from TestCrud where TestCrudId> @TestCrudId";
SqlParameter []sqlParameter = new SqlParameter[]
{
new SqlParameter ("@TestCrudId",1015)
};
int result = (int)SQLHelper.ExecuteScalar(str, sqlParameter);
Console.WriteLine("查询到的结果为:" + result.ToString());
}
2.插入一行并查询的方法
- @@identity全局变量,在C#中直接使用的时候查询不到,需要紧跟一条SQL语句
public static void Query2()
{
string str = "insert into Testchao(chaoName)values (@chaoName)";
str += ";"+"select @@identity";
SqlParameter[] sqlParameter = new SqlParameter[]
{
new SqlParameter ("@chaoName","小蜜蜂")
};
int result =Convert.ToInt32( SQLHelper.ExecuteScalar(str, sqlParameter));
Console.WriteLine("最大的ID值为:" + result);
}
8.使用DataSet储存查询结果的查询
1.针对单张数据表的查询
- 当需要单个值或者单行单列进行查询时,可以使用以下方法
这种方式时通过数据适配器对象SqlDataAdapter一次性从数据读取到客户端的缓存区DataSet里,关闭数据库,然后再进行处理
封装代码如下所示:
/// 使用DataSet储存查询结果的查询(针对一张数据表的查询)
/// </summary>
/// <param name="cmdText"></param>
/// <param name="tableName"></param>
/// <param name="param"></param>
/// <param name="isProcedure"></param>
/// <returns></returns>
public static DataSet GetDataSet(string cmdText, string tableName = null, SqlParameter[] param = null, bool isProcedure = false)
{
//创建连接对象
SqlConnection conn = new SqlConnection(connString);
//创建一个命令执行对象
SqlCommand cmd = new SqlCommand(cmdText, conn);
try
{
//添加参数
if (param != null)
{
cmd.Parameters.AddRange(param);
}
//判断是否为存储过程
if (isProcedure)
{
cmd.CommandType = CommandType.StoredProcedure;//这个枚举的设置表示当前cmdText是存储过程名称
}
conn.Open();
//创建一个数据适配器
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataSet data = new DataSet();
//判断表名称是否存在
if (tableName != null)
{
//在DataSet 中添加或刷新行以匹配使用DataSet 和 DataTable名称的数据源中的行。
adapter.Fill(data, tableName);
}
else
{
// 在DataSet 的指定范围中添加或刷新行,以与使用 DataTable 名称的数据源中的行匹配。
//DataSet里可以包含若干个数据表
//在使用时可以使用数据表名称去区分数据表,如果不以名称去区分,那么数据表则以1,2,3,4,5,6去排列
adapter.Fill(data);
}
return data;
}
catch (Exception ex)
{
string errorString = "调用GetDataSet方法发生异常:" + ex.Message;
throw new Exception(errorString);
}
finally
{
conn.Close();//关闭连接
}
}
DateSet: 标示数据在内存中的缓存,在使用时,因为DataSet里可以包含若干个数据表,所以索引时可以使用数据表的名称去区分,否则数据便将自动去以数字排列
SqlDataAdapter:表示用于填充 DataSet 和更新 SQL Server 数据库的一组数据命令和一个数据库连接.
- 调用时如下所示:
public static void SqlDataSet()
{
string sql = "select TestValueId, TestValueName, TestValueIdNo from TestValue where TestValueId>10002";
DataSet ds = SQLHelper.GetDataSet(sql);
foreach (DataRow item in ds.Tables[0].Rows)//通过第0行索引去遍历
{
//通过每一列的列名去遍历
Console.WriteLine(item["TestValueId"]+"\t"+item["TestValueName"]+"\t"+item["TestValueName"]);
}
}
可以打断点观察到DataSet数据里的表
- 运行结果
2.针对多张数据表的查询
- 当遇到多张数据表的情况可以使用以下方式进行查询
封装代码如下所示
/// <summary>
/// 使用DataSet储存查询结果的查询(针对多张数据表的查询)
/// </summary>
/// <param name="sqlDataname"></param>
/// <returns></returns>
public static DataSet GetDataSet(Dictionary<string, string> sqlDataname)
{
//创建连接对象
SqlConnection conn = new SqlConnection(connString);
//创建一个命令执行对象
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
try
{
conn.Open();
foreach (string tableName in sqlDataname.Keys)
{
cmd.CommandText = sqlDataname[tableName];
adapter.Fill(ds,tableName);
}
return ds;
}
catch (Exception ex)
{
string errorString = "调用GetDataSet方法发生异常:" + ex.Message;
throw new Exception(errorString);
}
finally
{
conn.Close();//关闭连接
}
}
- 调用
public static void SqlDataSet2()
{
string sql1 = "select TestValueId, TestValueName, TestValueIdNo from TestValue where TestValueId>10002";
string sql2 = "select TestCrudId, TestCrudName, TestCrudNub from TestCrud";
Dictionary<string, string> sqlDataname = new Dictionary<string, string>()
{
["TestValue"] = sql1,
["TestCrud"] = sql2,
};
DataSet ds = SQLHelper.GetDataSet(sqlDataname);
//实际开发中使用Dataset做数据源,做数据展示会非常容易
Console.WriteLine("-------------DataSet表中的名称------------");
foreach (DataTable dataTable in ds.Tables)
{
Console.WriteLine(dataTable.TableName);
}
Console.WriteLine("-------------TestValue表中的参数------------");
//遍历表中的行
foreach (DataRow item in ds.Tables["TestValue"].Rows)
{
//通过列名称找到对应的值
Console.WriteLine(item["TestValueId"] + "\t" + item["TestValueName"] + "\t" + item["TestValueName"]);
}
Console.WriteLine("-------------TestValue表中的列名称------------");
foreach (DataColumn dataColumn in ds.Tables["TestValue"].Columns)
{
Console.WriteLine(dataColumn.ColumnName);
}
Console.WriteLine("-------------TestValue表中的第6行的TestValueName的内容------------");
Console.WriteLine("6行的TestValueName的内容:" + ds.Tables["TestValue"].Rows[6]["TestValueName"]);
//下面这条和上面效果相同,一索引的方式去查找,上面的是以固定名称去查找,本质上是一样的
Console.WriteLine("6行的TestValueName的内容:" + ds.Tables["TestValue"].Rows[6][1]);
}
多张表查询和单张表类似,都是先拿到表的索引,行,列然后去输出参数
3.总结对比
1.DataAdpAter使用和Datareader对比
【1】相同点:都可以做数据查询
【2】不同点:
(1):DataAdpater和DataSet还可以实现增,删,改操作(因为不好控制所以基本不用)Datareader只读向前的(DataAdpater不仅可以查询,还可以把数据反向回来,Datareader不可以往数据段发送数据)。
(2):DataSet是保留搭配客户端的,是大量占用内存的,DataAdpater是轻量级的。
(3):DataSet因为是以表的形式表示数据,所以没有良好的OOP特性(面向对象特性),DataAdpater更适用于与面向对象的封装相结合
(4):DataSet在C/S开发中,可以单独的查询使用,适当使用。Web中不能使用。
DataAdpater在任何开发中都能使用
9.ADO.NET中使用事务
在前面的笔记中演示了如何在SQLServer中使用事务来保证的代码写入的安全性,那么在C#中去添加事务的作用也是一样的,保证在添加数据时因为某一句报错但是其他是正确的而添加进了不完整的参数进去
1).单个表事务
- 首先封装SQL语句
/// <summary>
/// 启用事务执行多条语句
/// </summary>
/// <param name="sqlList">sql语句列表</param>
/// <returns>事务是否执行成功</returns>
public static bool ExectueNoQueryByTran(List<string>sqlList)
{
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
try
{
conn.Open();
cmd.Transaction = conn.BeginTransaction();//开启数据库事务
foreach (string sql in sqlList)
{
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
cmd.Transaction.Commit();//提交事务
return true;
}
catch (Exception ex)
{
if (cmd.Transaction!=null)
{
cmd.Transaction.Rollback();//回滚事务
}
string msg = "调用ExectueNoQueryByTran方法出错" + ex.Message;
throw new Exception(msg);
}
finally
{
if (cmd.Transaction!=null)
{
cmd.Transaction = null;//清空事务
}
conn.Close();
}
}
- 调用
举一个简单的例子来调用事务,如果调用成功就会返回“True”否则有任意一条SQL语句发生错误,则会返回False,并且其他语句也不会被执行
public static void Execute()
{
string sql1= "insert into TestValue(TestValueName, TestValuePar, DataTestValue, TestValueIdNo, TeatValueNumber, TeatValuePos, TestMarstId)values('长胡子', 'OK', '2022-02-03', '300001', '14', '正常', 2)";
string sql2 = "insert into TestValue(TestValueName, TestValuePar, DataTestValue, TestValueIdNo, TeatValueNumber, TeatValuePos, TestMarstId)values('长胡子', 'OK', '2022-02-03', '300002', '14', '正常', 2)";
string sql3 = "insert into TestValue(TestValueName, TestValuePar, DataTestValue, TestValueIdNo, TeatValueNumber, TeatValuePos, TestMarstId)values('长胡子', 'OK', '2022-02-03', '300003', '14', '正常', 2)";
List<string> sqlList = new List<string> { sql1, sql2, sql3 };
try
{
bool result = SQLHelper.ExectueNoQueryByTran(sqlList);
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
- 上面代码因为插入重复键而不会被执行
2)基于主表和明细表带参数的SQL语句来调用事务
- 当想要多个表进行事务+回滚的情况,例如一张主表+多张子表进行写入数值
代码封装如下
/// <summary>
/// 启用事务提交多条带参数的SQL语句
/// </summary>
/// <param name="mainSql">主表sql语句</param>
/// <param name="mainParam">主表参数</param>
/// <param name="detailSql">明细表Sql语句</param>
/// <param name="sqlList"><明细表参数/param>
/// <returns>返回执行结果</returns>
public static bool ExectueNoQueryByTran(string mainSql,SqlParameter[]mainParam,string detailSql, List<SqlParameter[]> detailParam)
{
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
try
{
conn.Open();
cmd.Transaction = conn.BeginTransaction();//开启数据库事务
//执行主表操作
if (mainSql!=null&&mainSql.Length!=0)//先判断主表是否为空
{
cmd.CommandText = mainSql;
cmd.Parameters.AddRange(mainParam);
cmd.ExecuteNonQuery();
}
//循环执行明细表操作
cmd.CommandText = detailSql;
foreach (SqlParameter[] param in detailParam)
{
cmd.Parameters.Clear();//必须要先清除前面添加过的参数
cmd.Parameters.AddRange(param);
cmd.ExecuteNonQuery();
}
cmd.Transaction.Commit();//提交事务
return true;
}
catch (Exception ex)
{
if (cmd.Transaction != null)
{
cmd.Transaction.Rollback();//回滚事务
}
string msg = "调用ExectueNoQueryByTran(主表+明细表)方法出错" + ex.Message;
throw new Exception(msg);
}
finally
{
if (cmd.Transaction != null)
{
cmd.Transaction = null;//清空事务
}
conn.Close();
}
}
- 调用时和单张表相同,如果报错就会返回false
//基于主表和明细表带参数的SQL语句来调用事务
public static void Exectute2()
{
//主表SQL语句
string sqlMain = "insert into TestValue(TestValueName, TestValuePar, DataTestValue, TestValueIdNo, TeatValueNumber, TeatValuePos, TestMarstId)";
sqlMain += "values(@TestValueName,@TestValuePar,@DataTestValue, @TestValueIdNo, @TeatValueNumber,@TeatValuePos,@TestMarstId)";
//明细表SQL语句
string sqldeta = "insert into TestList(TestValueId)";
sqldeta += "values(@TestValueId)";
SqlParameter[] mianparam = new SqlParameter[]
{
new SqlParameter("@TestValueName","胡子"),
new SqlParameter("@TestValuePar","OK"),
new SqlParameter("@DataTestValue","2202-03-03"),
new SqlParameter("@TestValueIdNo","300011"),
new SqlParameter("@TeatValueNumber",14),
new SqlParameter("@TeatValuePos","非正常"),
new SqlParameter("@TestMarstId",2)
};
SqlParameter[] paremeter1 = new SqlParameter[]
{
new SqlParameter("@TestValueId",10005)
};
SqlParameter[] paremeter2 = new SqlParameter[]
{
new SqlParameter("@TestValueId",10089)
};
List<SqlParameter[]> detailParam = new List<SqlParameter[]> { paremeter1, paremeter2 };
try
{
bool result = SQLHelper.ExectueNoQueryByTran(sqlMain, mianparam, sqldeta, detailParam);
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}