小寒的blog
Programming is a darned hard thing—but I am going to like it.

试题管理是整个系统的核心。为了尽量的从逻辑上组织试题和尽量的减少数据冗余。在线考试系统把领域对象划分地非常细。下图4-7说明了试题库相关领域类的设计


图4-7 试题库模块类图

这些领域类最终都要保存到数据库表里。为了方便管理将所有QuestionContent类的所有子类全部保存到QuestionContent表中。并用Type字段来区分不同的题型。Choice侧以XML的格式保存到Choices字段中。并编写了自定义的ChoiceList类来负责Choice集合从字段中的XML格式到对象集合的转换

namespace ExaminationSystem.DAL.UserType
{
    public class ChoiceList : IUserType
{
        
//代码省略…
}

}

该类在领域类的NHibernate映射文件中配置使用。告诉NHibernate框架如何处理Choices字段。下图4-8是QuestionContent表的设计

图4-8 QuestionContent表



为了支持章节的树形模型。也就是说每个章节可以包含多个子章节。我们的Chapter表中有一个ParentID字段用来指明该章节的父章节。如下图4-9



4-9 Chapter

Subject表如下图4-10:

4-10 Subject

有了领域类的关系和对应的表关系。当需要加载领域类的时候必须把表里的记录转换成内存中的对象。当要保存领域类时候又必须把内存对象保存成记录。这些任务都有NHibernate自动来完成。我们要做的就是告诉NHibernate如何来做这两种不同风格数据表现的转换和映射。我们通过映射文件来告诉NHibernate那个类应该保存到那个表里。那个属性应该保存在那个字段里或者保存到其他表里。反之亦然!下面就用Subject,Chapter来说明类到表是如何映射的。

<?XML version="1.0" encoding="utf-8" ?>
<hibernate-mapping XMLns="urn:nhibernate-mapping-2.2" assembly="ExaminationSystem.BLL" namespace="ExaminationSystem.BLL.Domain">
  
<class name="Subject" table="Subject" lazy="false">
    
<id name="ID" column="ID">
      
<generator class="identity" />
    
</id>
    
<property name="Name" column="Name" />
    
<bag name="Chapters" table="Chapter" inverse="true" cascade="all">
      
<key column="SubjectID"/>
      
<one-to-many class="Chapter"/>
    
</bag>
    
<bag name="PaperStrategys" table="PaperStrategy" inverse="true" cascade="all">
      
<key column="SubjectID"/>
      
<one-to-many class="PaperStrategy"/>
    
</bag>
  
</class>
</hibernate-mapping>

以上是Subject.hbm.XML的完整代码其中

<class name="Subject" table="Subject" lazy="false">

节点告诉NHibernate Subject类放到Subject表。

<id name="ID"column="ID">

      <generator class="identity" />

</id>

告诉NHibernateID属性是Subject的标识属性,对应到表ID自增长字段。

<property name="Name" column="Name" />

告诉Nhibernate Name属性保存到Name字段。

    <bag name="Chapters" table="Chapter" inverse="true" cascade="all">

      <key column="SubjectID"/>

      <one-to-many class="Chapter"/>

</bag>

这里的Chapters属性是一个Chapter对象的集合。这些Chapter对象保存到Chapter表中。并且是通过Chapter表的SubjectId类来标识Chapter表的记录属于那个Subject对象。这里只给了Subject类的映射文件。并且只是简单的说明了一般属性的映射和一到多关系的映射。关于更加复杂的类继承关系映射和自定义用户类型的映射、多对多关联的映射、延迟加载等更高级的主题请参考NHibernate官方文档。通过映射文件NHibernate完成了类到表,表到类的转换。让我们可以用面向对象的方式设计和实现系统。使系统更加的健壮和灵活。下面介绍关于QuestionContent及其所有子类的更新和删除问题。为了减少数据冗余我们将试题分成了两种不同的概念。分别是QuestionQuestionContent Question代表的是试卷上的试题。下面用类图来说明QuestionQuestionContent的关系。如图4-11:






图4-11 Question和QuestionContent关系类图


QuestionContent包含了题目的内容和标准答案、难度等信息。Question类包含分值和学生答案和得分。这样分离可以使每个试题的内容和标准答案只保留一份。比如当有100张试卷。且每张试卷都只有一样的一道题。会有100个不同Question对象.但是只要一个QuestionContent对象。这样设计无疑会大大的减少数据的冗余。但是也带来了一些问题。比如,如果试卷Paper上的Question对象上引用了一个QuestionContent的对象。我们将无法删除这个QuestionContent对象。因为Question对象对QuestionContent对象的引用在数据库中是通过外键来引用的。如果我们删除一个被引用着的QuestionContent对象将违反外键约束。而且即使没有外键约束在逻辑上也不应该删除修改这个QuestionContent对象。假设一个学生做过了一个题A,题的内容是B。学生答对了该题目。学生根据分值得到了100分。如图4-12:


图4-12 Question和Content对象图

后来老师忽然觉得这个题目过于简单。于是将试题内容改了改。B对象的Content变成了1*1=?。答案自然也变成了1。如果老师改完题目后又直接去改卷子。发现学生竟然答案是2于是无情的就给了0分。当然这些假如有些勉强,但是问题是在逻辑上一旦题目在考试卷上出现过它就没办法改变。似乎Question应该单独拥有一个QuestionContent对象。这样在修改题库里QuestionContent才不会影响Question对象。但是如果这样就会有很多的重复的QuesitonContent对象。从逻辑上分析QuesitonContent 对象确实应该是一个值类型的对象。而不应该是引用类型。但是作为引用类型来处理确实能省掉很多的内存和数据库空间。只是在修改的时候才会出现问题。在线考试系统的确实是用引用类型的方式来处理QuestionContent的。但是怎样才能避免上面提到的修改问题呢。也就是说如何让一个引用类型的对象具有值类型的行为。这里借鉴了.net类库里对Sting类型的实现方法。就是保证QuestionContent 具有不变性。当在题库更新一个QuestionContent对象的时候就新创建一个QuestionContent然后在将旧的对象和Chapter对象直接的关系给移除。这样从逻辑上就把旧的QuestionContent给删除了。但是又不影响Quesiton。删除时就是把QuestionContent对象和Chapter对象的关系解除。当然如果这个QuestionContent 对象从来没有被Quesiton对象引用过。我们只解除和Chapter的关系而不是物理删除它会存在很多垃圾数据。这些数据将永远存在数据库中,却永远不会被系统使用。内存中的垃圾数据可以有垃圾回收机制来回收。但是数据库中的却没有,所以我们还应该定期的清理那些无用的数据。

 public class QuestionContentService
{
//更新试题操作
        public void Update(QuestionContent questionConent)
        
{
            
//新建一个试题,并删除老的试题
            QuestionContent newQuesitonContent = new QuestionContent();
            newQuesitonContent.Chapter 
= questionConent.Chapter;
            newQuesitonContent.Content 
= questionConent.Content;
            newQuesitonContent.Answer 
= questionConent.Answer;
            Dao.Save(newQuesitonContent);

            Delete(questionConent);
        }

        
public void Delete(QuestionContent quesitonContent)
        
{
                
//删除操作只是将试题和章节解除关联
                questionContent.Chapter=null;
                Dao.Update(questionContent);
        }

        
public void ClearUselessQuestionContent(QuestionContent quesitonContent)
        
{
            
//如果试题没有被使用就从数据库中删除,否则什么都不做
            if(questionContent is not used)
                    Dao.Delete(quesitonContent);
            
else
                
//Do nothing
        }

    }

这样就可以即减少数据冗余又保证系统的正确性!





posted on 2008-05-20 06:52  xhan  阅读(1121)  评论(0编辑  收藏  举报