C#与数据库的连接,找了半天,不如这个看一遍。
链接 :c#连接数据库
这里面有C#连接数据库的基本增删改查语法,
下面会有我的练习代码和我自己的一些理解。
因为我只会学我需要的和我感兴趣的东西,所以这里只有SqlServer的连接,其他的数据库我暂时用不上。
按照原文,我的理解是 :
操作数据库,最重要的对象有两个
SqlConnection 和 SqlCommand
正如其名字一样,
SqlConnection用来连接数据库,
SqlCommand用来执行Sql语句。
而SqlCommand是通过SqlConnection来得到的,
这很合理,只有连接上了数据库,才能谈得上执行Sql语句。
下面贴一段代码 :
private static void Insert()
{
// 连接字符串
string connString =
"Data Source = localhost; database = Test; User ID=sa;Password=";
//根据连接字符串创建连接对象
SqlConnection conn = new SqlConnection(connString);
//通过连接对象获得命令对象
SqlCommand cmd = conn.CreateCommand();
//这是要执行的Sql语句
string sql =
"insert into t_student(name, age, gender) values('张三', 18, 'm')";
//将Sql语句装入命令对象
cmd.CommandText = sql;
//打开连接
conn.Open();
//执行Sql
cmd.ExecuteNonQuery();
//关闭连接
conn.Close();
}
}
这是一个最简单的数据库操作,没有做判断和结果反馈
整个流程还是比较清晰的 :
1. 创建连接对象
2. 获得命令对象
3. 设置Sql语句
4. 打开连接
5. 执行Sql
6. 关闭连接
连接字符串中包括了连接数据库所需要的信息,SqlConnection (连接对象) 可以根据连接字符串中的信息来连接数据库。
SqlCommand(命令对象)的 CommandText 属性就是要执行的Sql语句
(也可以通过设置CommandType属性来用其他的方式解释CommandText )
SqlCommand(命令对象)提供的方法有很多,主要说这俩
ExecuteNonQuery() :
执行 CommandText 中的Sql语句并返回受影响的行数,如果返回0,说明该执行没有任何效果
和他的名字一样,用来执行非查询操作(增、删、改)
ExecuteReader() :
这个方法会返回一个 SqlDataReader 对象,此对象是一个连接的、单向的、只读的阅读器。
简单来说:
- 若使用 SqlDataReader,需要其关联的 SqlConnection 处于 Open() 状态。
- SqlDataReader 内部维护着一个指针,这个指针指向某一行数据,指针只能向前移动,不能后退。
- SqlDataReader 只能读取数据,不能增加、修改、删除数据。
ExecuteNonQuery() 很简单,调一下就执行了,所以介绍一下 SqlDataReader 对象
调用 SqlCommand 对象的 ExecuteReader() 方法将返回一个SqlDataReader对象,
该对象以迭代的方式来按行读取 CommandText 返回的结果集。
SqlDataReader 对象提供的方法属性也是一大堆,下面几个是我认为比较常用的几个
HasRows:
该属性指示 SqlDataReader 是否包含一行或多行。
它只表示结果集是否为空,与指针指向无关
Read():
Read() 方法会使 SqlDataReader 的指针前进到下一条记录,如果存在多个行返回 true,否则返回 false。
若未调用过 Read() 方法,则 SqlDataReader 没有数据
第一次调用 Read() 会使指针指向第一行,随后的调用使指针指向下一行。
Read() 方法会返回一个 bool 值,可以用这个 bool 作为循环条件来完成遍历。
Read() 的指针移动是不可逆的,也就是说,读了第二行,就别想再读第一行
FieldCount:
该属性表示当前行中的列数。
这个属性也可以作为循环条件,来遍历当前行的每一列
this[int i]:
把 SqlDataReader 对象当成数组来用,返回当前指向行中,下标为 i 的字段的值。
下标从 0 开始,若下标小于0或超出最大返回,会发生下标越界异常。
getValue(int i):
同上…
getName(int i):
与上面两个方法类似,不过返回的不是字段的值,而是字段的名字。
这个方法可以用来获得表头。
this[string name]:
把 SqlDataReader 对象当成 HashMap Dictionary 来用,返回当前指向行中,指定字段名的值。
如果找不到指定的字段名,会抛异常,异常的 Message 就是指定名。
有了这几个方法和属性,就可以对结果集进行处理了
public static void PrintTable()
{
string connString = "server = localhost; initial catalog = Test;User ID=sa;Password=;";
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "select * from t_student";
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read()) //读一下试试,如果有数据则继续
{
//打印标题
for (int i = 0; i < reader.FieldCount; i++)
{
Console.Write(reader.GetName(i) + "\t");
}
Console.Write("\n");
//打印每一行的值
do
{
for (int i = 0; i < reader.FieldCount; i++)
{
Console.Write(reader[i] + "\t");
}
Console.Write("\n");
} while (reader.Read());
}
else
{
Console.WriteLine("无数据");
}
reader.Close();
conn.Close();
}
这个代码就是对结果集的简单处理,
这里是选择打印到控制台,可以替换成其他的处理方式
如制成表格、Excel、JSON等。
要注意的是,SqlDataReader 和 SqlConnection 一样是需要手动释放资源,用完之后要调用 Close() 方法。
SqlDataReader 会有一个相应的连接,SqlDataReader 依赖并占用着这个连接。
若连接没有打开或已关闭时使用SqlDataReader,会抛出异常,异常的 Message 为 “阅读器关闭时尝试调用 xxx 无效。”
SqlDataReader 不仅依赖连接,还会占用连接,所以若是在 DataReader 未关闭时调用 SqlConnection的某些方法也会抛出异常,异常的 Message 为 “已有打开的与此 Command 相关联的 DataReader,必须首先将它关闭。”
还有一些可以提高效率的细节,可以参考 : 使用SqlDataReader和SqlDataAdapter的注意
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
有些时候Sql语句需要参数,这时可以在Sql语句中留下一个占位符,然后设置 SqlCommand 对象的 Parameters属性
//查询指定ID的数据
string sql = "select * from t_student where id = @id";
sqlCommand.CommandText = sql;
SqlParameter para = new SqlParameter("@id", SqlDbType.Int); //创建一个Sql参数对象
para.Value = 3; //设置值
sqlCommand.Parameters.Add(para); //将para追加入SqlCommand的参数集合
代码中的Sql语句多了一个 where 语句 “id = @id”,后面的那个”@id” 就是占位符,
在Sql语句的下面创建了一个 SqlParameter 对象,在创建该对象时传入了2个值 : (“@id” , SqlDbType.Int),
分别是该参数对应的占位符和该参数在数据库中的类型,然后还需要单独设置该参数的实际值。
最后将准备好的 SqlParameter 对象追加到 SqlCommand 的参数集合中。
这样,当SqlCommand 执行时,就会把占位符替换成相应的值。
需要注意,在Sql语句中的占位符必须要以”@”开头,而在初始化 SqlParameter 时可以省略”@”。
SqlParameter 的实际值还可以通过这种方式设置 :
sqlCommand.Parameters.Add( new SqlParameter("id", SqlDbType.Int)); //先将属性追加入集合
sqlCommand.Parameters["id"].Value = 3; //再设置实际值
参数集合提供的访问器还是很方便的。
如果Sql语句中有占位符,而 Parameters 中没有找到对应的参数,会抛出异常,异常的Message为 “必须声明标量变量 xxx”。
如果Sql语句中有占位符, Parameters 中能够找到对应的参数,但该参数的 Value 未设置过,也会抛出异常,异常的Message为 “参数化查询 ‘xxxxxxxxxx’ 需要参数 ‘xxx’,但未提供该参数。”。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
除了 SqlCommand 之外,还可以使用 SqlDataAdapter 来与数据库进行交互。
SqlDataAdapter 可以把数据库的数据放入到一个叫做 DataTable 的对象里面,或是把 DataTable 中的修改同步到数据库中。
听起来很神,但实际上还是基于 SqlCommand 来操作的。
private static void DataAdapterTest()
{
//先创建一个查询用的 SqlCommand 对象
string connStr = "Server = localhost; Database = Test; UID = sa; PWD = ;";
string sql = "select * from t_student";
SqlCommand selectCmd = new SqlCommand(sql, new SqlConnection(connStr));
SqlDataAdapter adapter = new SqlDataAdapter(); //创建 SqlDataAdapter 对象
adapter.SelectCommand = selectCmd; //设置查询用的命令对象
DataTable table = new DataTable(); //创建一个用来存数据的 DataTable 对象
adapter.Fill(table); //执行查询命令,并将结果存入 DataTable
//输出 DataTable 中的数据
printDataTable(table);
}
SqlDataAdapter 在执行Sql时会保持连接的状态,即:
若执行Sql前连接为关闭状态,SqlDataAdapter 会先打开连接并执行Sql,然后关闭连接;
若执行Sql前为连接状态,直接执行Sql,不会关闭连接。
SqlDataAdapter 可以执行查询命令并将查询结果保存到 DataTable 中,或将 DataTable 中的修改同步到数据库中。
SqlDataAdapter 的初衷就是减少与数据库连接的时间,以降低对连接资源的占用 :
打开连接
-执行查询命令并将结果集存入DataTable
关闭连接
-对数据进行各式各样的操作
打开连接
-将 DataTable 中的修改同步至数据库
关闭连接
Fill() 方法会将查询命令获得的结果集填充入 DataTable 对象中,可以通过 DataTable 来操作这些数据。
简单介绍一下 DataTable 对象:
DataTable 是一个用来储存数据的对象,它就像一张表一样由列与行组成,可以对任意行任意列进行访问,要比SqlDataReader 灵活。
DataTable 包含了若干个 DataColumn(列)与若干个 DataRow(行),
每一个 DataColumn 代表了结构上的一个字段,每一个 DataRow 代表了结果集中的一条记录。
DataColumn 代表的只是结构,可以通过它获得字段名、类型等,并不包含实际值;
DataRow 代表的是结果集中的某条记录,每条记录有N个值,N为字段数量。
DataRow 实现了访问器,可以通过传入下标、字段名或 DataColumn 对象来获取某个字段的值。
可以通过 DataTable 的 Columns 属性与 Rows 属性获得列集合与行集合,
这两个集合也实现了访问器,可以方便的获取列和行。
看代码能更直观一些:
private static void PrintDataTable(DataTable table)
{
DataColumnCollection columns = table.Columns; // table 的字段结构
DataRowCollection rows = table.Rows; //table 中数据的集合
//打印表头
for (int i = 0; i < columns.Count; i++)
Console.Write("{0, -10}", columns[i].ColumnName);
Console.WriteLine("\n");
//打印数据
for (int j = 0; j < rows.Count; j++)
{
for (int i = 0; i < columns.Count; i++)
Console.Write("{0, -10}", rows[j][i]);
Console.WriteLine("\n");
}
}
上面那操作方式还是比较简单的,
但有时需要对数据进行比较复杂的操作,如排序和筛选,可以这样:
private static void PrintDataTable2(DataTable table, string name)
{
DataView view = table.DefaultView;
view.RowFilter = "name = '" + name + "'";
view.Sort = "id DESC";
PrintDataTable(view.ToTable());
}
首先通过 DataTable 的 DefaultView 属性来获得一个 DataView 对象,
DataView 是表示用于排序、筛选、搜索、编辑和导航的 DataTable 的可绑定数据的自定义视图。
DataView 不存储数据,而是表示其对应的 DataTable 的已连接视图。
对 DataView 的数据的更改将影响 DataTable。
对 DataTable 的数据的更改将影响与之关联的所有 DataView。
简单来说,DataTable 用来储存数据,DataView 是展示数据的一个窗口,
可以通过设置来让 DataView 以特定的形式展示特定的数据,
上面的代码里有两个设置:
- view.RowFilter —— 展示满足该条件的数据
- view.Sort —— 将数据按照指定字段来排序 ASC 为升序,DESC 为降序
RowFilter 可以当做Sql中的 where 语句,而 Sort 可以当做Sql中的 order by语句来用。
–
还可以调用 Find 方法和 FindRows 方法来查找指定行。
这两个方法依赖于 DataView 的排序,也就是说想要使用它们,DataView 的 Sort 属性要有值,而调用这两个方法时需要把排序关键字作为参数传入,查询关键字要与排序字段的类型匹配。
Find 方法会返回找到的第一条记录的下标,若找不到则返回-1;
FindRows 方法以 DataRowView 对象数组的方式返回查找结果,找到多少返回多少,找不到的话数组长度为0;
Find:
private static void FindTable(DataTable table)
{
DataView view = table.DefaultView;
view.Sort = "name ASC";
int index = view.Find( "李四"); //name字段为字符串, 这里就必须传入字符串
//打印查找结果
if (index >= 0)
PrintRows(view.ToTable().Rows[index]);
}
FindRows :
private static void FindTable2(DataTable table)
{
DataView view = table.DefaultView;
view.Sort = "name ASC";
DataRowView[] rows = view.FindRows("一个男人");
PrintRows(rows); //
}
FindRows 返回的是 DataRowView 数组,
DataRowView 与 DataRow 的关系就像 DataView 与 DataTable 的关系,
DataRowView 可以直接用访问器来获取值,也可以通过 Row 属性来获得其对应的 DataRow 对象。
–
DataView 也有访问器,它的访问器接收一个下标,返回对应下标的记录。
可用通过 Count 属性来获得 DataView 的记录数量。
SqlDataAdapter 的 Update 方法可以将 DataTable 中的改动同步到数据库中。
想要使用 SqlDataAdapter 的Update 方法,需要设置命令对象(SqlCommand),
- InsertCommand
- UpdateCommand
- DeleteCommand
加上 SelectCommand 增删改查各对应一个 SqlCommand 对象,所以说SqlDataAdapter 的本质就是 SqlCommand 。
这三个命令对象可以通过 SqlDataAdapter 对应的属性来手动设置,也可以使用 SqlCommandBuilder 来自动设置。
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
SqlCommandBuilder 很方便,但要是用它有两个要求:
- DataTable 映射到单个数据库表或从单个数据库表生成。
- 必须设置 SelectCommand ,SelectCommand 所检索的表结构是生成其他命令对象的依据。
这些都设置好之后,调用 Update 方法时,SqlDataAdapter 会通过 DataRow 的 RowState 属性来判断哪些数据需要更新,并将对应版本的值作为参数,调用相应的命令对象。(关于 RowState 与 DataRowVersion(版本) 写于下方的附)。
代码 :
private static void DataAdapterTest2()
{
SqlConnection conn = new SqlConnection("Server = localhost; Database = Test; UID = sa; PWD =;");
SqlDataAdapter adapter = new SqlDataAdapter("select * from t_student", conn); //构造器中的Sql语句会被当做查询命令的Sql语句
conn.Open(); //手动打开连接,以避免连接频繁自动开关
DataTable table = new DataTable();
adapter.Fill(table);
SqlCommandBuilder builder = new SqlCommandBuilder(adapter); //SqlCommandBuilder 会自动为 adapter 设置命令对象
Console.WriteLine("------------增加一条数据---------------");
DataRow row = table.NewRow(); //根据 table 的结构创建一条新的记录
row["ID"] = 16;
row["name"] = "赵六";
row["age"] = 30;
row["gender"] = 'm';
table.Rows.Add(row);
adapter.Update(table); //只有执行了 Update 方法,才会将数据同步到数据库
Console.WriteLine("--------将所有赵六年龄改成50岁-----------");
foreach (DataRow r in table.Select("name = '赵六'"))
r["age"] = 50;
adapter.Update(table);
Console.WriteLine("--------删除所有名字叫赵六记录-----------");
foreach (DataRow r in table.Select("name = '赵六'"))
r.Delete();
adapter.Update(table);
conn.Close();
}
对于 DataTable 的操作只有在调用了 SqlDataAdapter 的 Update 方法后才会被同步到数据库。
SqlDataAdapter 将数据保存到内存,有修改之后再将内存中的数据同步到数据库,这样可以降低数据库的压力。
想法是很好的,但这种方式肯定会有线程安全问题,所以还要用事务之类各种各样的方式来管理,导致最后的效果就不一定很理想了。
下面这段代码和上面的基本一样,不过数据库的主键为自增长,所以就出现问题了:
private static void DataAdapterTest2()
{
SqlConnection conn = new SqlConnection("Server = localhost; Database = Test; UID = sa; PWD =;");
SqlDataAdapter adapter = new SqlDataAdapter("select * from t_student", conn); //构造器中的Sql语句会被当做查询命令的Sql语句
conn.Open(); //手动打开连接,以避免连接频繁自动开关
DataTable table = new DataTable();
adapter.Fill(table);
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
Console.WriteLine("------------增加一条数据---------------");
DataRow row = table.NewRow(); //根据 table 的结构创建一条新的记录
//row["ID"] = 16; 数据库的ID 为自增长,所以这里是否设置ID都没影响
row["name"] = "赵六";
row["age"] = 30;
row["gender"] = 'm';
table.Rows.Add(row);
adapter.Update(table);
table.Clear(); //因为数据库的ID是自增长的,所以需要在插入新语句后更新 DataTable
adapter.Fill(table); //否则下面的修改和删除会报异常
Console.WriteLine("--------将所有赵六年龄改成50岁-----------");
foreach (DataRow r in table.Select("name = '赵六'"))
r["age"] = 50;
adapter.Update(table);
Console.WriteLine("--------删除所有名字叫赵六记录-----------");
foreach (DataRow r in table.Select("name = '赵六'"))
r.Delete();
adapter.Update(table);
conn.Close();
}
数据库的ID是自增长的,所以我在插入新行时的ID数据库不会接收,数据库会自动生成一个ID,这就导致了 DataTable 中的新行的ID 与数据库中不一致,然后就报异常,所以要把 DataTable 中新行的ID 更新成数据库中实际的ID。
通常这种情况会用一个小技巧:
cmd.CommandText = "insert into t_student value(null, '如花', 18, 'm'); select @@Identity";
var id = cmd.ExecuteScalar();
SqlCommand 对象的 ExecuteScalar 方法会返回结果集中的第一行的第一列,
“@@Identity” 是 SqlServer 中的一个系统全局变量,它记录了最近的自动增长值。
这样就可以得到上一条自增长的ID。
但是!SqlDataAdapter 没有这样的操作,若想要更新数据库就要调用 Update 方法,而 Update 方法只会返回受影响的行数,这就很难受,所以上面的代码中我手动 Fill 了 DataTable。
可这种手动刷新 DataTable 会消耗数据库的资源。
所以也可以改成这样:
private static void DataAdapterTest2()
{
SqlConnection conn = new SqlConnection("Server = localhost; Database = Test; UID = sa; PWD =;");
SqlDataAdapter adapter = new SqlDataAdapter("select * from t_student", conn);
DataTable table = new DataTable();
conn.Open();
adapter.Fill(table);
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
Console.WriteLine("------------要加了---------------");
adapter.InsertCommand = builder.GetInsertCommand(true);
adapter.InsertCommand.CommandText += ";select @@Identity";
DataRow row = table.NewRow();
//row["ID"] = 0; //数据库的ID 为自增长,所以这里是否设置ID都没影响
row["name"] = "赵六";
row["age"] = 30;
row["gender"] = 'm';
table.Rows.Add(row);
adapter.InsertCommand.Parameters["@name"].Value = row["name"];
adapter.InsertCommand.Parameters["@age"].Value = row["age"];
adapter.InsertCommand.Parameters["@gender"].Value = row["gender"];
int id = Int32.Parse(adapter.InsertCommand.ExecuteScalar().ToString());
row["ID"] = id;
row.AcceptChanges();
conn.Close();
}
这就是不使用 Update 方法,手动更新数据库与维护 DataTable。。。
苦逼啊~~
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
附:
连接字符串 :
"Data Source = localhost; database = Test; User ID=sa;Password=";
这是一个字符串(废话),但应该会被解析成 key = value 的形式
key 是属性名, value 是具体值
也就是说,上面的连接字符串可以看做以下形式
Data Source = localhost (连接地址 = 本地)
database = Test (数据库名 = Test)
User ID=sa (用户ID = sa)
Password = (密码 = 无)
这应该就比较清楚了,连接字符串中的都是数据库所需要的信息
如果要连接外面的数据库,
则应是
Data Source = xxx.xxx.x.xxx,yyyy(要连接的地址 , 端口号)
database = xxxx (要连接的数据库的名字)
User ID=xxxx
Password = xxxx
即
"Data Source = xxx.xxx.x.xxx,yyyy; database = xxxx ; User ID=xxxx ;Password=xxxx"
顺便一说,连接字符串还是比较灵活的,它不区分大小写,而且对顺序没有要求,还有一些可以代替的关键字。
比如 :
"Data Source = localhost; database = Test; User ID=sa; Password="
等价于
"server = localhost; initial catalog = Test; uid=sa; pwd=;"
这四个是最基本的属性,数据库的连接字符串还有一些乱七八糟的属性,因为我用不上,所以这就不写了,想要了解的话可以自己查
SQL Server 2008连接字符串写法大全
C# 连接SQL数据库 常用连接字符串
连接 :
调用SqlConnection对象的 Open() 方法就会去尝试连接数据库,成功的话就打开了一条连接,
这条连接在释放之前会一直保持,期间可以多次访问数据库
比如这样 :
//建立连接,并获得SqlCommand对象
string connString = "Data Source = localhost; database = Test; uid = sa; pwd=";
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = conn.CreateCommand();
conn.Open();
//插入一条数据
cmd.CommandText = "insert into t_student(name, age, gender) values('李四', 18, 'm')";
cmd.ExecuteNonQuery();
//然后查一查
cmd.CommandText = "select * from t_student";
SqlDataReader reader = cmd.ExecuteReader();
printReader(reader); //将查询到的数据输出到控制台
reader.Close();
conn.Close();
这段代码执行了两次数据库操作,先是插入了一条新数据,然后查询数据库。
持续保持的连接很方便,但也会占用资源,所以用完后要调用SqlConnection对象的 Close() 方法释放资源。
SqlConnection对象与数据库的连接从 Open() 开始,直到 Close() 断开。
在连接其间,可以执行多次Sql语句,但一个连接指向一个数据库,若想要访问其他的数据库,只能另开连接。
这就像打电话一样,直到挂电话之前可以一直说,但你的通话对象一直是同一部手机。
顺便一提,频繁的开关连接会消耗大量的资源,所以应该尽量避免。
RowFilter :
RowFilter 整体的结果是一个布尔值,可以用AND,OR,NOT来连接成一个较短的表达式,也可以使用圆括号来组成子句,指定优先的运算。
通常是把某一字段同字母、数字、日期或另一字段进行比较。这里,可以使用关系运算符和算术运算符,如>=, <, >, +, *, % (取模)等等。
如果要选取的行并不能方便地通过算术或逻辑运算符表达,你可以使用IN操作符。
以下代码选择年龄为10,20,30的行:
view.RowFilter = "age IN (10, 20, 30)"
也可以使用通配符*和%,它们同LIKE运算符一起使用时显得更有用。它们都表示任意数量的字符,可以相互替代使用。
选择姓张的记录:
view.RowFilter = "name LIKE '张*'"
通配符只允许在过滤字符串的开头或结尾处使用,而不能在字符串中间出现。例如,下列语句会产生运行时错误:
view.RowFilter = "name LIKE '张*三" // 运算符中出错:字符串模式“张*三”无效。
字符串必须以单引号括起,而日期型必须以#符号括起。字符型值可以使用小数点和科学计数法。
RowFilter也支持聚合函数,如SUM, COUNT, MIN,MAX, and AVG。如果表中没有数据行,那么函数将返回NULL。
在介绍RowFilter表达式的最后,让我们讨论三个很便利的函数:Len,IIF和Substring。
正如其名,Len()返回特定表达式的长度。该表达式可以是一个列名,也可以是其他合法的表达式。
view.RowFilter = "len(name) = 4";
Substring()返回指定的表达式自特定位置开始,特定长度的字符子串。
名字第二个字和第三个字为“建国”的人。
view.RowFilter = "substring(name, 2, 2) = '建国'";
我最喜欢用的是IIF(),它按照逻辑表达式的值有一到两个值。IIF是IF-THEN-ELSE语句的紧凑表达。语法如下:
IIF(expression, if_true, if_false)
通过该函数,可以建立非常复杂的过滤字符串。例如,假定你从SQL Server的Northwind数据库中取得Employees表,下列表达式可以选出那些employeeID小于6且lastname为偶数个字符和employeeID大于6且lastname为奇数个字符的员工。
IIF(employeeID<6, Len(lastname) %2 =0, Len(lastname) %2 >0)
转自:
dataTable 、dataView、Dataset 区别的经典回答
Find:
DataView 中的2个查询方法:
-Find(object key)
- FindRows(object key)
这两个方法需要 DataView 是排序过的,参数 key 是要查找的关键字,关键字的数据类型要与排序字段匹配,如果关键字是一个以上,则需要调用重载方法:
-Find(object[] key)
- FindRows(object[] key)
关键字数组中关键字的顺序要与排序字段名的顺序一致,且不能缺少。
转自:
在DataTable和DataView中查找指定记录
RowState:
RowState 中储存着 DataRowState ,它是一个枚举类型,有以下几种状态:
RowState 值 | 说明 |
---|---|
Unchanged | 自上一次调用 AcceptChanges 之后,该行没有改变。 |
Added | 该行被添加到了 DataTable 中,AcceptChanges 尚未调用。 |
Deleted | 将该行标记为 Deleted |
Modified | 该行已被修改,AcceptChanges 尚未调用。 |
Detached | 该行不属于任何 DataTable 。DataRow 在创建之后添加到集合中之前,或从集合中移除之后为该状态。 |
Unchanged 表示没有可更新的操作,Update 方法不会关注状态为 Unchanged 的行。
调用 AcceptChanges 方法会将 Added 与 Modified 状态的行变为 Unchanged 状态。
Fill 方法填充的数据为 Aded,但默认设置下会自动调用 AcceptChanges 使其变为 Unchanged 。
默认设置下 Update 方法也会自动调用 AcceptChanges 来使原状态为 Modified 的行变成 Unchanged 。
Added 状态表示需要在数据库中插入该行,Update 方法会执行 InsertCommand 。
从 xml 文件或者使用 DataTable.Rows.Add(params object[]) 方法添加的 DataRow 的 RowState 为 Added。
当 DataAdapter.AcceptChangesDuringFill 属性设置为 false 时,Fill 方法填充的数据将为 Added。
Deleted 状态表示需要从数据库中删除的行,Update 方法会执行 DeleteCommand 。
调用 Delete 方法会使 DataRow 的状态变为 Deleted 。
Delete 状态只是一个标记,该状态的行会被统计在 DataTable 的 Count 中,也就是能被遍历到,但访问它会抛异常。
默认设置下 Update 方法会自动调用 AcceptChanges 来使原状态为 Delete 的行变成 Detached 。
Modified 表示该行已被修改,需要更新,Update 方法会执行 UpdateCommand 。
对于状态为 Unchanged 或者 Modified 的 DataRow, 修改数据后的状态为 Modified 。
对于状态为 Added 的 DataRow, 修改数据后仍然为 Added 。因为 Added 状态的数据可能并不存在于数据库,需要的是insert 。
Detached 的意思是分离的,此状态的行在逻辑上不存在,不能调用 DataRow.AcceptChanges 方法,会抛异常。
状态为 Deleted 的 DataRow, 使用 DataRow.AcceptChanges 方法后, 行状态转化为 Detached;
状态为 Added 的 DataRow, 使用 DataRow.RejectChanges 方法后, 行状态将转化为 Detached 。
参见 :
【C#】DataRowState演变备忘
–
DataRow 必定处于这几种状态之一, 调用 Update 方法时, DataAdapter 会根据其状态来选择不同的处理方式。
- Added 交由 InsertCommand 来处理。
- Deleted 交由 DeleteCommand 来处理。
- Modified 交由 UpdateCommand 来处理。
- Unchanged 不做处理
- Detached 不会出现在 DataTable 之中,当然也就没有处理。
要注意的是,DataAdapter 只是根据状态来调用不同的 SqlCommand,具体的实现取决于SqlCommand 中的 CommandText 。
DataAdapter.Upadate 执行结束后,默认会调用 AcceptChanges 方法来确认改动,若不想自动 AcceptChanges ,可以将DataAdapter.AcceptChangesDuringUpdate 设置为 false 。
一些操作会导致状态的变化,包括 Add 方法、Delete 方法、对行进行编辑等。
当我们执行这些操作时,可以理解为开启了一段事务,直到 AcceptChanges 或 RejectChanges 之后该事务结束。
Accept 的意思是承认、接受;Reject 的意思是拒绝、排斥,所以 AcceptChanges 实际上就是承认改动,并把其状态变成改动后应保持的状态,也就是 Commit ;RejectChanges 就是不承认改动,并将其状态变成改动前的状态,也就是 Rollback 。
AcceptChanges 应该在 Update 之后调用,因为 DataAdapter 需要根据状态来选择不同的处理方式。
顺便一提,在 DataRowCollection (DataTable.Rows) 中有 Remove 和 RomoveAt 两个方法,这两个方法会直接将 DataRow 从集合中删掉,不会有回滚的机会,实现是 :
row.Delete();
row.AcceptChanges();
借鉴:
SqlDataAdapter的Update方法实现原理
DataTable的AcceptChanges()方法和DataRow的RowState属性
DataRow的RowState属性变化
测试代码:
private static void StateTest()
{
//---------------获得一个 DataTable-------
SqlDataAdapter adapter = new SqlDataAdapter("select * from t_student", "Server = localhost; Database = Test; UID = sa; PWD = ;");
DataTable table = new DataTable();
adapter.Fill(table);
//PrintDataTable(table);
DataRow row = table.Rows[0];
//——————————————————————————————————————
Console.WriteLine("——————Fill——————");
Console.WriteLine("Fill 后的状态 :" + row.RowState);
Console.WriteLine();
//——————————————————————————————————————
Console.WriteLine("——————修改——————");
row["name"] = "test";
Console.WriteLine("修改后的状态 :" + row.RowState);
row.RejectChanges();
Console.WriteLine("修改,RejectChanges后的状态 :" + row.RowState);
row["name"] = "test";
row.AcceptChanges();
Console.WriteLine("修改,AcceptChanges后的状态 :" + row.RowState);
Console.WriteLine();
//——————————————————————————————————————
Console.WriteLine("——————删除——————");
row.Delete();
Console.WriteLine("Delete后的状态 :" + row.RowState);
row.RejectChanges();
Console.WriteLine("Delete,RejectChanges后的状态 :" + row.RowState);
row.Delete();
row.AcceptChanges();
Console.WriteLine("Delete,AcceptChanges后的状态 :" + row.RowState);
Console.WriteLine();
//——————————————————————————————————————
Console.WriteLine("——————先修改再删除——————");
row = table.Rows[0];
row["name"] = "hhhhhhh";
row.Delete();
Console.WriteLine("先修改再删除的状态 :" + row.RowState);
row.RejectChanges();
Console.WriteLine("先修改再删除,RejectChanges后的状态 :" + row.RowState);
row["name"] = "hhhhhhh";
row.Delete();
row.AcceptChanges();
Console.WriteLine("先修改再删除,AcceptChanges后的状态 :" + row.RowState);
Console.WriteLine();
//——————————————————————————————————————
Console.WriteLine("——————新行与Add——————");
row = table.NewRow();
Console.WriteLine("新行的状态 :" + row.RowState);
table.Rows.Add(row);
Console.WriteLine("Add后的状态 :" + row.RowState);
row.RejectChanges();
Console.WriteLine("Add,RejectChanges后的状态 :" + row.RowState);
table.Rows.Add(row);
row.AcceptChanges();
Console.WriteLine("Add,AcceptChanges后的状态 :" + row.RowState);
Console.WriteLine();
//——————————————————————————————————————
Console.WriteLine("——————Add后修改——————");
row = table.NewRow();
table.Rows.Add(row);
row[1] = "Add & Edit";
Console.WriteLine("Add并修改后的状态 :" + row.RowState);
row.RejectChanges();
Console.WriteLine("Add并修改,RejectChanges后的状态 :" + row.RowState);
row = table.NewRow();
table.Rows.Add(row);
row[1] = "Add & Edit";
row.AcceptChanges();
Console.WriteLine("Add并修改,AcceptChanges后的状态 :" + row.RowState);
Console.WriteLine();
//——————————————————————————————————————
Console.WriteLine("——————Add后删除——————");
row = table.NewRow();
table.Rows.Add(row);
row.Delete();
Console.WriteLine("Add并Delete后的状态 :" + row.RowState);
row.RejectChanges();
Console.WriteLine("Add并Delete,RejectChanges后的状态 :" + row.RowState);
row = table.NewRow();
table.Rows.Add(row);
row.Delete();
//row.AcceptChanges(); //异常:不能对该表中没有的行进行此操作
table.AcceptChanges();
Console.WriteLine("Add并Delete,AcceptChanges后的状态 :" + row.RowState);
Console.WriteLine();
/*
* 也就是说 Add 后 Delete 相当于没有 Add
* 等价于:
* row = table.NewRow();
* row.AcceptChanges(); //异常:不能对该表中没有的行进行此操作
*/
//——————————————————————————————————————
Console.WriteLine("——————Add后先修改再删除——————");
row = table.NewRow();
table.Rows.Add(row);
row[1] = "Add & Edit & Delete";
row.Delete();
Console.WriteLine("Add后先修改再删除后的状态 :" + row.RowState);
row.RejectChanges();
Console.WriteLine("Add后先修改再删除,RejectChanges后的状态 :" + row.RowState);
row = table.NewRow();
table.Rows.Add(row);
row[1] = "Add & Edit & Delete";
row.Delete();
//row.AcceptChanges(); //异常:不能对该表中没有的行进行此操作
table.AcceptChanges();
Console.WriteLine("Add后先修改再删除,AcceptChanges后的状态 :" + row.RowState);
Console.WriteLine();
}
控制台信息:
——————Fill——————
Fill 后的状态 :Unchanged
——————修改——————
修改后的状态 :Modified
修改,RejectChanges后的状态 :Unchanged
修改,AcceptChanges后的状态 :Unchanged
——————删除——————
Delete后的状态 :Deleted
Delete,RejectChanges后的状态 :Unchanged
Delete,AcceptChanges后的状态 :Detached
——————先修改再删除——————
先修改再删除的状态 :Deleted
先修改再删除,RejectChanges后的状态 :Unchanged
先修改再删除,AcceptChanges后的状态 :Detached
——————新行与Add——————
新行的状态 :Detached
Add后的状态 :Added
Add,RejectChanges后的状态 :Detached
Add,AcceptChanges后的状态 :Unchanged
——————Add后修改——————
Add并修改后的状态 :Added
Add并修改,RejectChanges后的状态 :Detached
Add并修改,AcceptChanges后的状态 :Unchanged
——————Add后删除——————
Add并Delete后的状态 :Detached
Add并Delete,RejectChanges后的状态 :Detached
Add并Delete,AcceptChanges后的状态 :Detached
——————Add后先修改再删除——————
Add后先修改再删除后的状态 :Detached
Add后先修改再删除,RejectChanges后的状态 :Detached
Add后先修改再删除,AcceptChanges后的状态 :Detached
请按任意键继续…
DataRowVersion :
DataRowVersion 就是行版本,它与 DataState 是生成 Sql 命令的依据。行版本也为回滚提供了支持。
DataRowVersion 维护 DataRow 在各个阶段的值,包括当前值、原始值、默认值。
例如,在修改了某行的数据后,该行的状态将为 Modified ,并且有两个行版本:Current (修改后的当前值) 和 Original (修改前的原始值)。
DataRowVersion 值 | 说明 |
---|---|
Current | 行的当前值。deleted 状态的行不存在该行版本 |
Original | 行的原始值。Added 状态的行不存在该行版本 |
Proposed | 行的建议值。只有 Detached 状态的行和正在进行编辑的行,存在该行版本。 |
Default | 表示行的默认版本。Added、Modified、Unchanged 状态的行的默认行版本是 current;Deleted 状态的行没有默认版本。Detached 状态的行的默认版本是 proposed。 |
当行的状态变化时,其版本的值也会跟着发生变化。
调用 AcceptChanges 时,会将所有行 Current 版本的值赋给 Original 版本,并会移除所有状态为 Deleted 的行,非 Deleted 的行会被赋予 UNchanged 状态。
调用 RejectChanges 时,会将所有行 Original 版本的值赋给 Current 版本,并会移除所有状态为 Added 的行,非 Added 的行会被赋予 UNchanged 状态。
Detached 状态的行不会收到 AcceptChanges 和 RejectChanges 的影响。
关于各方法对状态和版本的影响请参见:
SqlDataAdapter的Update方法实现原理 第3页
测试用代码:
private static void VersionTest()
{
//---------------获得一个 DataTable-------
SqlDataAdapter adapter = new SqlDataAdapter("select * from t_student", "Server = localhost; Database = Test; UID = sa; PWD = ;");
DataTable table = new DataTable();
adapter.Fill(table);
//PrintDataTable(table);
DataRow row = table.Rows[0];
//——————————————————————————————————————
Console.WriteLine("——————Fill——————");
PrintVersion(row);
Console.WriteLine("——————修改——————");
row["name"] = "新李四";
PrintVersion(row);
row.AcceptChanges();
PrintVersion(row);
Console.WriteLine("——————删除——————");
row.Delete();
PrintVersion(row);
row.AcceptChanges();
PrintVersion(row);
//Console.WriteLine(row["name"]); //此行已从表中移除并且没有任何数据。BeginEdit() 将允许在此行中创建新数据
Console.WriteLine("——————新行与Add——————");
row = table.NewRow();
row["name"] = "新行";
PrintVersion(row);
table.Rows.Add(row);
PrintVersion(row);
row.AcceptChanges();
PrintVersion(row);
Console.WriteLine("——————Test——————");
row = table.Rows[2];
PrintVersion(row);
row.Delete();
PrintVersion(row);
//Console.WriteLine(row["name"]); //不能通过已删除的行访问该行的信息。
Console.WriteLine(row["name", DataRowVersion.Original]); //这样就可以
}
private static void PrintVersion(DataRow row)
{
Console.WriteLine("RowState : " + row.RowState);
if (row.HasVersion(DataRowVersion.Current))
Console.WriteLine("DataRowVersion.Current : " + row["name", DataRowVersion.Current]);
else
Console.WriteLine("DataRowVersion.Current : ——该版本不存在——" );
if (row.HasVersion(DataRowVersion.Original))
Console.WriteLine("DataRowVersion.Original : " + row["name", DataRowVersion.Original]);
else
Console.WriteLine("DataRowVersion.Original : ——该版本不存在——");
if (row.HasVersion(DataRowVersion.Proposed))
Console.WriteLine("DataRowVersion.Proposed : " + row["name", DataRowVersion.Proposed]);
else
Console.WriteLine("DataRowVersion.Proposed : ——该版本不存在——");
if (row.HasVersion(DataRowVersion.Default))
Console.WriteLine("DataRowVersion.Default : " + row["name", DataRowVersion.Default]);
else
Console.WriteLine("DataRowVersion.Default : ——该版本不存在——");
Console.WriteLine();
}
控制台信息 :
——————Fill——————
RowState : Unchanged
DataRowVersion.Current : 李四
DataRowVersion.Original : 李四
DataRowVersion.Proposed : ——该版本不存在——
DataRowVersion.Default : 李四
——————修改——————
RowState : Modified
DataRowVersion.Current : 新李四
DataRowVersion.Original : 李四
DataRowVersion.Proposed : ——该版本不存在——
DataRowVersion.Default : 新李四
RowState : Unchanged
DataRowVersion.Current : 新李四
DataRowVersion.Original : 新李四
DataRowVersion.Proposed : ——该版本不存在——
DataRowVersion.Default : 新李四
——————删除——————
RowState : Deleted
DataRowVersion.Current : ——该版本不存在——
DataRowVersion.Original : 新李四
DataRowVersion.Proposed : ——该版本不存在——
DataRowVersion.Default : ——该版本不存在——
RowState : Detached
DataRowVersion.Current : ——该版本不存在——
DataRowVersion.Original : ——该版本不存在——
DataRowVersion.Proposed : ——该版本不存在——
DataRowVersion.Default : ——该版本不存在——
——————新行与Add——————
RowState : Detached
DataRowVersion.Current : ——该版本不存在——
DataRowVersion.Original : ——该版本不存在——
DataRowVersion.Proposed : 新行
DataRowVersion.Default : 新行
RowState : Added
DataRowVersion.Current : 新行
DataRowVersion.Original : ——该版本不存在——
DataRowVersion.Proposed : ——该版本不存在——
DataRowVersion.Default : 新行
RowState : Unchanged
DataRowVersion.Current : 新行
DataRowVersion.Original : 新行
DataRowVersion.Proposed : ——该版本不存在——
DataRowVersion.Default : 新行
——————Test——————
RowState : Unchanged
DataRowVersion.Current : 新行
DataRowVersion.Original : 新行
DataRowVersion.Proposed : ——该版本不存在——
DataRowVersion.Default : 新行
RowState : Deleted
DataRowVersion.Current : ——该版本不存在——
DataRowVersion.Original : 新行
DataRowVersion.Proposed : ——该版本不存在——
DataRowVersion.Default : ——该版本不存在——
新行
请按任意键继续…
不指定版本,直接通过访问器获取的是该行的默认版本的值。
从这个代码中可以看到 Deleted 状态的行是没有默认版本的,这就是不能访问直接 Deleted 行的原因吧。
借鉴 :
DataRow对象的RowState和DataRowVersion属性特点
C# DataRowState - 状态更改细节
SqlDataAdapter的Update方法实现原理
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
参考 :
c#连接数据库
使用ADO.NET访问数据库,类和对象概述
SqlDataReader_百度百科
SqlDataReader的用法
使用SqlDataReader和SqlDataAdapter的注意
一篇文章搞定SqlDataAdapter
C#之DataSet和DataTable
C# DataTable 详解
C# DataTable常用方法总结
遍历datatable的几种方法(C# )
DataView RowFilter 的语法
在DataTable和DataView中查找指定记录
SqlDataAdapter 用法详解
C#中SqlDataAdapter的使用小结
C#用DataTable实现Group by数据统计
SqlDataReader和SqlDataAdapter 区别
dataTable 、dataView、Dataset 区别的经典回答
SqlCommandBuilder 可批量新增与修改数据
【C#】DataRowState演变备忘
SqlDataAdapter的Update方法实现原理
DataTable的AcceptChanges()方法和DataRow的RowState属性
DataRow的RowState属性变化
SQL Server 2008连接字符串写法大全
C# 连接SQL数据库 常用连接字符串
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
大坑总算写完了…. By2018-1-16 17:13