创建多项选择考试(DLINQ - 第一部分)
一篇关于使用 DLINQ 创建多项选择考试的文章(第一部分)。
引言
几个月前,我写了一篇文章,其中讨论了使用 NHibernate
创建在线考试。 在本文中,我将使用 LINQ to Classes 创建在线考试。 这是一个多系列文章,在这一部分中,我将讨论应用程序的架构和设计。
应用程序需求
客户需要一个应用程序,学生可以登录并参加考试。 考试是使用客户提供的 XML 文件上传的。 用户将参加考试,然后查看他的分数。
我将不讨论用户登录和身份验证部分,而将重点放在考试模块上。
数据库设计
数据库名称为 School
,它由五个表组成。 请看下面的数据库图

Users
:此表保存用户。Exams
:此表保存考试。Questions
:此表保存考试的问题。Choices
:此表保存问题的选项。UserExams
:此表保存用户的考试成绩和分数。
类图
LINQ to Classes 允许您使用数据库表创建类。 简单地,从服务器资源管理器将表拖放到设计视图中,它将自动创建类图。 请看下面的实体类图

除了实体类图,我还包含了 Repository
和 Services
类图。 这是一个很大的图,所以不要害怕!

让我解释一下存储库的架构。 每个聚合根都有自己的存储库。 Exam
有 ExamRepository
,User
有 UserRepository
等。 没有 Question
和 Choices
的存储库。 这是因为它们由 ExamRepository
处理。
每个存储库都继承自特定的存储库接口和 BaseRepository
类。 每个具体的存储库都继承自名为 IBaseRepository
的基本存储库接口。 这是因为每个具体的存储库,即 ExamRepository
,UserRepository
必须公开一些通用方法,例如 GetById
,Add
,PersistAll
,GetAll
。 如果我将这些方法放在相应的存储库接口中,那么我必须在具体的存储库中实现这些接口,这意味着重复的代码。
BaseRepository
来救援并实现了所有存储库公开的通用方法。 这些方法也被标记为 virtual,因此可以在子存储库中覆盖它们。
让我们看一下 IBaseRepository
接口
public interface IBaseRepository
{
T GetById<t>(int id) where T : class;
void Add<t>(T item) where T : class;
void PersistAll();
void AddAndPersistAll<t>(T item) where T : class;
}
如您所见,IBaseRepository
接口使用泛型类型 T
。 我将在本文后面解释使用约束的目的。 现在,让我们看看在 BaseRepository
类中实现的泛型 GetById
方法的实现
public T GetById<t>(int id) where T : class
{
var table = school.GetTable<t>();
// finding the name of the PK column in the database
MetaModel mapping = table.Context.Mapping;
ReadOnlyCollection<metadatamember /> members = mapping.GetMetaType(typeof(T)).DataMembers;
string pk = (members.Single<metadatamember />(m => m.IsPrimaryKey)).Name;
// getting the object by Id
return table.SingleOrDefault<t>
(delegate(T t)
{
int memberId = (int)t.GetType().GetProperty(pk).GetValue(t, null);
return memberId == id;
});
}
上面的 GetById
方法看起来有点复杂,但我会尽力解释它。 首先,让我们谈谈约束,如下所示
public T GetById<t>(int id) where T : class
约束表示类型 T
必须是一个类。 这是因为方法 school.GetTable<t>()
仅适用于引用类型。 GetById
方法的目的是根据其 Id 返回对象。 问题是每个实体类的 primary key 列的名称都不同。 User
类有 UserID
,Exam
类有 ExamID
等等。 这使得事情更加复杂,因为我们必须找到充当 primary key 的列的名称。 为此,我使用以下代码,该代码查找表的所有列,然后查找充当 primary key 的列名。
// finding the name of the PK column in the database
MetaModel mapping = table.Context.Mapping;
ReadOnlyCollection<metadatamember /> members = mapping.GetMetaType(typeof(T)).DataMembers;
string pk = (members.Single<metadatamember />(m => m.IsPrimaryKey)).Name;
一旦我们获得了 primary key,我们就可以从该列中提取值并将其与我们传递的参数 id
进行比较,如下所示
return table.SingleOrDefault<t>
(delegate(T t)
{
int memberId = (int)t.GetType().GetProperty(pk).GetValue(t, null);
return memberId == id;
});
当使用上述方法时,由于我们使用反射来查找值,因此会产生性能损失。 但这是我为了避免编写重复代码而做出的权衡。
让我们也看看其他三个 BaseRepository
方法
public void Add<t>(T item) where T : class
{
var table = school.GetTable<t>();
table.InsertOnSubmit(item);
}
public void PersistAll()
{
school.SubmitChanges();
}
public void AddAndPersistAll<t>(T item) where T : class
{
var table = school.GetTable<t>();
table.InsertOnSubmit(item);
PersistAll();
}
Add<t>(T item)
方法将项目添加到表集合,但不将其提交到数据库。 PersistAll
方法将项目提交到数据库。 最后,AddAndPersistAll
方法添加并将项目保存在数据库中。
结论
在本文中,我们讨论了在线考试应用程序的架构。 在下一篇文章中,我们将编写一些单元测试来测试我们的域层和存储库。
下载将在本系列的下一部分中提供。