核心内容:包含分页查询、新增学生、修改学生信息、删除学生4个存储过程,修正原代码潜在问题,补充详细注释,适配 C# WinForm 调用场景
适配表结构:StudentInfo(StudentId, StudentName, Score)(StudentId 为自增主键)
一、分页查询存储过程(usp_test3)
实现带条件筛选的分页查询,返回指定页数据,通过输出参数返回总页数,适配 VS 端分页逻辑调用。
-- 1. 判断分页存储过程是否存在,存在则删除
if exists(select * from sysobjects where name ='usp_test3')
drop procedure usp_test3;
go
-- 2. 创建分页存储过程
-- 参数说明:
-- @pagesize:输入参数,每页显示记录条数(由VS传递)
-- @currentpage:输入参数,当前页码(由VS传递)
-- @where:输入参数,筛选条件(带and前缀,如:and StudentName like '%张%')
-- @totalPage:输出参数,返回筛选后的总页数(供VS端分页按钮使用)
create procedure usp_test3
@pagesize int,
@currentpage int,
@where varchar(max),
@totalPage int output
as
begin
-- 关闭计数消息返回,提升执行效率,避免返回无关的受影响行数
SET NOCOUNT ON;
-- 定义变量
declare @sql1 nvarchar(max); -- 存储分页查询的动态SQL语句
declare @sql2 nvarchar(max); -- 存储统计总记录数的动态SQL语句
declare @total int; -- 存储筛选后的总记录数
declare @offset int; -- 存储分页时需要跳过的记录数
-- 计算跳过的记录数:分页核心公式(当前页-1)*每页条数
Set @offset = (@currentpage - 1) * @pagesize;
-- 初始化分页查询SQL(N前缀:识别为Unicode编码,避免中文乱码)
set @sql1 = N'select * from StudentInfo where 1=1 ';
-- 初始化统计总记录数的SQL(通过@count变量接收统计结果)
set @sql2 = N'select @count = count(*) from StudentInfo where 1=1 ';
-- 拼接筛选条件:若筛选条件非空(去除前后空格后长度不为0),则拼接至两个SQL后
if(len(trim(@where)) <> 0)
begin
set @sql1 += @where; -- 拼接分页查询的筛选条件
set @sql2 += @where; -- 拼接统计总记录数的筛选条件
end
-- 完善分页查询SQL:添加排序+OFFSET-FETCH分页条件
-- 注意:OFFSET-FETCH必须配合ORDER BY使用,否则会报错
set @sql1 += N' order by Score offset ' + CAST(@offset as nvarchar)
+ N' rows fetch next ' + CAST(@pagesize as nvarchar) + N' rows only';
-- 执行分页查询SQL,返回当前页数据
exec(@sql1);
-- 执行统计总记录数的SQL,通过sp_executesql传递输出参数,获取总记录数
-- 参数说明:@sql2(执行的SQL)、N'@count int output'(参数定义)、@total output(接收输出值)
exec sp_executesql @sql2, N'@count int output', @total output;
-- 计算总页数:总页数=总记录数/每页条数,若有余数则加1
if(@total % @pagesize = 0)
set @totalPage = @total / @pagesize; -- 无余数,总页数为商
else
set @totalPage = @total / @pagesize + 1; -- 有余数,总页数为商+1
-- 开启计数消息返回,恢复默认设置
SET NOCOUNT OFF;
end
go
-- 3. 验证分页存储过程
declare @f1 int; -- 定义变量,接收总页数(输出参数)
-- 调用存储过程:每页3条,查询第2页,无筛选条件
exec usp_test3 3, 2, '', @f1 output;
print '总页数:' + CAST(@f1 as nvarchar); -- 打印总页数
print '------------------------';
-- 带筛选条件的验证示例(查询姓名含"张"的记录,每页3条,查询第1页)
-- declare @f2 nvarchar(max);
-- set @f2 = ' and StudentName like ''%张%'' '; -- 注意:单引号需转义(两个单引号表示一个)
-- exec usp_test3 3, 1, @f2, @f1 output;
-- print '筛选后总页数:' + CAST(@f1 as nvarchar);
二、新增学生信息存储过程(p_stundentInfo_add)
实现学生信息的新增功能,接收学生姓名和成绩,插入到 StudentInfo 表中。
-- 1. 判断新增存储过程是否存在,存在则删除
if exists(select * from sysobjects where name ='p_stundentInfo_add')
drop procedure p_stundentInfo_add;
go
-- 2. 创建新增存储过程
-- 参数说明:
-- @studentName:输入参数,学生姓名
-- @score:输入参数,学生成绩
create procedure p_stundentInfo_add
@studentName varchar(10),
@score int
as
begin
SET NOCOUNT ON; -- 关闭计数消息返回
-- 插入数据到StudentInfo表(StudentId为自增主键,无需手动赋值)
insert into StudentInfo(StudentName, Score) values(@studentName, @score);
SET NOCOUNT OFF;
end
go
-- 3. 验证新增存储过程
exec p_stundentInfo_add 'zs是是是', 100; -- 调用存储过程,新增一条学生记录
select * from StudentInfo; -- 查询表,验证新增结果
三、修改学生信息存储过程(p_studentinfo_update)
根据学生ID修改学生姓名和成绩,实现学生信息的编辑功能。
-- 1. 判断修改存储过程是否存在,存在则删除
IF EXISTS(SELECT * FROM sys.procedures WHERE name='p_studentinfo_update')
DROP PROC p_studentinfo_update;
GO
-- 2. 创建修改存储过程
-- 参数说明:
-- @StudentId:输入参数,学生ID(修改依据,主键)
-- @StudentName:输入参数,修改后的学生姓名
-- @Score:输入参数,修改后的学生成绩
CREATE PROC p_studentinfo_update
@StudentId int,
@StudentName varchar(50),
@Score int
AS
BEGIN
SET NOCOUNT ON; -- 关闭计数消息返回
-- 根据学生ID更新姓名和成绩
UPDATE StudentInfo
SET StudentName = @StudentName, Score = @Score
WHERE StudentId = @StudentId;
SET NOCOUNT OFF;
END
GO
-- 3. 验证修改存储过程
exec p_studentinfo_update 1002, 'pis1', 10; -- 调用存储过程,修改StudentId=1002的学生信息
select * from StudentInfo; -- 查询表,验证修改结果
四、删除学生信息存储过程(p_studentinfo_delete)
根据学生ID删除对应的学生记录,实现学生信息的删除功能。
-- 1. 判断删除存储过程是否存在,存在则删除
IF EXISTS(SELECT * FROM sys.procedures WHERE name='p_studentinfo_delete')
DROP PROC p_studentinfo_delete;
GO
-- 2. 创建删除存储过程
-- 参数说明:
-- @StudentId:输入参数,学生ID(删除依据,主键)
CREATE PROC p_studentinfo_delete
@StudentId int
AS
BEGIN
SET NOCOUNT ON; -- 关闭计数消息返回
-- 根据学生ID删除记录
DELETE FROM StudentInfo WHERE StudentId = @StudentId;
SET NOCOUNT OFF;
END
GO
-- 3. 验证删除存储过程
exec p_studentinfo_delete 1002; -- 调用存储过程,删除StudentId=1002的学生记录
select * from StudentInfo; -- 查询表,验证删除结果
核心优化与注意事项(必背)
1. 关键优化点
-
添加
SET NOCOUNT ON/OFF:关闭/开启计数消息返回,提升存储过程执行效率,避免返回无关的受影响行数。 -
补充参数说明:为每个存储过程的参数添加注释,明确参数用途、类型,适配复习和开发调用。
-
规范验证示例:补充带筛选条件的分页调用示例,注释转义字符的使用(单引号转义),避免语法错误。
-
统一判断方式:兼容
sysobjects和sys.procedures两种判断存储过程存在的方式,适配不同SQL Server版本。
2. 易错踩坑点
-
分页存储过程中,动态SQL拼接时遗漏N前缀,导致中文筛选条件乱码;未添加ORDER BY子句,导致OFFSET-FETCH语法报错。
-
筛选条件拼接时,未带and前缀(如直接写
StudentName like '%张%'),导致拼接后SQL变为where 1=1StudentName like '%张%',出现语法错误。 -
新增存储过程中,若StudentName字段长度不足,插入过长姓名会报错(需根据实际表结构调整参数长度)。
-
修改、删除存储过程中,未传入StudentId或传入不存在的StudentId,会导致修改/删除无效果(无报错,需添加判断优化)。
-
动态SQL执行时,使用exec而非sp_executesql,导致无法传递输出参数,无法获取总记录数;同时存在SQL注入风险。
3. VS端调用注意事项(适配C#)
-
调用分页存储过程时,输出参数需指定参数方向为
ParameterDirection.Output,获取总页数后计算分页按钮状态。 -
调用增删改存储过程时,确保传入的参数类型与存储过程定义一致(如StudentId为int,StudentName为字符串)。
-
传递筛选条件时,需拼接and前缀,特殊字符(如单引号)需转义,避免SQL语法错误和SQL注入。
-
调用完成后,刷新DataGridView数据,同步展示增删改后的结果。
C# WinForm 存储过程实现分页和增删改查
优化说明:补充异常处理、规范变量命名、增加输入验证、优化代码注释,解决原代码中潜在的空值、类型转换异常问题,适配实际开发场景。
using Microsoft.ApplicationBlocks.Data.Ch;
using System;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
namespace 使用存储过程实现分页和增删改查
{
public partial class Form1 : Form
{
// 数据库连接字符串(建议放到配置文件中,此处为演示)
private readonly string _connString = "server=.,1344;database=BankSystem;uid=sa;pwd=123456";
// 分页相关变量(规范命名,增加可读性)
private int _currentPage = 1; // 当前页码
private int _pageSize = 3; // 每页显示条数
private int _totalPage = 0; // 总页数
public Form1()
{
InitializeComponent();
// 初始化分页下拉框,默认选中第一项(3条/页)
cbbPageSize.SelectedIndex = 0;
}
/// <summary>
/// 窗体加载事件:首次绑定数据表格
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Load(object sender, EventArgs e)
{
BindGridView();
}
/// <summary>
/// 核心方法:绑定数据表格(分页查询+筛选)
/// </summary>
public void BindGridView()
{
try
{
// 拼接筛选条件(带and前缀,适配存储过程的where拼接逻辑)
string whereCondition = string.Empty;
// 姓名筛选:非空则拼接模糊查询条件
if (!string.IsNullOrWhiteSpace(txtStudentName.Text))
{
whereCondition += $" and StudentName like '%{txtStudentName.Text.Trim()}%'";
}
// 最低分筛选:大于0则拼接范围条件
if (nudScoreStart.Value > 0)
{
whereCondition += $" and Score >= {nudScoreStart.Value}";
}
// 最高分筛选:大于0则拼接范围条件
if (nudScoreEnd.Value > 0)
{
whereCondition += $" and Score <= {nudScoreEnd.Value}";
}
// 定义存储过程参数(输入+输出)
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter("@currentpage", SqlDbType.Int) { Value = _currentPage },
new SqlParameter("@pagesize", SqlDbType.Int) { Value = _pageSize },
new SqlParameter("@where", SqlDbType.VarChar) { Value = whereCondition },
new SqlParameter("@totalPage", SqlDbType.Int) { Direction = ParameterDirection.Output } // 输出参数:总页数
};
// 调用分页存储过程,获取数据集
DataSet dataSet = SqlHelper.ExecuteDataset(
_connString,
CommandType.StoredProcedure,
"usp_test3",
parameters
);
// 获取输出参数(总页数),处理空值异常
_totalPage = parameters[3].Value != DBNull.Value
? int.Parse(parameters[3].Value.ToString())
: 0;
// 绑定数据到DataGridView,处理空表情况
dataGridView1.DataSource = dataSet.Tables[0];
// 更新分页显示标签
lblPageAndTotalPage.Text = $"当前页:{_currentPage} / 总页数:{_totalPage}";
// 禁用/启用分页按钮(优化用户体验)
btnFirst.Enabled = _currentPage > 1;
btnPrev.Enabled = _currentPage > 1;
btnNext.Enabled = _currentPage < _totalPage;
btnLast.Enabled = _currentPage < _totalPage;
}
catch (Exception ex)
{
// 捕获异常并提示,避免程序崩溃
MessageBox.Show($"数据绑定失败:{ex.Message}", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 分页条数下拉框选择事件:修改每页显示条数,重置为第一页
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cbbPageSize_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
// 转换下拉框选中值,更新每页显示条数
_pageSize = int.Parse(cbbPageSize.SelectedItem.ToString());
_currentPage = 1; // 切换每页条数后,重置为第一页
BindGridView(); // 重新绑定数据
}
catch (Exception ex)
{
MessageBox.Show($"分页条数设置失败:{ex.Message}", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 搜索按钮点击事件:根据筛选条件查询,重置为第一页
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSearch_Click(object sender, EventArgs e)
{
// 重置当前页为第一页,重新绑定数据
_currentPage = 1;
BindGridView();
}
/// <summary>
/// 新增按钮点击事件:调用新增存储过程
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAdd_Click(object sender, EventArgs e)
{
try
{
// 输入验证:姓名和成绩不能为空
string studentName = textBox1.Text.Trim();
if (string.IsNullOrEmpty(studentName))
{
MessageBox.Show("请输入学生姓名!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
textBox1.Focus(); // 聚焦到姓名输入框
return;
}
if (string.IsNullOrWhiteSpace(textBox2.Text))
{
MessageBox.Show("请输入学生成绩!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
textBox2.Focus(); // 聚焦到成绩输入框
return;
}
// 成绩类型转换,处理转换异常
if (!int.TryParse(textBox2.Text, out int score))
{
MessageBox.Show("成绩必须是整数!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
textBox2.Focus();
return;
}
// 定义新增存储过程参数
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter("@studentName", SqlDbType.VarChar) { Value = studentName },
new SqlParameter("@score", SqlDbType.Int) { Value = score }
};
// 调用新增存储过程,获取受影响行数
int affectedRows = SqlHelper.ExecuteNonQuery(
_connString,
CommandType.StoredProcedure,
"p_stundentInfo_add",
parameters
);
// 判断新增是否成功
if (affectedRows > 0)
{
MessageBox.Show("新增学生信息成功!", "成功提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
// 清空输入框,提升用户体验
textBox1.Clear();
textBox2.Clear();
// 重新绑定数据,刷新表格
BindGridView();
}
else
{
MessageBox.Show("新增学生信息失败,请检查参数!", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
MessageBox.Show($"新增失败:{ex.Message}", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 编辑按钮点击事件:调用修改存储过程
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnEdit_Click(object sender, EventArgs e)
{
try
{
// 验证是否选中数据行
if (dataGridView1.CurrentRow == null || dataGridView1.CurrentRow.Index< 0)
{
MessageBox.Show("请先选中要编辑的学生信息行!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 获取选中行的主键(StudentId),处理空值异常
if (dataGridView1.CurrentRow.Cells["StudentId"].Value == DBNull.Value)
{
MessageBox.Show("选中行的学生ID无效!", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
int studentId = Convert.ToInt32(dataGridView1.CurrentRow.Cells["StudentId"].Value);
// 输入验证
string studentName = textBox1.Text.Trim();
if (string.IsNullOrEmpty(studentName))
{
MessageBox.Show("请输入学生姓名!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
textBox1.Focus();
return;
}
if (!int.TryParse(textBox2.Text, out int score))
{
MessageBox.Show("成绩必须是整数!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
textBox2.Focus();
return;
}
// 定义修改存储过程参数
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter("@StudentId", SqlDbType.Int) { Value = studentId },
new SqlParameter("@StudentName", SqlDbType.VarChar, 50) { Value = studentName },
new SqlParameter("@Score", SqlDbType.Int) { Value = score }
};
// 调用修改存储过程
SqlHelper.ExecuteNonQuery(
_connString,
CommandType.StoredProcedure,
"p_studentinfo_update",
parameters
);
MessageBox.Show("修改学生信息成功!", "成功提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
// 清空输入框
textBox1.Clear();
textBox2.Clear();
// 重新绑定数据
BindGridView();
}
catch (Exception ex)
{
MessageBox.Show($"修改失败:{ex.Message}", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 删除按钮点击事件:调用删除存储过程
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnDelete_Click(object sender, EventArgs e)
{
try
{
// 验证是否选中数据行
if (dataGridView1.CurrentRow == null || dataGridView1.CurrentRow.Index < 0)
{
MessageBox.Show("请先选中要删除的学生信息行!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 获取选中行的主键(StudentId)
if (dataGridView1.CurrentRow.Cells["StudentId"].Value == DBNull.Value)
{
MessageBox.Show("选中行的学生ID无效!", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
int studentId = Convert.ToInt32(dataGridView1.CurrentRow.Cells["StudentId"].Value);
// 确认删除(防止误操作)
DialogResult result = MessageBox.Show(
$"确定要删除ID为{studentId}的学生信息吗?",
"确认删除",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question
);
if (result != DialogResult.Yes)
{
return; // 用户取消删除,直接返回
}
// 定义删除存储过程参数
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter("@StudentId", SqlDbType.Int) { Value = studentId }
};
// 调用删除存储过程
SqlHelper.ExecuteNonQuery(
_connString,
CommandType.StoredProcedure,
"p_studentinfo_delete",
parameters
);
MessageBox.Show("删除学生信息成功!", "成功提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
// 处理分页:删除后如果当前页是最后一页且无数据,页码减1
if (_currentPage > _totalPage)
{
_currentPage = _totalPage > 0 ? _totalPage : 1;
}
// 重新绑定数据
BindGridView();
}
catch (Exception ex)
{
MessageBox.Show($"删除失败:{ex.Message}", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 首页按钮点击事件:跳转到第一页
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnFirst_Click(object sender, EventArgs e)
{
_currentPage = 1;
BindGridView();
}
/// <summary>
/// 上一页按钮点击事件:跳转到上一页
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnPrev_Click(object sender, EventArgs e)
{
if (_currentPage > 1)
{
_currentPage--;
BindGridView();
}
}
/// <summary>
/// 下一页按钮点击事件:跳转到下一页
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnNext_Click(object sender, EventArgs e)
{
if (_currentPage< _totalPage)
{
_currentPage++;
BindGridView();
}
}
/// <summary>
/// 末页按钮点击事件:跳转到最后一页
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLast_Click(object sender, EventArgs e)
{
_currentPage = _totalPage;
BindGridView();
}
/// <summary>
/// 跳转按钮点击事件:跳转到指定页码
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnGo_Click(object sender, EventArgs e)
{
try
{
// 获取指定页码,验证有效性
int targetPage = int.Parse(nudPage.Text);
if (targetPage < 1 || targetPage > _totalPage)
{
MessageBox.Show($"请输入1到{_totalPage}之间的页码!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
_currentPage = targetPage;
BindGridView();
}
catch (FormatException)
{
MessageBox.Show("页码必须是整数!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
catch (Exception ex)
{
MessageBox.Show($"页码跳转失败:{ex.Message}", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
核心优化点说明
-
异常处理:所有数据库操作、类型转换操作都添加 try-catch 捕获异常,避免程序崩溃,同时给出友好的错误提示。
-
输入验证:新增、编辑时验证姓名和成绩的输入有效性,处理空值、非整数等异常情况,提升用户体验。
-
变量规范:优化变量命名(如 _currentPage、_pageSize),增加可读性,符合 C# 命名规范。
-
分页优化:根据当前页码禁用/启用分页按钮,避免无效点击;处理删除后页码越界问题。
-
空值处理:处理数据库返回的 DBNull 值、DataGridView 单元格空值,避免类型转换异常。
-
用户体验优化:操作成功后清空输入框、聚焦到对应输入框,给出明确的成功/失败提示。
适配注意事项
-
确保项目中已引用
Microsoft.ApplicationBlocks.Data.Ch程序集(SqlHelper 所在程序集)。 -
检查数据库连接字符串中的服务器地址、端口、账号密码,确保与实际数据库环境一致。
-
确保数据库中已创建对应的4个存储过程(分页、新增、修改、删除),存储过程名称与代码中一致。
-
DataGridView 中需包含
StudentId列(主键列),否则编辑、删除功能会报错。