基于三层架构的学生选课系统.docx
《基于三层架构的学生选课系统.docx》由会员分享,可在线阅读,更多相关《基于三层架构的学生选课系统.docx(19页珍藏版)》请在冰豆网上搜索。
基于三层架构的学生选课系统
模块四基于3层架构的学生选课管理系统
模块概述
在前一个模块中,实现了基于3层的课程管理,巩固了面向对象的设计理念,引入了三层架构的设计思路和实现技巧。
在本模块中,应用三层架构实现完整的学生选课管理系统,包括管理员和学生的登录功能,学生的选课退选功能,管理员除了课程管理功能外的学生管理和选课浏览等功能。
在此过程中,进一步理解和应用三层架构原理,理解面向对象的理念。
本模块工作任务
Ø任务4-1:
用户登录
Ø任务4-2:
学生选课退选
Ø任务4-3:
管理员的其余功能实现
本模块学习目标
Ø1、熟悉三层架构的原理和应用
Ø2、理解各功能的业务流程,并在三层中设计类,逐层实现功能
Ø3、巩固OOP的基本概念和OOP的编程思路
---------------------------------------------------------------------------------------------------------------------------------
任务4-2:
学生选课退选
Ø效果与描述
学生用户登录成功后,就会将这类用户的使用界面展示出来。
如下图4.1所示:
图3.1学生选课退选界面
在此界面中,学生用户可以看到所有课程的列表,表示可以选的课程,在此表的最后一列是选课链接,按下链接,可以进行选课;选课要符合此学生尚未选此课程、学生选课总学分小于等于8、每门课程的选课人数小于等于30的前提。
若选课成功,此学生的已选课程列表、已选总学分会更新,在已选课程的最后一列是退选链接,按下链接,可以退选;退选没有条件;若退选成功,此学生的已选课程列表亦更新。
已选课程列表框中的已选总学分亦随之更新。
选课的业务流程:
图4.2选课的业务流程
退选的业务流程:
图4.3退选的业务流程
本任务的总体业务流程:
图4.4本任务总体业务流程
然后,根据业务需求,从底到高来设计每层。
依据业务流程的需要,针对选课表的数据访问层类的方法有:
1、当前可选课程是课程表的所有课程,其记录集合的取得在课程表的数据访问类;
2、当前已选课程,要根据此学生的学号,在选课表中查询此学生所选课程,这个记录集合的获取方法,也放在选课表的数据访问层类;
3、判断某学生是否选某课,既判断选课表中是否有这条记录的方法;
4、由于在选课过程中需要判断此学生的已选总学分和此课程的已选人数,所以,这2个方法也需放在这个类中。
5、选课的记录添加方法;
6、退选的记录删除方法;
依据业务流程和界面层的需要,针对选课的业务逻辑层类的方法有:
1、选课方法:
根据以上选课业务流程,调用数据访问层的方法,实现选课;
2、退选方法:
根据以上退选业务流程,调用数据访问层的方法,实现退选;
3、已选课程列表方法:
将数据访问层取得的记录集合,向上传递;
4、根据界面层需求,还需要一个返回此学生的已选总学分的方法,将数据访问层取得的统计值,向上传递;
选课退选的界面层,依据业务逻辑层的方法,设计相关控件事件中的代码,实现设计功能。
1、在选课时,根据用户当前所选课程,生成选课实体类对象,传入业务逻辑层的方法作为实参。
2、退选时,根据用户所选退选课程,生成退选必须的条件,传入业务逻辑层的方法作为实参。
Ø相关知识与技能
4-2-1学号的获取既变量值在窗体间的传递
在以上的已选课程浏览、选课、退选流程中,都需要用到此学生的学号。
也就是说,如果某学生登录成功,此学生的学号应该从登录窗体传递到后续的选课退选窗体,在选课退选窗体不再需要再次收集学号。
这样做,不仅保证了学号的准确性,也使整个系统更加方便的整合在一起。
这也是软件系统通用的惯例之一。
在Windows窗体应用程序中,如何在窗体间传递某变量的值呢?
由于所有窗体都处于UI层的同一个命名空间中。
因此:
首先将变量定义在需使用它的窗体(选课退选窗体)中,并定义其访问修饰符为internal,此变量即可在命名空间中通用。
在登录窗体中,学生用户登录成功后,实例化后选课退选窗体,将登录成功时的学号赋值给变量。
此变量的值就从登录窗体传递到了选课退选窗体,在选课退选窗体中就可以应用了。
代码如下:
在选课退选窗体中的定义:
publicpartialclassFormXKTX:
Form
{
internalstringcurrentStuId=string.Empty;
…
}
在登录窗体中,登录成功后的赋值:
if(radioButton1.Checked)
{
StudentBizsb=newStudentBiz();
if(sb.StuLogin(userId,userPassword))//登录成功
{
this.Hide();
FormXKTXxs=newFormXKTX();//实例化选课窗体
xs.currentStuId=userId;//为此窗体的学号变量赋值
xs.Show();//显示此窗体
}
else
MessageBox.Show("学号或密码错误");
}
在其他类型的应用程序中,页面之间传递变量值也是常用的技巧,只是实现的方法不同,目标都是一样的。
4-2-2数据访问类DBHelper类的重构
在选课流程中,需要计算此学生目前选课的总学分,计算当前选此课程的学生人数。
这都需要在数据访问层设计方法取得统计值。
那么,就需要到数据访问类DBHelper中调用相关方法。
来回顾一下,目前的DBHelper类:
publicclassDBHelper
{
//连接字符串字段,从配置文件取值
privatestaticreadonlystringconnectionString…
//执行INSERT、DELETE、UPDATE等非查询命令的方法
publicstaticintExecNonQuery(stringstrSQL)
//执行查询命令,取得OleDbDataReader的方法
publicstaticOleDbDataReaderGetReader(stringstrSQL)
//执行查询命令,取得DataTable的方法
publicstaticDataTableGetTable(stringstrSQL)
}
可见,目前并没有取得统计值的方法。
来回顾一下Command对象,实例化时,必须指定的属性是conn连接对象和命令语句strSQL,常用的方法是ExecuteNonQuery()、ExecuteScalar()和ExecuteReader()。
到目前为止,ExecuteScalar()方法尚未应用,此时该用了。
ExecuteScalar()方法的回顾:
在连接上执行Transact-SQL语句,并返回结果集中第一行的第一列。
忽略其他列或行。
publicstaticintGetScalar(stringstrSql)
{
intresult;
OleDbCommandcmd=newOleDbCommand(strSql,Connection);
objectobj=cmd.ExecuteScalar();
if(obj==DBNull.Value)
result=0;
else
result=Convert.ToInt32(obj);
returnresult;
}
此方法用形参strSql和连接Connection,实例化1个命令类对象,执行此对象的ExecuteScalar()方法,判断返回的Object是否为数据库空(DBNull.Value),若是,表示没统计值,返回0;否则将返回的对象转换为整型再返回。
所以,在DBHelper类中添加新方法如下:
publicstaticintGetScalar(stringstrSQL)
{
using(OleDbConnectionconn=newOleDbConnection(connectionString))
{
try
{
conn.Open();
OleDbCommandcmd=newOleDbCommand(strSQL,conn);
objectobj=cmd.ExecuteScalar();
if(obj==DBNull.Value)
{
return0;
}
else
{
returnConvert.ToInt32(obj);
}
}
catch(OleDbExceptionex)
{
thrownewException(string.Format("执行{0}失败{1}",strSQL,ex.Message));
}
}
}
定义了此方法后,在数据访问层的统计方法,就可以调用它,进行相关统计了。
4-2-3异常的捕捉
在上面的方法GetScalar()中,多了一个语句try..catch语句,是异常处理语句。
在应用程序中,语法的错误时很容易就被编译系统检测到的,但有些错误就不会被编译系统发现,但是也会造成严重的运行故障。
看下面的代码:
intx=2,y=0;
Console.WriteLine(x/y);
这段代码是可以通过编译的,但运行时异常结束,并显示:
像类似的异常很多,大部分是由于开发人员未预料到可能出现的边缘数据(如除数为0,记录集为空等)造成的,这就会造成程序的异常终止。
如何使程序可以正常结束,即使在边缘数据不可避免出现的情况下?
C#提供了异常捕捉机制来对可能出现的异常进行捕捉和保护。
异常捕捉语句:
try
{
需保护的代码段
}
catch(捕捉异常的类)
{
异常捕捉到后,显示相关信息的语句
}
表示:
如果在被保护的代码段中发生异常,则被catch段中捕捉异常的类捕捉,并显示异常相关信息,程序正常结束。
其中,System.Exception类是所有异常类的基类,可捕捉到所有的异常,此类的message描述当前异常的消息。
可以把上面除0的代码保护起来:
intx=2,y=0;
try
{
Console.WriteLine(x/y);
}
catch(Exceptione)
{
Console.WriteLine(e.Message);
}
程序仍通过编译,运行结果如下:
此时,虽然有异常,但程序仍正常的而结束,并给出异常信息。
读者应理解异常保护的方法和代码书写方法,在今后的学习中,养成给相关代码加异常保护的习惯。
在上面的GetScalar()方法中,将命令的执行和统计值的获取和返回代码在try块中保护起来,在catch块中用OleDbException异常类捕捉,以防可能会出现的数据库操作错误。
4-2-4数据网格的columns集合
在以上章节观察到,数据网格DataGridView可以用来展示数据表、数组或泛型集合中的数据,在默认情况下,数据网格中的列包含表、数组或泛型集合中的所有列。
数据网格中的列也可以自定义,在选课退选模块中,就需要自定义列。
自定义的列可以是:
DataGridViewButtonColumn列、DataGridViewCheckBoxColumn列、DataGridViewComboBoxColumn列、DataGridViewImageColumn列、DataGridViewLinkColumn列、DataGridViewTextBoxColumn列,分别表示按钮列、复选框列、下拉框列、图片列、链接列、文本列。
以前用的都是文本列,在选课退选模块中,需要用到文本列和链接列。
在数据网格的Columns集合中,选择“添加”按钮,可以添加各种类型的列,如下图所示:
1、自定义文本列
添加了自定义文本列后,除了列宽和外观的设置外,要注意列所绑定的数据库列。
由于在已选课程或可选课程的列表中,都有1列是课程号,其在数据库中的字段名为courseid,所以,此自定义列就要设计其DataPropertyName属性为courseid,进行数据库列的绑定。
2、自定义链接列
在选课退选中,“选课”和“退选”都需要做成链接列。
此时,必须注意,除了headerText和Text属性要设置为一致的文本外,UseColumnTextForLinkValue属性必须改为true,表示用指定的Text作为链接文本。
4-2-5数据网格中行值的获取
在选课退选中,都存在这种操作:
先选定数据网格的某行,再进行选课或退选。
在选课时需要选课表的实体对象,在退选时需要课程号和学号,都需要从数据网格中的行中获取数据。
从数据网格的当前行中获取指定字段的值,需要2个集合:
Rows集合表示所有行,Cells集合表示行中的所有单元格。
假设在某数据网格的CellContentClick事件中,有以下代码:
privatevoiddataGridViewkx_CellContentClick(objectsender,DataGridViewCellEventArgse)
stringcourseId=dataGridViewyx.Rows[e.RowIndex].Cells[1].Value.ToString();
当前行的下标由e.RowIndex表示,则Rows[e.RowIndex]表示当前行;每行都由Cells集合组成,Cells[1]表示当前行第1列,则以上代码表示将当前行的第1列的值取出,转换类型后,放入变量courseId。
依次类推,可以取出当前需要选课或退选的那行的所需数据,作为选课或退选的条件,在代码中调用。
4-2-6数据访问层的设计思路
1、判断某学生是否已选某课
方法名:
HasSelected
形参:
学号、课程号
返回值:
bool
方法内代码设计:
(1)设计语句select*from选课表where学号=形参学号and课程号=形参课程号
(2)利用using语句,调用DBHelper类的GetReader方法,生成一个datareader对象,利用HasRows属性判断此datareader对象是否有行,若有,返回真,否则返回假。
应用场合:
在选课前判断,若有则不能再选。
2、求某学生所选的总学分
方法名:
StudentTotalCredit
形参:
学号
返回值:
int
方法内代码设计:
(1)设计语句selectsum(course.coursecredit)astotalcreditfromcourseinnerjoincourseselectoncourse.courseid=courseselect.courseidwherecourseselect.studentid=形参学号
(2)利用using语句,调用DBHelper类的GetScalar方法,返回一个统计值。
应用场合:
在选课前判断,若超过规定总学分则不能再选。
3、求某课的已选学生人数
方法名:
CourseTotalStudent
形参:
课程号
返回值:
int
方法内代码设计:
(1)设计语句selectcount(*)astotalstudentfromcourseselectwherecourseselect.studentid=形参课程号
(2)利用using语句,调用DBHelper类的GetScalar方法,返回一个统计值。
应用场合:
在选课前判断,若超过规定人数则不能再选。
4、获取某学生已选课程列表
方法名:
SelectedCourseList
形参:
学号
返回值:
List
方法内代码设计:
(1)设计语句selectcourse.courseid,course.coursename,course.coursecreditfromcourseinnerjoincourseselectoncourse.courseid=courseselect.courseidwherecourseselect.studentid=形参学号
(2)利用using语句,调用DBHelper类的GetReader方法,生成一个datareader对象,利用Read()方法,读取记录集的每行到课程对象中,并加入集合。
返回集合。
应用场合:
获取某学生的已选课程集合。
5、添加选课
方法名:
AddSelect
形参:
选课表实体类对象
返回值:
int
方法内代码设计:
(1)设计insert语句
(2)利用using语句,调用DBHelper类的ExeNonQuery方法,执行语句,返回整型数值。
应用场合:
添加选课,根据返回值是否大于0,判断添加是否成功。
6、删除选课
方法名:
DelSelect
形参:
选课表实体类对象
返回值:
int
方法内代码设计:
(1)设计delete语句
(2)利用using语句,调用DBHelper类的ExeNonQuery方法,执行语句,返回整型数值。
应用场合:
删除选课,根据返回值是否大于0,判断添加是否成功。
4-2-7业务逻辑层的设计思路
1、选课
方法名:
AddSelect
形参:
选课表实体类对象
返回值:
void
方法内代码设计:
(1)在配置文件中加入每学生的总选课学分,和每门课的总选课人数,读取这2个设置值;
(2)从选课类实体对象中取出学号和课程号待用,并根据课程号生成课程类实体对象。
为此,需要先生成课程和选课的数据访问类对象;
(3)判断该学生是否已选该课程;
(4)判断该学生原来已选总学分加上本课程学分,是否已超过规定;
(5)判断此课程已选人数,是否已超过规定;
(6)所有条件都满足,添加选课记录,并提示是否成功。
2、退选
方法名:
DelSelect
形参:
学号、课程号
返回值:
void
方法内代码设计:
(1)删除选课记录,并提示是否成功。
3、获取某学生已选课程列表
将数据访问层取得的记录集合,向上传递。
4、求某学生所选的总学分
将数据访问层取得的总学分,向上传递。
4-2-8界面层的设计
调用业务逻辑层的方法,实现功能需求。
1、抽取1个已选课程列表绑定方法:
利用选课业务逻辑类,将已选集合绑定到已选课程列表的数据网格,并将学生的已选总学分,显示在界面上。
2、抽取1个可选课程列表方法:
利用课程的业务逻辑类,将所有课程集合,绑定到可选课程列表的数据网格。
3、窗体Load事件发生时,利用以上2个方法,绑定2个数据网格。
4、窗体FormClosed事件发生时,关闭整个应用程序。
5、按下“选课”链接,生成选课实体,添加选课。
刷新已选信息。
6、按下“退选”链接,删除选课。
刷新已选信息。
Ø任务的设计思路
1、按以上思路,设计选课的数据访问类SelectAccess.cs文件,加入相应方法。
2、按以上思路,设计选课的业务逻辑类SelectBiz.cs文件,加入相应方法。
3、按以上思路,在界面层设计2个自定义方法和4个事件相应方法,将相关代码放入。
Ø任务的实施
1、App.Config文件
--每个学生最多能选多少学分-->
--每门课最多可以有多少位学生-->
2、SelectAccess.cs文件
publicclassSelectAccess
{
publicboolHasSelected(stringstudentId,stringcourseId)
{
stringstrSql=string.Format("select*fromcourseselectwherecourseid='{0}'andstudentid='{1}'",courseId,studentId);
using(OleDbDataReaderdr=DBHelper.GetReader(strSql))
{
if(dr.HasRows)
returntrue;
else
returnfalse;
}
}
///
///求某学生所选的总学分
///
///
///
publicintStudentTotalCredit(stringstudentId)
{
stringstrSql=string.Format("selectsum(course.coursecredit)astotalcreditfromcourseinnerjoincourseselectoncourse.courseid=courseselect.courseidwherecourseselect.studentid='{0}'",studentId);
inttotal=DBHelper.GetScalar(strSql);
returntotal;
}
///
///求某课程的选课人数
///
///
///
publicintCourseTotalStudent(stringcourseId)
{
stringstrSql=string.Format("selectcount(studentid)fromcourseselectwherecourseid='{0}'",courseId);
inttotal=DBHelper.GetScalar(strSql);
returntotal;
}
///
///获得某学生已选课程列表
///
///
///
publicListSelectedCourseList(stringstudentId)
{
stringstrSql=string.Format("selectcourse.courseidasid,course.coursenameasname,course.coursecreditascreditfromcourseinnerjoincourseselectoncourse.courseid=courseselect.courseidwherecourseselect.student