65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 数据库入门项目

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (45投票s)

2013年12月2日

CPOL

7分钟阅读

viewsIcon

387972

downloadIcon

19959

类、数据库连接、登录 Winform

引言

本文仅适用于刚开始尝试使用类连接数据库的初学者。本文的主要目标是为初学 C# 的开发者提供一个项目启动思路。我在开始 C# 项目时遇到了许多问题。所以我想帮助其他人,让他们能够获得灵感并开始一个项目,使用类概念进行数据库连接和查询。我以一个带有登录界面的 C# 项目为例。
以下是基本流程

  1. 用户运行软件。
  2. 软件等待用户名和密码。
  3. 用户输入用户名和密码。
  4. 软件打开数据库连接并使用用户输入进行查询。
  5. 如果软件找到了该用户,则允许用户进入下一个 Winform。
  6. 软件将用户名传递给下一个 Winform。
  7. 用户在下一个 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# 知识不足)的建议。这样我们这些初学者就能对这类项目开发有清晰的认识。以下是一些我想与你分享的关键点:

  1. 抽象类
  2. 构造函数
  3. SqlClient 用于数据库连接类
  4. SqlConnection 用于数据库连接
  5. SqlTransaction 用于检查事务状态
  6. SqlCommand 用于执行 T-SQL 语句或存储过程
  7. SqlDataReader 用于从 SQL Server 表读取行。
  8. DataSet 用于在内存中表示数据
  9. SqlDataReader 用于表示命令和数据库连接以填充 Dataset。
  10. try-catch 用于错误处理
  11. 抛出异常 用于带消息的错误处理。
  12. Microsoft.net 中的变量和方法作用域
© . All rights reserved.