C# 数据库入门项目






4.38/5 (45投票s)
类、数据库连接、登录 Winform
引言
本文仅适用于刚开始尝试使用类连接数据库的初学者。本文的主要目标是为初学 C# 的开发者提供一个项目启动思路。我在开始 C# 项目时遇到了许多问题。所以我想帮助其他人,让他们能够获得灵感并开始一个项目,使用类概念进行数据库连接和查询。我以一个带有登录界面的 C# 项目为例。
以下是基本流程
- 用户运行软件。
- 软件等待用户名和密码。
- 用户输入用户名和密码。
- 软件打开数据库连接并使用用户输入进行查询。
- 如果软件找到了该用户,则允许用户进入下一个 Winform。
- 软件将用户名传递给下一个 Winform。
- 用户在下一个 Winform 中收到带有用户名的消息。
我的目标不是在此项目中使用 Entity Framework 或 LINQ to SQL。
背景
我是一名初学的 C# 开发者。我尝试在 Google 上查找关于 C# 项目的清晰思路。但找不到简单的例子。大多数示例都只是我想要实现的一部分。最终我制作了一个可行的版本。希望这能帮助其他新的 C# 开发者开始他们的旅程。
使用代码
创建表脚本
CREATE TABLE [dbo].[RegLogUser](
[LoginID] [int] IDENTITY(1,1) NOT NULL,
[UserName] [varchar](50) NULL,
[Password] [varchar](50) NULL,
[LogType] [bit] NULL)
希望你在 MS SQL Server 数据库中运行此脚本。我使用了 MS SQL Server 2008,但你也可以用其他版本。对于 C# 项目,我使用了 Visual Studio 2010,但它也适用于其他版本。
在此项目中,我想创建一个用于数据库连接的类。那么什么是类?你可以找到数百万篇关于类的文章。简而言之,我们可以说类是一组通用项,就像汽车是一个类,鸟是一个类一样。
如果我们要定义一个 Car 类,我们可以用 C# 编写。格式是 class <name>
class car {
}
连接 SQL Server,我们做同样的事情,定义一个名为 dbConnection 的类。
class dbConnection
{
// you can use any name for the class instead of dbConnection
}
假设它是一辆出租车,这意味着它是公共交通工具。在 C# 中,我们如何让某物公开访问?只需在类名前加上一个关键字。
public class car
{
}
我们可以将相同的概念用于我们的 dbConnection 类,使其公开可访问。
public class dbConnection
{
}
如果你对汽车充满热情,那么你就知道 保时捷。假设我们有一辆名为保时捷的汽车。在面向对象编程语言中,我们称保时捷为 对象。对象是代表现实世界事物的实体。在 C# 中,我们按以下格式声明对象:<类名> <对象名> = new <类构造函数>;
public class car{
}
//now declare object of car class
car Porsche=new car();
现在我们有了一个新的构造函数项。首先,我们必须知道,如果我们不在 C# 类中声明任何构造函数,它会为该类创建一个构造函数。我们也可以定义一个类构造函数。构造函数必须与我们在类名中声明的名称相同。
public class car
{
public car() // Constructor function
{
}
}
对于 dbConnection 类
public class dbConnection
{
public dbConnection() // Constructor function
{
}
}
我的车库里有一辆 SUV 和一辆卡车。我需要为它们编写两个新类。
class SUV{
}
class Truck{
}
它们都属于汽车类,但具有不同的特征。所以,我从汽车类派生出了 SUV 和卡车类。
//class <derived_class> : <base_class>
class SUV : car
{
}
//class <derived_class> : <base_class>
class Truck: car
{
}
这里,car 是一个基类,我创建了两个新的派生类 SUV 和 Truck。在我的项目中,我使用另一个名为 dbProcess 的类专门用于数据库操作。所以我的项目代码变成这样。
//class <derived_class> : <base_class>
class dbProcess : dbConnection
{
}
现在,我希望 car 类只能从其派生类 SUV 和 Truck 访问,所以我将在 car 类的定义中放置一个 abstract 关键字。
public abstract class car
{
}
简而言之,这是完整的代码。
public abstract class car{
public car() // Constructor function
{
}
}
class SUV: car
{
}
在我的项目中,我做了同样的事情。
public abstract class dbConnection
{
public dbConnection() // Constructor function
{
}
}
//class <derived_class> : <base_class>
class dbProcess : dbConnection
{
}
我启动 Visual Studio 2010,在“Windows Forms Application”下创建一个新的 C# 项目。默认情况下,VS(Visual Studio)会创建一个名为 Program.cs 的文件。这是项目的起始文件。每个 C# 程序都必须有一个 main() 函数。“Program.cs”就包含这个 main() 函数。Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace VIMS
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() //every c# code has a main function
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmLogin()); // frmLogin is my login Win Form Name
}
}
}
为了连接数据库,我们需要两个由 Microsoft 在 .NET Framework 下提供的引用。
using System.Data;
using System.Data.SqlClient;
现在我们在 dbConnection 类中声明两个成员变量,因为如果我们想连接数据库,我们需要打开一个连接。SqlConnection 类帮助我们做到这一点。此外,对于事务状态,我们使用 SqlTransaction 类来声明一个事务成员变量。
public SqlConnection conn;
public SqlTransaction transaction;
在构造函数中,我用所有数据库参数值构建了一个简单的字符串,并创建了 conn 对象的实例。
public dbConnection() // constructor Function
{
string strProject = "YourServer"; //Enter your SQL server instance name
string strDatabase = "YourDatabase"; //Enter your database name
string strUserID = "testUser"; // Enter your SQL Server User Name
string strPassword = "testPassword"; // Enter your SQL Server Password
string strconn = "data source=" + strProject +
";Persist Security Info=false;database=" + strDatabase +
";user id=" + strUserID + ";password=" +
strPassword + ";Connection Timeout = 0";
conn = new SqlConnection(strconn);
}
每个连接都需要使用 SqlConnection 类的 open 方法打开。所以我把它放在一个单独的方法中,以便在需要时可以调用它。
public void openConnection() // Open database Connection
{
conn.Close();
conn.Open();
transaction = conn.BeginTransaction();
}
我认为在查询执行完成后关闭连接是个好习惯,这样网络就可以释放。由于这个关闭操作需要多次使用,所以我再次创建了一个方法。
public void closeConnection() // database connection close
{
transaction.Commit();
conn.Close();
}
有时我们需要在单个连接中更新多个表。但是,如果我们在执行多个更新或修改查询时遇到任何错误,则存在数据完整性丢失的风险。因此,我们可以使用一个事务类来监控此事。如果有任何错误,它可以回滚到其之前的状态。
public void errorTransaction()
{
transaction.Rollback();
conn.Close();
}
在我的项目中,我需要使用日期值更新/删除表,所以我为此特定目的创建了一个方法。
protected void ExecuteSQL(string sSQL)
{
SqlCommand cmdDate = new SqlCommand(" SET DATEFORMAT dmy", conn, transaction);
cmdDate.ExecuteNonQuery();
SqlCommand cmd = new SqlCommand(sSQL, conn, transaction);
cmd.ExecuteNonQuery();
}
我的计划是更新/删除不带日期值的表。
protected void OnlyExecuteSQL(string sSQL)
{
SqlCommand cmd = new SqlCommand(sSQL, conn);
cmd.ExecuteNonQuery();
}
需要将数据显示为网格视图。
protected DataSet FillData(string sSQL, string sTable)
{
SqlCommand cmd = new SqlCommand(sSQL, conn, transaction);
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
adapter.Fill(ds, sTable);
return ds;
}
有时需要逐行获取数据,而不是一次获取整个数据集。
protected SqlDataReader setDataReader(string sSQL)
{
SqlCommand cmd = new SqlCommand(sSQL, conn, transaction);
cmd.CommandTimeout = 300;
SqlDataReader rtnReader;
rtnReader = cmd.ExecuteReader();
return rtnReader;
}
有关综合代码,请查看项目中的 dbConnection.cs 文件。现在,dbUser.cs 帮助我声明了我的 dbUser 类。在这个类中,我创建了四个属性来保存我的登录信息数据,以及一个用于与数据库检查用户的方法。
/* user class */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace VIMS.Class
{
class dbLogUser : dbConnection
{
public int LoginID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public bool LogType { get; set; }
public bool ValidRegLogUser()
{
bool _UserValid = false;
using (SqlCommand cmd = new SqlCommand())
{
openConnection();
SqlDataReader conReader;
conReader = null;
cmd.CommandText = "Select * from RegLogUser where username=@userName and UserPassword=@UserPassword";
cmd.Connection = conn;
cmd.Transaction = transaction;
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add("@userName", SqlDbType.VarChar).Value = UserName;
cmd.Parameters.Add("@UserPassword", SqlDbType.VarChar).Value = Password;
try
{
conReader = cmd.ExecuteReader();
while (conReader.Read())
{
LoginID = Convert.ToInt32(conReader["LoginID"]);
LogType = (bool)conReader["LogType"];
_UserValid = true;
}
}
catch (Exception ex)
{
errorTransaction();
throw new ApplicationException("Something wrong happened in the Login module :", ex);
}
finally
{
conReader.Close();
closeConnection();
}
}
return _UserValid;
}
}
}
在 frmLogin Winform 中,我使用了两个文本框来获取用户名和密码,以及两个按钮(出于个人兴趣,我放了一个 picturebox 而不是 Button)。当用户输入用户名、密码并单击“确定”按钮时,我调用一个名为 UserLogin() 的函数。
dbProcess MainDB = new dbProcess(); //create a object for dbProcess class
dbUser LogUser=new dbUser (); //create a object for dbUser class
bool LoginOk = false; // login Flag
private void UserLogin()
{
if (TxtUserName.Text == "") // check Username is Empty or Not
{
return;
}
if (TxtPassword.Text == "") // check Password is Empty or Not
{
return;
}
LogUser.UserName = TxtUserName.Text .Trim();
LogUser.Password = TxtPassword.Text.Trim();
LoginOk=LogUser.ValidRegLogUser();
if (LoginOk)
{
FrmMain MainForm = new FrmMain();
this.Hide();
MainForm.Show();
}
else
{
MessageBox.Show("Please check username and password",
"Error Connection", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
到目前为止一切都很好,但我的目标是成功登录后,用户可以移动到另一个 Windows Form 并收到一条带有他名字的消息。
我来自 VB.NET 背景,所以在这一点上我感到非常沮丧。为什么?在 VB.NET 中,我可以使用全局变量。但在 C# 中,没有全局变量的概念。 :(
我在 Google 上搜索并找到了很多关于这个主题的信息。但我无法决定哪种方式可以在我的项目中实现这个目标。
(全局变量的替代方案)。我深入研究,并想到了一个叫做 static 的想法。
static 是 C# 中使用的一个关键字,因此它会在应用程序生命周期内保留其值。
public static string UserName = "";
现在最大的问题来了,我应该把这个 static 字符串变量放在我的项目的哪里,才能在应用程序的整个生命周期中获取它的值?正如我之前告诉你的,我是一名 C# 开发新手,所以我选择了捷径(也许这不是正确的方法,但它在这个项目中有效,希望有人能纠正我)。我把它放在了 Program.cs 文件中。现在 Program 类变成了这样:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace VIMS
{
static class Program
{
public static string UserName = "";
public static int LoginID = 0;
public static bool LogType = false;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmLogin());
}
}
}
还改变了frmLogin 类中的 UserLogin 方法。
private void UserLogin()
{
TxtUserName.Text = "test";
TxtPassword.Text = "123456";
if (TxtUserName.Text == "")
{
return;
}
if (TxtPassword.Text == "")
{
return;
}
LogUser.UserName = TxtUserName.Text .Trim();
LogUser.Password = TxtPassword.Text.Trim();
LoginOk=LogUser.ValidRegLogUser();
if (LoginOk)
{
Program.UserName = LogUser.UserName; // Store username into static variable
Program.LoginID = LogUser.LoginID; // Store username into static variable
Program.LogType = LogUser.LogType; // Store username into static variable
// I can get them in FrmMain Window.
FrmMain MainForm = new FrmMain();
this.Hide();
MainForm.Show();
}
else
{
MessageBox.Show("Please check username and password",
"Error Connection", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
最后,我在我的第二个窗口窗体 FrmMain 的加载事件中放了一个消息框(仅用于测试)。
我想强调一点,在此代码中我没有使用加密密码,但我强烈建议你对密码进行加密。
private void FrmMain_Load(object sender, EventArgs e)
{
MessageBox.Show( Program.UserName,"Successful Login" ,
MessageBoxButtons .OK ,MessageBoxIcon.Information );
}
关注点
有很多方法可以实现这一点。你可以尝试一下,或者给我一些关于我在项目中使用的误导性概念(可能是因为我对 C# 知识不足)的建议。这样我们这些初学者就能对这类项目开发有清晰的认识。以下是一些我想与你分享的关键点:
- 类
- 抽象类
- 构造函数
- SqlClient 用于数据库连接类
- SqlConnection 用于数据库连接
- SqlTransaction 用于检查事务状态
- SqlCommand 用于执行 T-SQL 语句或存储过程
- SqlDataReader 用于从 SQL Server 表读取行。
- DataSet 用于在内存中表示数据
- SqlDataReader 用于表示命令和数据库连接以填充 Dataset。
- try-catch 用于错误处理
- 抛出异常 用于带消息的错误处理。
- Microsoft.net 中的变量和方法作用域