年底用户提出新的需求,为了提高教学质量,某二级学院要求一个有个性的评教问卷,于是我就临时组建了一个攻坚小组,做一个评教系统。基本的需求是有单选题,多选题,填空题。目前多选题还没有实现,只是实现的单选题的功能。在做功能中,恰逢我们计算机一级考试系统验收。我有一点深刻的体会,那就是不要让你的系统进行太多次的IO操作,争取每一个的IO操作都得到最高的利用。
评教系统相比考试系统而言数据量也是不相上下的。如果一条选择题都要去打开一次数据库连接池,那么再高配的数据库也承受不住100人的正常提交。我发现前辈就是前辈,看到评教代码,就想为前辈们点赞。评教数据的提交都是以整张表为单位进行的提交,比如说,一个考生完成了10道选择题,我们将创建一个DataTable来存放这10个选择题,然后一起提交整个DataTable,这样1次IO完成10条记录的insert,在性能上有了很大的提高。
1.前台提交评教结果按钮 代码:
protected void btnAddEvaluation_Click(object sender, EventArgs e) { //评分主页业务逻辑层 EvaluateTeacherBLL evaluateTeacherBLL = new EvaluateTeacherBLL(); //提交评教答案,跳入考试界面 #region 创建单项选择成绩表,并写入T_SingleAnswer--周洲--2015年12月31日 //创建选修课成绩表,并写入成绩 DataSet dsExperimentEvaluation = new DataSet("ds_ExperimentEvaluation"); //手动创建一个名为“ds_ExperimentEvaluation”的DataSet文件 DataTable dtExperimentEvaluation = new DataTable("dt_ExperimentEvaluation"); //为dt_ExperimentEvluation表内建立Column(表头),添加数据列: dtExperimentEvaluation.Columns.Add(new DataColumn("studentID", typeof(string))); dtExperimentEvaluation.Columns.Add(new DataColumn("singleChoiceID", typeof(string))); dtExperimentEvaluation.Columns.Add(new DataColumn("singleAnswer", typeof(string))); dtExperimentEvaluation.Columns.Add(new DataColumn("setDatetime", typeof(DateTime))); dtExperimentEvaluation.Columns.Add(new DataColumn("isAvailable", typeof(string))); //定义一个数组,存放隐藏控件(隐藏控件存放考核项目ID、选项ID、分数) string[] oneExperimentAssessProject = hidScore.Value.Split('#'); //依次取出考核项目ID、选项ID、分数 for (int i = 1; i < oneExperimentAssessProject.Count(); i++) { string studentid = Session["StudentID"].ToString(); //考核项目ID string singleChoiceID = oneExperimentAssessProject[i].Split('$')[0]; //选项ID string singleOptionID = oneExperimentAssessProject[i].Split('$')[1]; #region 一条信息 //添加评分信息表的新行 DataRow drAddTheoryEvaluation = dtExperimentEvaluation.NewRow();//注意这边创建dt的新行的方法。指定类型是DataRow而不是TableRow,然后不用new直接的用创建的DataTable下面的NewRow方法。 //考核项目信息表对应的各列值 drAddTheoryEvaluation["studentID"] = studentid; //学号列 drAddTheoryEvaluation["singleChoiceID"] = singleChoiceID; //考核项目ID drAddTheoryEvaluation["singleAnswer"] = singleOptionID; //选项ID drAddTheoryEvaluation["setDatetime"] = DateTime.Now; //当前日期时间 drAddTheoryEvaluation["isAvailable"] = "Y"; //是否可用 dtExperimentEvaluation.Rows.Add(drAddTheoryEvaluation); //将一整条数据写入表中 #endregion } dsExperimentEvaluation.Tables.Add(dtExperimentEvaluation); //写入实验课成绩 //dsExperimentEvaluation.Tables.Add(dtSuggestion); //希尔建议 #endregion //将单选题答案传回数据库中 Boolean flagExpScore = evaluateTeacherBLL.AddSingleScore( dsExperimentEvaluation); if (true == flagExpScore) { Page.ClientScript.RegisterStartupScript(Page.GetType(), "message", "<script language='javascript' defer>alert('问卷提交成功!将跳入考试系统');</script>"); } else { Page.ClientScript.RegisterStartupScript(Page.GetType(), "message", "<script language='javascript' defer>alert('评分失败');</script>"); } }
/// <summary> /// 将选择题填入数据库中--周洲--2015年12月31日11:47:48 /// </summary> /// <param name="enstudent"></param> /// <param name="dsExpScore"></param> /// <returns></returns> public Boolean AddSingleScore(DataSet dsExpScore){ // experimentProjectScoreDAL = new ExperimentProjectScoreDAL(); singleDAL =new SingleDAL(); //实例化单项选择题操作类 //定义事务执行所使用的链接 SqlConnection sqlCon = new SqlConnection(ConfigurationManager.ConnectionStrings["strConnDB"].ConnectionString); //打开连接 sqlCon.Open(); //定义事务 SqlTransaction sqlTran = sqlCon.BeginTransaction(IsolationLevel.ReadCommitted); //用try...Catch...finally保证事务在出错时会回滚 try { //修改评估结果 //添加实验课成绩 //Boolean flagAddExpScore = experimentProjectScoreDAL.AddExperimentProjectScore(dsExpScore, sqlCon, sqlTran); Boolean flagAddExpScore = singleDAL.AddSingleScore(dsExpScore, sqlCon, sqlTran); // Boolean flagEditIsEvaluation = teacherCourseStudentLinkDAL.EditIsEvaluation(enTeacherCourseStudent, sqlCon, sqlTran); //判断评分是否成功 if ( flagAddExpScore) { //如果都为真,提交 sqlTran.Commit(); return true; //添加成功 } else { sqlTran.Rollback(); return false; //添加失败 } } catch (Exception) { //出现异常时,事物回滚 sqlTran.Rollback(); return false; } finally { sqlCon.Close(); } }
3.D层调用sqlhelper将记录送入数据库表里。
public Boolean AddSingleScore(DataSet dsScore, SqlConnection sqlCon, SqlTransaction sqlTran) { //定义布尔型标记变量, //添加实验课成绩信息 Boolean flagAddExpScore; //调用sqlHelper的"批量导入datatable表"的方法 flagAddExpScore = sqlHelper.InsertTable(dsScore.Tables["dt_ExperimentEvaluation"], "T_SingleAnswer", dsScore.Tables["dt_ExperimentEvaluation"].Columns, sqlCon, sqlTran); //返回结果 return (flagAddExpScore); }
#region 批量导入DataTable 使用事务 /// <summary> /// 批量导入DataTable 使用事务 /// </summary> /// <param name="dt">DataTable数据表</param> /// <param name="tableName">表名</param> /// <param name="dtColum">列名</param> public Boolean InsertTable(DataTable dt, string tableName, DataColumnCollection dtColum, SqlConnection sqlConns, SqlTransaction sqlTran) { try { //声明SqlBulkCopy ,using释放非托管资源 using (SqlBulkCopy sqlBC = new SqlBulkCopy(sqlConns, SqlBulkCopyOptions.CheckConstraints, sqlTran)) { //一次批量的插入的数据量 //sqlBC.BatchSize = 1000; //超时之前操作完成所允许的秒数,如果超时则事务不会提交 ,数据将回滚,所有已复制的行都会从目标表中移除 //sqlBC.BulkCopyTimeout = 60; //設定 NotifyAfter 属性,以便在每插入10000 条数据时,呼叫相应事件。 //sqlBC.NotifyAfter = 10000; // sqlBC.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnSqlRowsCopied); //设置要批量写入的表 sqlBC.DestinationTableName = tableName; //自定义的datatable和数据库的字段进行对应 //sqlBC.ColumnMappings.Add("id", "tel"); //sqlBC.ColumnMappings.Add("name", "neirong"); for (int i = 0; i < dtColum.Count; i++) { sqlBC.ColumnMappings.Add(dtColum[i].ColumnName.ToString(), dtColum[i].ColumnName.ToString()); } //批量写入 sqlBC.WriteToServer(dt); } //conn.Dispose(); //GetConn(); return true; } catch { return false; } }
1.尽量减少IO操作,提高IO操作的利用率。
2. 使用事务保证一组sql语句执行的完整性,避免造成很多的脏数据。
3. NOLOCK能使当前会话的查询,不受其它会话的事务所阻塞。但是这样做,就读取了其它事务没有执行完产生的脏数据。