Few months back I wrote an article in which I talked about creating an online exam using NHibernate. In this article I will use LINQ to Classes to create the online exam. This is a multi-series article and in this part I will discuss the architecture and the design of the application.
Introduction:
Few months back I wrote an article in which I talked about creating an online exam using NHibernate. In this article I will use LINQ to Classes to create the online exam. This is a multi-series article and in this part I will discuss the architecture and the design of the application.
Application Requirement:
The client needs an application where students can log in and give the exam. The exam is uploaded using an XML file provided by the client. The user will take the exam then view his scores.
I will not discuss the user login and authentication section and will focus on the exam modules.
Database Design:
The database name is “School” and it consists of five tables. Take a look at the database diagram shown below:
Users: This table holds the users.
Exams: This table holds the exams.
Questions: This table holds the questions of the exams.
Choices: This table holds the choices of the questions.
UserExams: This table holds the user grade and score of the exams.
Class Diagram:
LINQ to Classes allows you to create classes using the database tables. Simply, drag and drop the table from the server explorer on the design view and it will automatically create the class diagram. Take a look at the entity class diagram below:
Apart from the entity class diagram I have also included the Repository and Services class diagram. This one is a big diagram so don’t be scared!
Let me explain the architecture of the repositories. Each aggregate root has its own repository. Exam has ExamRepository, User has UserRepository etc. There are no repositories for Question and Choices. This is because they are handled by the ExamRepository.
Each repository inherits from the particular repository interface and the BaseRepository class. Every concrete repository inherits from the base repository interface called IBaseRepository. This is because each concrete repository i.e. ExamRepository, UserRepository must expose some common methods like GetById, Add, PersistAll, GetAll. If I put these methods in the corresponding repository interfaces then I have to implement those interfaces in the concrete repositories which, means repetitive code.
The BaseRepository comes to the rescue and implements the common methods exposed by all the repositories. The methods are also marked as virtual so they can be overridden in the sub repositories.
Let’s take a look at the IBaseRepository interface.
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;
}
As, you can see the IBaseRepository interface works with the generic type T. I will explain the purpose of using the constraints later in this article. Now, let’s see the implementation of the generic GetById method implemented in BaseRepository class.
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;
});
}
The GetById method above looks little complicated but I will try my best to explain it. First, let’s talk about the constraint as shown below:
public T GetById<T>(int id) where T : class
The constraint says that the type T must be a class. This is because the method school.GetTable<T>() only works on the reference types. The purpose of GetById method is to return the object based on its Id. The problem is that each entity class has a different name for its primary key column. User class has UserID, Exam class has ExamID and so on. This makes things more complicated as we have to find the name of the column which serves as the primary key. For this I am using the following code which finds all the columns of the table and then finds the column name which serves as the 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;
Once, we got the primary key can extract the value out of that column and compare with our passed parameter “id” as shown below:
return table.SingleOrDefault<T>
(delegate(T t)
{
int memberId = (int)t.GetType().GetProperty(pk).GetValue(t, null);
return memberId == id;
});
There is a performance hit when using the above approach since we are using reflection to find the value. But this was a trade off I took over writing repetitive code.
Let’s also take a look at the other three BaseRepository methods.
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();
}
The Add<T>(T item) method adds the item to the table collection but does not commit it to the database. The PersistAll method commits the item to the database. And finally the AddAndPersistAll method add and persist the item in the database.
Conclusion:
In this article we discussed the architecture of the online exam application. In the next article we will write some unit tests to test our domain layer and the repositories.
The download will be provided in the next part of this series.