ado.net笔记

  1. ADO.Net基础
  • 程序要和数据库交互要通过ADO.Net进行,通过ADO.Net就能在程序中执行SQL语句了。ADO.Net中提供了对各种不同数据库的统一操作接口。
  • 直接在项目中内嵌mdf文件的方式使用SQLServer数据库(基于服务的数据库)。mdf文件随着项目走,用起来方便,和在数据库服务器上创建数据库没有什么区别,运行的时候会自动附加(Attach)。
  • 双击mdf文件会在“服务器资源管理器”中打开,管理方式和在Management Studio没有什么本质不同。要拷贝mdf文件需要关闭所有指向mdf文件的连接。
  • 正式生产运行的时候附加到SQLServer上、修改连接字符串即可,除此之外没有任何的区别,在“数据库”节点上点右键“附加”:在数据库点上→任务→分离就可以得到可以拷来的拷去的mdf文件。
  • 用的时候要在控制台、WinForm项目中在Main函数最开始的位置加入备注的代码。ASP.Net项目中不需要。
  1. 连接SQLServer
  • 连接字符串:程序通过连接字符串指定要连哪台服务器上的、哪个实例的哪个数据库、用什么用户名密码等。
  • 项目内嵌mdf文件形式的连接字符串

“DataSource =.\SQLEXPRESS:AttachDBFilename=|DataDirectory|\Database1.mdf;Integrated Security=True; User Instance=True”。其中”.\SQLEXPRESS”表示本机上的SQLEXPRESS实例,如果数据库实例名不是SQLEXPRESS,则需要修改。”Database1.mdf”为mdf的文件名。

     //使用using,那么conn只能在using的{}内使用,之外就会释放。当然也可以使用try catch语句。

            using(

                SqlConnection conn = new SqlConnection(

                    @"Data Source=.\SQLEXPRESS;

                    AttachDBFilename = |DataDirectory|\Database1.mdf;

                    Integrated Security = True;

                    User Instance = True"

                    )           

            ){

                conn.Open();

            }

     必有的代码:

     //加上下面的代码,可以去看数据库中数据的更新情况,否则,即使更新了数据库也是不显示的。

            string dataDir = AppDomain.CurrentDomain.BaseDirectory;

            if(dataDir.EndsWith(@"\bin\Debug\") ||

                dataDir.EndsWith(@"\bin\Release\"))

            {

                dataDir = System.IO.Directory.GetParent(dataDir).Parent.Parent.FullName;

                AppDomain.CurrentDomain.SetData("DataDirectory",dataDir);

            }

  • ADO.Net中通过SqlConnection类创建到SQLServer的连接,SqlConnection代表一个数据库连接,ADO.Net中的连接等资源都实现了Idisposable接口,可以使用using进行资源管理,执行备注中的代码如果成功了就ok。
  • 可能出现的问题:连接不上数据库:连接字符串有错误。
  1. 执行简单的Insert语句
  • SqlCommand表示向服务器提交一个命令(SQL语句等),CommandText属性为要执行的SQL语句,ExecuteNonQuery方法执行一个非查询语句(Update、Insert、Delete等)
    using(SqlCommand cmd = conn.CreateCommand())
    {
        cmd.CommandText = “Insert into T_Users(UserName,Password) values (‘admin’,’888888’)”;
        cmd.ExecuteNonQuery();
    }
  • ExecuteNonQuery返回值是执行的影响行数。
  • 注意:
        string username = ‘test’;
        ……
        cmd.CommandText = “Insert into T_Users(UserName,Password) values(username,’888888’)”;
  1. ExecuteScalar
  • SqlCommand的ExecuteScalar方法用于执行查询,并返回查询所返回的结果集中的第一行的第一列,因为不能确定返回值的类型,所以返回值是object类型。

l   cmd.CommandText = “select count(*) from T_Users”;

l   int i = Convert.ToInt32(cmd.ExecuteScala());

l   cmd.CommandText = “select getdate()”;

l   DateTime dt = Convert.ToDataTime(cmd.ExecuteScalar());

  • 得到自动增长字段的主键值,在values关键词前加上output inserted.Id,其中Id为主键字段名。执行结果就是插入的主键值,用ExecuteScalar执行最方便。

l   cmd.CommandText = “Insert into T_Users(UserName,Password) output inserted.Id values(‘admin’,’888888’);

l   int i = Convert.ToInt32(cmd.ExecuteScalar());

  1. 执行查询
  • 执行有多行结果集的用ExecuteReader

l   SqlDataReader reader = cmd.ExecuteReader();

l   while(reader.Read())

l   {

Console.WriteLine(reader.GetString(1));

l   }

  • reader的GetString、GetInt32等方法只接受整数参数,也就是序号,用GetOrdinal方法根据列名动态得到序号。
  • using与close与dispose。如果没有用using,只是close那么还可以open,而dispose就是直接销毁了,不能在open。使用using,当变量出了using区域,就会看有没有close,如果没有,先执行close,再执行dispose。使用using比较好,如果使用别的,可能不会执行到dispose语句。造成别的时候连接不上了。
  • 另一种写法:

     reader.GetString(reader.GetOrdinal("UserId"));  == reader.GetString(0)

     reader.GetString(reader.GetOrdinal("UserName"));  == reader.GetString(1)

     reader.GetString(reader.GetOrdinal("Password"));  == reader.GetString(2)

  1. SQL注入漏洞攻击
  • 登录判断:cmd.CommandText = "select count(*) from Users where UserName = '"+ userName + "' and Password = '" + password +"'"; 通过将参数拼到SQL语句中。
  • 构造恶意的Password: 12’ or ‘1’ = ‘1 这样的话,就会成功登录了。
  • 防范注入漏洞攻击的方法:不使用SQL语句拼接,通过参数赋值。
  1. 查询参数
  • SQL语句使用@UserName表示“此处用参数代替”,向SqlCommand的Parameters中添加参数cmd.CommandText = "select count(*) from Users where UserName = @UN and Password = @P";

         cmd.Parameters.Add(new SqlParameter("UN", userName));

         cmd.Parameters.Add(new SqlParameter("P", password));

  • 参数在SQLServer内部不是简单的字符串替换。SQLServer直接用添加的值进行比较,因此不会有注入漏洞攻击。
  1. 案例
  • 用户界面中进行用户名的增加。输错三次禁止登录,用数据库记录ErrorTimes。

l   注意:在同一个连接中,如果SqlDataReader没有关闭,那么不能执行update类型的语句。因为指针还在指着它,是不能被别人访问的。可以再写一个函数,把它放在函数中。

  • 数据导入:从文本文件导入用户信息。易错点:Parameter的重复添加。

l   连接数据库用创建SQL命令,执行一次就可以了。不要写在循环里面。

l   有关乱码问题:使用nvarchar而不是varchar,插入的汉字之前加N

  • 数据导出:将用户信息导出到文本文件。
  • 问题:在导入汉字的过程中,如何保存使之在数据库中存入的不是乱码?

         设置读入时候的字符编码为Default即可。默认就是UTF-8的。

  1. 读取配置中的字符串
  • 将字符串写在代码中缺点:多次重复。

l   添加App.config文件。App.config文件是.Net的通用配置文件,在ASP.Net中也能同样使用。

l   在App.config中添加connectionString段,添加一个add项,用name属性起一个名字(比如DbConnStr),connectionString属性指定连接字符串。

l   在“引用”节点上点右键“添加引用”,找到System.configuration。不是所有.Net中的类都能直接调用,类所在的Assembly要被添加到项目的引用中才可以。

l   ConfigurationManager.ConnectionStrings[“DbConnStr”].ConnectionString得到连接字符串。

l   如何改变了位置,只需在App.config文件中将连接字符串改变即可。

  1. DataSet
  • SqlDataReader是连接相关的,SqlDataReader中的查询结果并不是放到程序中的,而是放在数据库服务器中,SqlDataReader只是相当于放了一个指针(游标),只能读取当前游标指向的行,一旦连接断开就不能再读取,这样做的好处就是无论查询结果有多少条,对程序占用的内存几乎没有影响。
  • SqlDataReader对于小数据量的数据来说带来的只能麻烦,优点可以忽略不计。ADO.Net中提供了数据集的机制,将查询结果填充到本地内存中,这样连接断开、服务器断开都不影响数据的读取。
  • DataSet dataset = new DataSet();
  • SqlDataAdpater adpater = new SqlDataAdpater(cmd);
  • Adpter.Fill(dataset);
  • SqlDataAdpater是DataSet和数据库之间沟通的桥梁。数据集DataSet包含若干表DataTable,DataTable包含若干行DataRow,foreach(DataRow row in dataset.Tables[0].Rows) row[“Name”]
  1. DataSet的更新
  • 可以更新行row[“Name”]=”changed”、删除行datatable.Rows.Remove()、新增行datatable.NewRow()。这一切都是修改的内存中的DataSet,没有修改数据库中的。
  • 可以调用SqlDataAdapter的Update方法将对DataSet的修改提交到数据库,Update方法有很多重载方法,可以提交整个DataSet、DataTable或者若干DataRow。但是需要为SqlDataAdapter提供DeleteAdpater提供DeleteCommand、UpdateCommand、InsertCommand它才知道如何将对DataSet的修改提交到数据库,由于这几个Command要求的格式非常的苛刻,因此开发人员自己写非常的困难,可以用SqlCommandBuilder自动生成这几个Command,用法简单,new SqlCommandBuiler(adapter)。查看生成的Command。SqlCommandBuilder要求表必须有主键。
  • 通过DataRow的RowState可以获得行的状态(删除、修改、新增等);
  • 调用DataSet的GetChangeds()方法得到变化的结果集,降低传递的资源占用。
  1. 可空类型
  • Int? 可空的int,触决数据库和对int不能为空的不同设置的问题
  • Int? i3 = 4; int i4 = i3.Value;
  1. 弱类型DataSet的缺点 
  • 只能通过列名引用,dataset.Tables[0].Rows[0][“Age”],如果写错了列名编译时不会发现错误,因此开发时必须要记着列名。
  • Int age = Convert.ToInt32(dataset.Rows[0][“Age”]),取到的字段的值是object类型,必须小心翼翼的进行类型转换。
  • 将DataSet传递给其他使用者,使用者很难识别出有哪些列可以使用。
  • 运行时才能知道所有列名,数据绑定麻烦,无法使用Winform、ASP.Net的快速开发功能。
  • 自己动手写强类型DataSet(类型化DataSet,TypeDataSet),创建继承自DataSet的PersonDataSet类,封装出int?Age等属性和bool IsAgeNull等方法,向PersonDataSet中填充。
  1. VS自动生成强类型DataSet
  • 添加→新建项→数据集
  • 将表从服务器资源管理器拖放到DataSet中。注意拖放过程是自动根据表结构生成强类型DataSet等类,没有把数据也拖过来,程序还是连的那个数据库,自动将数据库连接字符串写在了App.Config中。
  • 代码中使用DataSet示例:CC_RecordTableAdapter adapter = new CC_RecordTAbleAdapter(); 如何得知Adapter为类名?选中DataSet中下半部分的Adapter,Name属性就是类名。需要右键点击类名→解析
  • 取得所有数据:adapter.GetData(),例子程序:遍历显示所有数据,i<adapter.GetData().Count; adapter.GetData()[i].Age。
  • 常见问题:类名敲不对,表名+TableAdapter,表名+DataTable,表名+Row,然后用“解析”来填充类名。
  • 常见问题:类的内部定义的类要通过包含namespace的全名来引用,不能省略。类的内部定义的类就能避免同一个namespace下类不能重名的问题。
  1. 更新DataSet
  • 调用Adapter的Update方法就可以将DataSet的改变保存到数据库。Adapter.Update(datatable)
  • 要调用Update方法更新必须设置数据库主键,后面的Delete也是如此。
  • 常见错误:“当传递具有已修改行的DataRow集合时,更新要求有效的UpdateCommand”,要为表设置主键。“谁都变了,唯有主键不会变”,程序要通过主键来定位要更新的行,如果没有设定,可以先到数据库中设置主键,然后在DataSet的对应DataTable上点击右键,选择“配置”,在【对话框】中点击完成。
  1. 其它问题
  • 插入新行,调用insert方法。
  • 增加字段怎么办?DataSet设计器中点【配置】,对话框中点【查询生成器】,勾选新增加的字段即可,删除字段同样如此,也可以直接在上面进行修改。
  • 要修改字段就要重新配置生成,这就是强类型DataSet的弱点,因此强类型转换并不一定真的强,还是叫“类型化DataSet”吧。
  • 常见错误:报错:数据为空,判断列的值为空的方法:Is ** Null
  • 为什么Select方法会填充、Update方法会更新,Insert方法会插入?看看Adapter的SelectCommand等属性,是那些SQL语句在起作用,如果在需要完全可以自己手工调整。
  1. 增加新的SQL语句
  • 设计器的Adapter中点右键,选择“添加查询”→“使用SQL语句”,就可以添加多种类型的SQL语句,如果是“Select(返回行)”则SQL语句的列必须是对应DataSet类的父集合,生成两个方法:FillBy*和GetBy*,方法名自己要重新命名,Fillby是将结果填充到现有的DataSet,GetBy是将结果以DataSet方式返回,建议两个都生成,方便以后使用,
  • “Select(返回单个值)”就是ExecuteScalar
  • 对于增加的SQL语句在代码中是以方法的形式使用的。方法的参数类型、顺序就是VS猜测的,如果不正确或者需要调整只要选中对应的语句,然后在【属性】窗口中修改Parameters属性即可。
  • 增加新的SQL语句本质论:探寻源码:不能并发调用。
  • 像使用普通类的方法一样使用Adapter。SQL语句不用再写在界面代码中。这就是一种数据访问层(DAL:Data Access Layer)
  1. 批量插入反应太慢(通过秒表来计时)时,就在执行语句之前,先把连接打开。

            Stopwatch sw = new Stopwatch();

            sw.Start();

            T_UsersTableAdapter adapter = new T_UsersTableAdapter();

            adapter.Connection.Open();

            for (int i = 0; i < 2000; i++)

            {

                adapter.Insert(i.ToString(),null,i.ToString(),null);

            }

            adapter.Connection.Close();

            sw.Stop();

            MessageBox.Show(sw.Elapsed.ToString());

  1. 示例:向一个表中插入大量随机数据。

            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();

            stopwatch.Start();

 

            //为Test数据库中的T_Product表与T_Manufacturer表生成数据

            string connectionString = "Data Source=.;Initial Catalog=Test;Integrated Security = true";

 

            SqlConnection conn = new SqlConnection(connectionString);

            conn.Open();

            SqlCommand cmd = conn.CreateCommand();

            cmd.Parameters.Add("name", SqlDbType.NVarChar);

            cmd.Parameters.Add("address", SqlDbType.NVarChar);

            cmd.Parameters.Add("email", SqlDbType.NVarChar);

            cmd.Parameters.Add("tel", SqlDbType.VarChar);

            cmd.Parameters.Add("model", SqlDbType.NVarChar);

            cmd.Parameters.Add("builddate", SqlDbType.Date);

 

            double num = 0;

            for (int i = 0; i < 1000; i++)

            {

                int randomNum = new Random().Next(Int32.MaxValue);

                DateTime builddate = new DateTime(1998, 1, 1).AddDays(new Random().Next(10000));

 

                AddDataToManufacturer(cmd, "深圳恒丰塑化材料有限公司" + randomNum, "广东深圳市光明新区" + randomNum, "guangdong" + randomNum + "@163.com", "12345678902" + randomNum, "经销批发" + randomNum, builddate);

                Console.WriteLine(string.Format("第{0}条完成......", num+1));

                num++;

 

                AddDataToManufacturer(cmd, "北京伊特伟业科技公司" + randomNum, "北京市朝阳区" + randomNum, "yite" + randomNum + "@163.com", "12345678903" + randomNum, "个体经营" + randomNum, builddate);

                Console.WriteLine(string.Format("第{0}条完成......", num + 1));

                num++;

 

                AddDataToManufacturer(cmd, "苍南县飞翔彩印厂" + randomNum, "浙江苍南县" + randomNum, "cangnian" + randomNum + "@163.com", "12345678901" + randomNum, "生产厂家" + randomNum, builddate);

                Console.WriteLine(string.Format("第{0}条完成......", num + 1));

                num++;

            }

 

            Console.WriteLine("\r\n完成");

            Console.WriteLine(string.Format("共添加{0}条记录", num));

 

            stopwatch.Stop();

            Console.WriteLine(string.Format("共用{0}时{1}分{2}秒{3}毫秒", stopwatch.Elapsed.Hours,stopwatch.Elapsed.Minutes,stopwatch.Elapsed.Seconds,stopwatch.Elapsed.Milliseconds));

            Console.ReadKey();

        }

 

        /*        

         * 共添加3000条记录

         * 共用0时0分16秒401毫秒

         * 共用0时0分17秒726毫秒

         * 共用0时0分16秒960毫秒

         */

 

        public static void AddDataToManufacturer(SqlCommand cmd,string name, string address,string email,string tel,string model,DateTime builddate)

        {          

            cmd.CommandText = @"insert into T_Manufacturer(ManufacturerName,ManufacturerAddress,ManufacturerEmail,ManufacturerTel,BusinessModel,BuildDate) values(@name,@address,@email,@tel,@model,@builddate)";          

            cmd.Parameters["name"].Value = name;

            cmd.Parameters["address"].Value = address;

            cmd.Parameters["email"].Value = email;

            cmd.Parameters["tel"].Value = tel;

            cmd.Parameters["model"].Value = model;

            cmd.Parameters["builddate"].Value = builddate;

            cmd.ExecuteNonQuery();

        }

 

 

 

posted @ 2016-09-16 12:25  芜明-追星  阅读(323)  评论(0编辑  收藏  举报