数据库的连接

1 篇文章 0 订阅

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值