前言
利用ADO.NET
連接數據庫進行相關操作可以說是每個.NET新手必須學習的一項內容。從學習的角度來看,我個人其實反對新手一開始就學Entity Framework
之類的ORM
框架,因爲Entity Framework
本質上還是基於ADO.NET
的二次封裝,所以紮實掌握SQL
和ADO.NET
才能讓新手更好的學習之後的ORM
框架。下面就來介紹一下ADO.NET
中常用的幾個類。
數據準備
爲了方便,我這裏選用VS2015自帶的LocalDB作爲數據源,在其中新建了一個數據庫DBSchool
,然後新建一張數據表[TStudent]
,其字段結構如下圖所示:
其中Id
字段爲自增主鍵,其餘字段用於描述學生信息,然後在數據表中添加三條數據,我們的實驗數據就準備好了,如下圖所示:
1、數據庫的連接
想要對數據庫進行操作,首先肯定得連接數據庫,連接數據庫就需要相應的數據庫連接字符串
,本文的連接字符串如下所示:
@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
在ADO.NET
中,SqlConnection
類主要負責連接和關閉數據庫,下面代碼演示了數據庫的連接和關閉:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static string ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
static void Main(string[] args)
{
SqlConnection connection = new SqlConnection(ConnectionString);
connection.Open();
if (connection.State == ConnectionState.Open)
{
Console.WriteLine("數據庫連接已開啓");
Console.WriteLine("數據源:{0}", connection.DataSource);
Console.WriteLine("數據庫名:{0}", connection.Database);
Console.WriteLine("連接時間:{0}", connection.ConnectionTimeout);
}
// 關閉連接
connection.Close();
connection.Dispose();
if (connection.State == ConnectionState.Closed)
{
Console.WriteLine("數據庫連接已關閉");
}
Console.ReadKey();
}
}
}
運行結果如下所示:
數據庫連接已開啓
數據源:(localdb)\MSSQLLocalDB
數據庫名:DBSchool
連接時間:30
數據庫連接已關閉
一般connection.Open()
用於開啓數據庫連接,connection.Close()、connection.Dispose()
用於關閉數據庫連接,在這兩者之間的區域就是你對數據庫操作的具體代碼,但一般我們不推薦上面這種寫法,取而代之的是利用using
關鍵字將一段數據庫操作代碼包裝起來,代碼如下所示:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static string ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
static void Main(string[] args)
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
Console.WriteLine("數據庫連接已開啓");
Console.WriteLine("數據源:{0}", connection.DataSource);
Console.WriteLine("數據庫名:{0}", connection.Database);
Console.WriteLine("連接時間:{0}", connection.ConnectionTimeout);
}
Console.ReadKey();
}
}
}
運行結果如下所示:
數據庫連接已開啓
數據源:(localdb)\MSSQLLocalDB
數據庫名:DBSchool
連接時間:30
2、數據庫的增刪改
數據庫的增刪改操作主要利用SqlCommand
實現,其中的幾個重要屬性和方法如下表所示:
名稱 | 作用 |
---|---|
CommandType | 指定執行SQL語句還是執行存儲過程 |
CommandText | 設置SQL語句或存儲過程名稱 |
Parameters | SQL命令參數,避免SQL注入 |
ExecuteNonQuery | 執行增刪改等命令,返回受影響的行數 |
2.1、添加數據:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static string ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
static void Main(string[] args)
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
// SQL參數
SqlParameter[] parameters =
{
new SqlParameter("@Sid", SqlDbType.NVarChar, 20),
new SqlParameter("@Sname", SqlDbType.NVarChar, 10),
new SqlParameter("@Sgender", SqlDbType.NVarChar, 2),
new SqlParameter("@Sage", SqlDbType.Int),
new SqlParameter("@Sphone", SqlDbType.NVarChar, 15),
};
// 參數賦值
parameters[0].Value = "1004";
parameters[1].Value = "吳六";
parameters[2].Value = "男";
parameters[3].Value = 20;
parameters[4].Value = "15874513687";
// 設置SQL語句
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = "insert into [TStudent] values(@Sid,@Sname,@Sgender,@Sage,@Sphone)";
command.Parameters.AddRange(parameters);
// 執行SQL
try
{
command.ExecuteNonQuery();
command.Parameters.Clear();
Console.WriteLine("添加數據成功");
}
catch
{
Console.WriteLine("添加數據失敗");
}
}
Console.ReadKey();
}
}
}
2.2、修改記錄
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static string ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
static void Main(string[] args)
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
// SQL參數
SqlParameter[] parameters =
{
new SqlParameter("@Sid", SqlDbType.NVarChar, 20),
new SqlParameter("@Sname", SqlDbType.NVarChar, 10)
};
// 參數賦值
parameters[0].Value = "1004";
parameters[1].Value = "吳老六";
// 設置SQL語句
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = "update [TStudent] set Sname=@Sname where Sid=@Sid";
command.Parameters.AddRange(parameters);
// 執行SQL
try
{
command.ExecuteNonQuery();
command.Parameters.Clear();
Console.WriteLine("修改數據成功");
}
catch
{
Console.WriteLine("修改數據失敗");
}
}
Console.ReadKey();
}
}
}
2.3、刪除記錄
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static string ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
static void Main(string[] args)
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
// SQL參數
SqlParameter[] parameters =
{
new SqlParameter("@Sid", SqlDbType.NVarChar, 20)
};
// 參數賦值
parameters[0].Value = "1004";
// 設置SQL語句
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = "delete from [TStudent] where Sid=@Sid";
command.Parameters.AddRange(parameters);
// 執行SQL
try
{
command.ExecuteNonQuery();
command.Parameters.Clear();
Console.WriteLine("刪除數據成功");
}
catch
{
Console.WriteLine("刪除數據失敗");
}
}
Console.ReadKey();
}
}
}
在以上的增刪改代碼中,一般推薦使用SqlParameter
封裝相關參數,一是方便SQL
的書寫和閱讀,二是能夠有效避免SQL注入問題
。
3、數據庫的查詢
數據庫的查詢用得最爲廣泛,關於查詢操作,ADO.NET
中一般有三種寫法,下面逐一介紹。
3.1、查詢方法一
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static string ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
static void Main(string[] args)
{
DataTable dataTable = new DataTable();
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
// 設置SQL語句
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = "select * from [TStudent]";
// 獲取查詢結果,填充到DataTable
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = command;
adapter.Fill(dataTable);
}
foreach (DataRow row in dataTable.Rows)
{
Console.Write(row["Id"].ToString() + "\t");
Console.Write(row["Sid"].ToString() + "\t");
Console.Write(row["Sname"].ToString() + "\t");
Console.Write(row["Sgender"].ToString() + "\t");
Console.Write(row["Sage"].ToString() + "\t");
Console.Write(row["Sphone"].ToString() + "\t");
Console.WriteLine();
}
Console.ReadKey();
}
}
}
3.2、查詢方法二
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static string ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
static void Main(string[] args)
{
DataTable dataTable = new DataTable();
using (SqlDataAdapter adapter = new SqlDataAdapter("select * from [TStudent]", ConnectionString))
{
adapter.Fill(dataTable);
}
foreach (DataRow row in dataTable.Rows)
{
Console.Write(row["Id"].ToString() + "\t");
Console.Write(row["Sid"].ToString() + "\t");
Console.Write(row["Sname"].ToString() + "\t");
Console.Write(row["Sgender"].ToString() + "\t");
Console.Write(row["Sage"].ToString() + "\t");
Console.Write(row["Sphone"].ToString() + "\t");
Console.WriteLine();
}
Console.ReadKey();
}
}
}
3.3、查詢方法三
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static string ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
static void Main(string[] args)
{
SqlConnection connection = new SqlConnection(ConnectionString);
connection.Open();
// 設置SQL語句
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = "select * from [TStudent]";
// 遍歷數據行
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Console.Write(reader.GetInt32(reader.GetOrdinal("Id")) + "\t");
Console.Write(reader.GetString(reader.GetOrdinal("Sid")) + "\t");
Console.Write(reader.GetString(reader.GetOrdinal("Sname")) + "\t");
Console.Write(reader.GetString(reader.GetOrdinal("Sgender")) + "\t");
Console.Write(reader.GetInt32(reader.GetOrdinal("Sage")) + "\t");
Console.Write(reader.GetString(reader.GetOrdinal("Sphone")) + "\t");
Console.WriteLine();
}
// 關閉連接
connection.Close();
connection.Dispose();
Console.ReadKey();
}
}
}
運行結果如下所示:
1 1001 張三 男 20 13745258478
2 1002 李四 女 21 18754124736
3 1003 王五 女 22 14963587415
上面三種方法都可以進行數據庫查詢操作,但也需要注意它們之間的區別。方法一和方法二查詢後將結果放入內存中的DataTable
,接下來我們只需要操作DataTable
即可。方法三則是在數據讀取完畢之前一直保持與數據庫的連接,直到讀取操作完成。一般更推薦方法一和方法二的寫法。
4、SQL參數化操作
同志們一定發現了,在上面的代碼中有一個類的出現頻率很高,那就是SqlParameter
。之前也介紹過SqlParameter
的作用主要是防止SQL注入問題
。那麼到底什麼是SQL注入問題
?下面來看一個WinForm實現用戶登錄的demo。首先建立一張用戶表[TUser]
,加入兩條數據,如下圖所示:
登錄界面代碼
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private static string ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
public Form1()
{
InitializeComponent();
}
private void btnLogin_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtUserName.Text) || string.IsNullOrEmpty(txtPassword.Text))
{
MessageBox.Show("請輸入用戶名和密碼", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
DataTable dataTable = new DataTable();
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
// 設置SQL語句
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = "select * from [TUser] where UserName='" + txtUserName.Text + "' and Password='" + txtPassword.Text + "'";
// 填充DataTable
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = command;
adapter.Fill(dataTable);
// 判斷登錄是否成功
if (dataTable.Rows.Count > 0)
{
MessageBox.Show("登陸成功");
}
else
{
MessageBox.Show("登陸失敗");
}
}
}
}
}
WinForm登錄界面如下圖所示,輸入admin
和123456
後點擊按鈕,發現能夠正常登陸,這看起來好像沒什麼問題。
現在我們將密碼改一改,輸入abcd
、' or '1'='1
,如下圖所示:
事實就是數據庫中根本就沒這條記錄,但最後竟然也能登陸!!!這其實就是一個典型的SQL注入問題
。問題就出在下面這條語句:
command.CommandText = "select * from [TUser] where UserName='" + txtUserName.Text + "' and Password='" + txtPassword.Text + "'";
很多同志都喜歡這麼寫,其實這是一個很大的安全隱患。這種情況我們要麼使用存儲過程
,要麼使用參數化的SQL語句
,而SqlParameter
就是幹這個活的。我們將登陸代碼改一改,如下所示:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private static string ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DBSchool;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
public Form1()
{
InitializeComponent();
}
private void btnLogin_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtUserName.Text) || string.IsNullOrEmpty(txtPassword.Text))
{
MessageBox.Show("請輸入用戶名和密碼", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
DataTable dataTable = new DataTable();
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
// SQL查詢參數
SqlParameter[] parameters =
{
new SqlParameter("@UserName", SqlDbType.NVarChar, 20),
new SqlParameter("@Password", SqlDbType.NVarChar, 20),
};
parameters[0].Value = txtUserName.Text;
parameters[1].Value = txtPassword.Text;
// 設置SQL語句
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = "select * from [TUser] where UserName=@UserName and Password=@Password";
command.Parameters.AddRange(parameters);
// 填充DataTable
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = command;
adapter.Fill(dataTable);
// 判斷登錄是否成功
command.Parameters.Clear();
if (dataTable.Rows.Count > 0)
{
MessageBox.Show("登陸成功");
}
else
{
MessageBox.Show("登陸失敗");
}
}
}
}
}
首先輸入admin
、123456
,如下圖所示,發現能夠正常登陸。
然後輸入abcd
、' or '1'='1
,如下圖所示,發現登陸失敗。
使用參數化的SQL語句
有兩個好處:一是書寫和閱讀都很清晰,二是能夠有效避免SQL注入問題
。以後同志們在傳遞參數做查詢時,一定要記得利用SqlParameter
封裝你的查詢參數。