评教系统优化之使用事务批量导入DataTable

前提: 

  年底用户提出新的需求,为了提高教学质量,某二级学院要求一个有个性的评教问卷,于是我就临时组建了一个攻坚小组,做一个评教系统。基本的需求是有单选题,多选题,填空题。目前多选题还没有实现,只是实现的单选题的功能。在做功能中,恰逢我们计算机一级考试系统验收。我有一点深刻的体会,那就是不要让你的系统进行太多次的IO操作,争取每一个的IO操作都得到最高的利用。


  评教系统相比考试系统而言数据量也是不相上下的。如果一条选择题都要去打开一次数据库连接池,那么再高配的数据库也承受不住100人的正常提交。我发现前辈就是前辈,看到评教代码,就想为前辈们点赞。评教数据的提交都是以整张表为单位进行的提交,比如说,一个考生完成了10道选择题,我们将创建一个DataTable来存放这10个选择题,然后一起提交整个DataTable,这样1次IO完成10条记录的insert,在性能上有了很大的提高。


评教系统优化之使用事务批量导入DataTable_第1张图片


实现代码:


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>");
               

            }
        }

2.逻辑层,将DataTable送入,其中使用了事务来保证整个插入操作不会被打断,如果发生意外就回滚到执行插入之前的状态。

   /// <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);
        }


   InserTable方法,传入表名,列名就可以了。

#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能使当前会话的查询,不受其它会话的事务所阻塞。但是这样做,就读取了其它事务没有执行完产生的脏数据。                                                                                                                                             



你可能感兴趣的:(优化,数据库,数据库连接池,事务,dataset)