1. 环境准备与项目创建

在开始构建图书管理系统之前,我们需要准备好开发环境。首先确保已安装Visual Studio 2022(社区版即可)和SQL Server Express。打开Visual Studio后,选择"新建项目",在模板中选择"Windows窗体应用(.NET Framework)",命名为"BookManagementSystem"。

我习惯在解决方案资源管理器中先创建三个文件夹:

  • Models :存放数据模型类
  • DAL :数据访问层
  • UI :用户界面相关文件

接下来需要安装必要的NuGet包。右键项目选择"管理NuGet程序包",搜索并安装:

  • Microsoft.Data.SqlClient(最新的ADO.NET提供程序)
  • System.Data.DataSetExtensions(用于数据绑定)

提示:如果使用SQL Server身份验证而非Windows身份验证,连接字符串需要包含用户名和密码。生产环境中建议将连接字符串加密存储在配置文件中。

2. 数据库设计与建表

图书管理系统的核心是数据库设计。打开SQL Server Management Studio,新建名为"BookDB"的数据库。我们需要创建三张基础表:

-- 管理员表
CREATE TABLE t_admin (
    id VARCHAR(20) PRIMARY KEY,
    psw VARCHAR(50) NOT NULL
);

-- 图书表
CREATE TABLE t_book (
    isbn VARCHAR(20) PRIMARY KEY,
    name NVARCHAR(100) NOT NULL,
    author NVARCHAR(50),
    publisher NVARCHAR(100),
    stock INT DEFAULT 0
);

-- 借阅记录表
CREATE TABLE t_lend (
    id INT IDENTITY(1,1) PRIMARY KEY,
    user_id VARCHAR(20) NOT NULL,
    book_isbn VARCHAR(20) NOT NULL,
    lend_date DATETIME DEFAULT GETDATE(),
    return_date DATETIME,
    FOREIGN KEY (book_isbn) REFERENCES t_book(isbn)
);

实测中发现几个常见问题需要注意:

  1. ISBN字段长度应根据实际需求设置,国际标准是13位
  2. 图书名称和作者字段建议使用NVARCHAR以支持中文
  3. 借阅记录的归还日期应为NULL表示未归还

3. ADO.NET核心数据访问层

在DAL文件夹中创建Dao.cs,这是整个系统的数据库操作核心。我采用单例模式来管理数据库连接:

using Microsoft.Data.SqlClient;
using System.Configuration;

namespace BookMS.DAL
{
    public class Dao
    {
        private static SqlConnection _connection;
        private static readonly string _connStr = ConfigurationManager.ConnectionStrings["BookDB"].ConnectionString;

        private Dao() { }

        public static SqlConnection GetConnection()
        {
            if (_connection == null)
            {
                _connection = new SqlConnection(_connStr);
                _connection.Open();
            }
            else if (_connection.State == System.Data.ConnectionState.Closed)
            {
                _connection.Open();
            }
            return _connection;
        }

        public static int ExecuteNonQuery(string sql, params SqlParameter[] parameters)
        {
            using (var cmd = new SqlCommand(sql, GetConnection()))
            {
                cmd.Parameters.AddRange(parameters);
                return cmd.ExecuteNonQuery();
            }
        }

        public static SqlDataReader ExecuteReader(string sql, params SqlParameter[] parameters)
        {
            var cmd = new SqlCommand(sql, GetConnection());
            cmd.Parameters.AddRange(parameters);
            return cmd.ExecuteReader();
        }
    }
}

踩过的坑:早期版本没有妥善处理连接关闭问题,导致连接池耗尽。后来改用using语句确保资源释放,关键业务操作使用事务:

public static bool ExecuteTransaction(params SqlCommand[] commands)
{
    using (var transaction = GetConnection().BeginTransaction())
    {
        try
        {
            foreach (var cmd in commands)
            {
                cmd.Connection = GetConnection();
                cmd.Transaction = transaction;
                cmd.ExecuteNonQuery();
            }
            transaction.Commit();
            return true;
        }
        catch
        {
            transaction.Rollback();
            throw;
        }
    }
}

4. 登录模块实现

登录界面需要区分管理员和普通用户。在UI文件夹中添加Login窗体,包含:

  • 两个TextBox(用户名和密码)
  • 两个RadioButton(用户类型选择)
  • 一个Button(登录)

核心登录逻辑:

private void btnLogin_Click(object sender, EventArgs e)
{
    if (string.IsNullOrEmpty(txtUser.Text) || string.IsNullOrEmpty(txtPwd.Text))
    {
        MessageBox.Show("用户名或密码不能为空");
        return;
    }

    string tableName = radioAdmin.Checked ? "t_admin" : "t_user";
    string sql = $"SELECT * FROM {tableName} WHERE id=@id AND psw=@psw";
    
    var parameters = new[]
    {
        new SqlParameter("@id", txtUser.Text),
        new SqlParameter("@psw", txtPwd.Text) // 实际项目应该加密
    };

    using (var reader = Dao.ExecuteReader(sql, parameters))
    {
        if (reader.Read())
        {
            Data.UID = reader["id"].ToString();
            Data.UName = reader["name"]?.ToString();
            
            var mainForm = radioAdmin.Checked 
                ? (Form)new AdminMainForm() 
                : new UserMainForm();
            
            this.Hide();
            mainForm.ShowDialog();
            this.Show();
        }
        else
        {
            MessageBox.Show("登录失败,请检查凭证");
        }
    }
}

安全提示:实际项目中密码应该使用哈希加盐存储,SQL语句要参数化防止注入。我曾在早期版本直接拼接SQL字符串,结果被测试人员轻松注入,这个教训很深刻。

5. 图书管理模块开发

管理员界面主要包含DataGridView显示图书列表,以及增删改查功能。先创建AdminMainForm,添加MenuStrip控件设置菜单项。

图书管理窗体(AdminBookForm)的关键代码:

public partial class AdminBookForm : Form
{
    private BindingSource _bindingSource = new BindingSource();

    public AdminBookForm()
    {
        InitializeComponent();
        dgvBooks.AutoGenerateColumns = false;
        LoadBooks();
    }

    private void LoadBooks()
    {
        string sql = "SELECT isbn, name, author, publisher, stock FROM t_book";
        var adapter = new SqlDataAdapter(sql, Dao.GetConnection());
        var dataSet = new DataSet();
        adapter.Fill(dataSet, "Books");
        
        _bindingSource.DataSource = dataSet.Tables["Books"];
        dgvBooks.DataSource = _bindingSource;
    }

    private void btnAdd_Click(object sender, EventArgs e)
    {
        var form = new BookEditForm();
        if (form.ShowDialog() == DialogResult.OK)
        {
            string sql = @"INSERT INTO t_book 
                          VALUES (@isbn, @name, @author, @publisher, @stock)";
            
            var parameters = new[]
            {
                new SqlParameter("@isbn", form.ISBN),
                // 其他参数...
            };
            
            if (Dao.ExecuteNonQuery(sql, parameters) > 0)
            {
                MessageBox.Show("添加成功");
                LoadBooks();
            }
        }
    }
}

数据绑定技巧:设置DataGridView的AutoGenerateColumns为false,手动配置列与数据字段的映射关系。这样能精确控制显示格式:

private void ConfigureDataGridView()
{
    dgvBooks.Columns.Add(new DataGridViewTextBoxColumn()
    {
        DataPropertyName = "isbn",
        HeaderText = "ISBN",
        Width = 150
    });
    // 其他列配置...
}

6. 借阅功能实现

用户界面需要实现图书查询和借阅功能。在UserMainForm中添加搜索面板和图书列表:

private void btnSearch_Click(object sender, EventArgs e)
{
    string keyword = "%" + txtKeyword.Text + "%";
    string sql = @"SELECT * FROM t_book 
                  WHERE name LIKE @keyword OR author LIKE @keyword";
    
    var adapter = new SqlDataAdapter(sql, Dao.GetConnection());
    adapter.SelectCommand.Parameters.AddWithValue("@keyword", keyword);
    
    var table = new DataTable();
    adapter.Fill(table);
    dgvBooks.DataSource = table;
}

private void btnBorrow_Click(object sender, EventArgs e)
{
    if (dgvBooks.CurrentRow == null) return;
    
    string isbn = dgvBooks.CurrentRow.Cells["isbn"].Value.ToString();
    string sql = "INSERT INTO t_lend (user_id, book_isbn) VALUES (@uid, @isbn)";
    
    var parameters = new[]
    {
        new SqlParameter("@uid", Data.UID),
        new SqlParameter("@isbn", isbn)
    };
    
    try
    {
        if (Dao.ExecuteNonQuery(sql, parameters) > 0)
        {
            MessageBox.Show("借阅成功");
        }
    }
    catch (SqlException ex)
    {
        if (ex.Number == 547) // 外键约束错误
            MessageBox.Show("图书不存在");
        else
            throw;
    }
}

性能优化点:当图书数据量大时,应该实现分页查询而不是一次性加载所有数据。可以使用ROW_NUMBER()实现分页:

WITH BookPage AS (
    SELECT ROW_NUMBER() OVER (ORDER BY name) AS RowNum, *
    FROM t_book
    WHERE name LIKE @keyword
)
SELECT * FROM BookPage 
WHERE RowNum BETWEEN @start AND @end

7. 报表与统计功能

管理员通常需要查看借阅统计。我们可以使用Chart控件可视化数据:

private void LoadStats()
{
    string sql = @"SELECT b.name, COUNT(l.id) AS lend_count
                  FROM t_book b LEFT JOIN t_lend l ON b.isbn = l.book_isbn
                  GROUP BY b.name
                  ORDER BY lend_count DESC";
    
    var adapter = new SqlDataAdapter(sql, Dao.GetConnection());
    var dataSet = new DataSet();
    adapter.Fill(dataSet);
    
    chart1.Series["Series1"].Points.DataBind(
        dataSet.Tables[0].DefaultView, "name", "lend_count", "");
}

对于复杂报表,我推荐使用ReportViewer控件或第三方报表工具如DevExpress。曾经一个项目需要导出Excel报表,直接用DataTable导出性能很差,后来改用ClosedXML库效率提升明显。

8. 系统部署与维护

完成开发后,需要准备安装包。在Visual Studio中添加安装项目,包含:

  • 主程序文件
  • .NET Framework运行环境(如果目标机器没有)
  • 数据库脚本
  • 配置文件模板

数据库部署建议:

  1. 准备SQL脚本创建数据库和表
  2. 包含初始数据插入脚本(如管理员账户)
  3. 考虑添加数据库升级脚本

维护技巧:在app.config中配置连接字符串时,可以使用配置文件转换功能为不同环境(开发/测试/生产)设置不同的连接字符串:

<connectionStrings>
    <add name="BookDB" connectionString="..." providerName="System.Data.SqlClient" />
</connectionStrings>

日志记录:建议使用log4net记录系统操作日志和异常,便于故障排查。我在Dao类中添加了日志记录后,排查数据库问题效率提高了许多。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐