Database


This story went much faster than expected, so I let a little feature creep occur and expanded the implementation. The original story had me creating a new field in the Lessons DB table, which marked a lesson as deleted. I don’t want to actually delete the lesson record, just mark it as delete, so after adding the new field, all I had to do was retrieve only the non-deleted lessons and update that field when a lesson was deleted.

deletelesson

This would have been fine, except I wondered whether it would be useful, then, to support undeleting a lesson, which is presumably what marking a record as deleted is for in many cases. So, I set out to make a new page called Deleted Lessons, which shows all the deleted lessons for a particular class. From here, the user can click on the Undelete button to restore the previously deleted lesson.

undeletelesson

This story demonstrated the dangers of feature creep, however, as the story took about 3 times as long as what it took to just implement the original story… but oh well.

This ended up being a fairly substantial story, mainly because there wasn’t any lesson-related code set up in the project yet, and because it required a new service class to implement from scratch. It wasn’t too bad as it is the same kind of thing required for the course and study group services, but it did add time to what I thought it would take.

One new thing here was dealing with DateTime objects and SQL datetime types. I wanted to know how to convert between the types, and did a quick Google search, using the 1st result as a way to do the code. This was a bad idea, and I learned to be a little more thorough in my code research…luckily it didn’t even compile, because I couldn’t introduced a more subtle error. Instead, all that was required was a cast, which makes sense as if Microsoft owns both SQL Server and ASP, they shouldn’t require some jumble of bloated code to communicate with each other. At least one would think.

Anyhow, I got it working, and below is the admin view of the lessons for a particular class. The edit button doesn’t do anything, as that is just a placeholder for a later story.

viewlessons

Today’s story was to add support for adding a student to a course’s enrollment. This might seem pretty straightforward, except that we also need to add that user to the Unassigned study group.

What I wanted to do was to have the Course table entries also store the ID of its corresponding Unassigned study group, because otherwise I’d have to do a search query on the Study Group table with the appropriate course ID, and also search for the one that was named “Unassigned.” In addition, I would need to restrict users from naming another study group to Unassigned, since it would mess up this query.

A simpler solution would be to record the ID of the study group and put this in the course table when the course is created, since an Unassigned study group is also created at the same time. It took quite a long time to complete… I had to revisit my DB structure and how I was creating courses first, and also correct the previously added test courses so that they were assigned something appropriate.

The hardest part was probably modifying the course adding operation, since I needed to retrieve both the courseID and studyGroupID, both of which are auto-increment, and so have to be discovered after creation.

And all that was just to set things up for adding users to courses. The rest of the way was relatively complex as well. One of the hardest parts was coming up with a query that would give me all the users who were eligible to be added to a course. For example, you shouldn’t be able to add a user to a course twice. So, I basically had to get a list of all the students who were not enrolled in a class.

This is another example of where a lack of SQL experience hurts me — I’m pretty sure there is a query that I could write that would do this, but I ended up, after some frustration, retrieving all the users, then retrieving all the enrolled users, and finally comparing the lists to get the list of unenrolled users. It’s a performance no-no and bad practice, but under my time constraints, it’s good enough and it works!

The final step was hooking the unenrolled list to the ListBox that I had setup, and thankfully that wasn’t too painful. I did implement it such that you could add multiple users at one time, which is helpful for the user.

changeenrollment

I spent about 4x the time on this story than previous stories, so I think this iteration is now overbudgeted. I’ll try the next story, but unless that and the following ones are super easy, I’ll probably close this iteration after that story is completed.

The first story in this iteration was a little harder than previous ones, but went relatively ok. In this story, we needed to support creating a class, which is simple enough, but also needed to add an Unassigned study group automatically to the class, so that newly enrolled students would always be assigned to a study group upon entry into the class.

The first thing I did was to hardcode into the database Unassigned study group entries for the already existing classes in order to maintain internal consistency. Then I modified the existing Create Class page in the admin directory to use the MVP pattern. From there, in the CourseService, I set out to add an insert command to create the new unassigned study group in the database.

Since our Class table is set to autonumber, I wanted to retrieve the new ClassID, but I was thinking there must be a simple way to get the id from a newly inserted row. Turns out after some amount of effort, I can do so by retrieving the @@identity property… but the whole point was that I just wanted to avoid making an insert statement, and then immediately a select statement. Well… even with the @@identity property, I still have to make another select statement anyway, so I’m not sure I gained much. I guess it’s a little more robust though, since it’s possible if two admins are creating classes at the same time, that an insert from one might happen between the insert and select from the other. This isn’t a huge concern for the scope of this project, but oh well, it’s good to know for future reference.

Anyhow, I verified that the new create class operations works. Here’s the page to create the class:

create class

Then the table data for the class table and study group table after:

class table

sgtable

Note that the newly created class, “test”, has a ClassID of 15, and the study group table now has an “Unassigned” study group, corresponding to a ClassID of 15.

This story basically involved doing some house cleaning and small assorted improvements as the last part of this release.

First, I noticed that having separate StudyGroup_User and Class_User join tables was necessitating messy, multiple join statements in the SQL queries. But the thing is that StudyGroups and Classes have a many-to-one relationship, and that a study group could not belong to more than one class. More importantly, if a user belonged to a class, the user could not belong to more than one study group. Therefore, I could instead add StudyGroupID as a field in the Class_User table and completely get rid of the StudyGroup_User table.

Here you can see the cleaned up DB diagram, which also shows the new Lesson table created earlier in the release. The DB change required a few pages to be changed to use the new field and remove references to the now defunct StudyGroup_User table.

DB Diagram 3

I did a little cleanup of the pages as well. There didn’t seem to be a compelling reason for a “View My Classes” page, since we already show this in “My WBI”, so I removed this page. There is a “View My Study Groups” page as well, which had a stronger reason for existing. If you remember, we just created an empty page as a placeholder for this, so in this story the page has been implemented, with links to the appropriate View Class and View Study Group pages.

My Study Groups

Another straightforward story today. Here, we’re taking the new data fields made available by the new Lessons table, and displaying the associated lessons of a particular class in its view class page. Similar to the other gridviews on this page, we are using the classid as a request parameter to generate the appropriate SQL statement, and in this case we are also doing an ORDER BY start date, in order to get some natural sequence.

I decided earlier on that I wouldn’t add some sort of sequence number to these lessons, because as someone who has taught before, plans change and you end up skipping around. It would make more sense to just change start dates, and have that be the determination as to which lessons appear first.

Because the view class page currently is intended for student users, it didn’t seem all that meaningful to display the # of total points that each assessment was going to be. Not that this is not useful for the student to know; just not on this page. It would probably make more sense to show this on a page where the student was viewing progress and assessment records.

But the student does see the dates of the assessments, as well as the required mastery level needed, expressed as a percentage. The display of all this data was mostly accomplished through the existing gridview setup, but I tweaked a little with the formatting to get something close to what a student might expect.

View Class Lessons

Incidentally, the story was implemented way under the 1.5 hours estimated, so I guess I should be happy that my pace is quickening.

Today’s story involves adding instructional units to the database. Since that’s a mouthful and might be confusing terminology to people outside the education academia realm, we’re going with a simpler term, “Lesson.”

The purpose of the lesson table will be to store the definition of the lesson — the lesson name, as well as its data relevant to the mastery learning method. In particular, there will be a start date and a date for the 2 assessments. Any practice that the students do will occur between these dates.

Also, in this lesson we specify the number of possible points for assessment 1 and 2, as well as define what level of achievement (as a percentage) constitutes mastery.

Lesson Table

The data fields are pretty straightforward, with the LessonID obviously being the primary key and being auto-numbered, and a foreign key relationship with the Class table.

The rest of the time for the story was for setting up test data with some simple lesson records for the Arithmetic class, which is mostly for helping testing, and also a lesson record for vectors in the physics class, which will be the more “realistic” lesson module.

This was somewhat of a challenging user story, but after several attempts, I got it working. I realized in the process that I need to beef up some of my database skills, but the main thing is that for now it works alright.

The main task was to make URLs for the current user’s classes and associated study groups. For this, I needed to get the user id, the class id, the class name, the study group id, and the study group name. Unfortunately these were spread apart in several different tables, and up to this point I had only done joins on two tables.

I did a little searching, and although it’s not the best performance-wise, it’s acceptable to do a join on 3 or 4 tables… and this is basically what I did to get the feature working. I had a little trouble at first with the syntax, ending up with too many matching records because I didn’t specify all the necessary conditions across all tables for the userid to remain constant. After fixing this, it seems to work well, and I set up the links to use the study group and class ids to open the view class and view study group pages without a hitch.

A screenshot of the implementation for one user:

My WBI

I wasn’t quite sure what to call this page, as it isn’t technically the home page, but it is the main page that users should go to after login. I finally settled on “My WBI”, which I don’t really like, and feels oh-so-dot-com-bubble-ish. But oh well.

Wow… another story that was straightforward. I’m on a roll.

Today’s story had two parts that were essentially doing the same thing — we previously were displaying just a username for the list of students in a class and in a study group. Now, we want to use the new UserInfo table to draw out the user fields, specifically alias, last name, and first name.

I set up a data source to do a join on the UserInfo table and the User_Classes, and User_StudyGroups tables respectively, which gave me the relevant records, and did a display on the user information without any hitches.

View Class 3

Since everything went quickly, I also added the class name on the view study group page, since it’s relevant as to which class the study group is for.

View Study Group 2

This story definitely didn’t take the 1.5 hours estimated, which is great. It looks like for the features where I’ve done something similar before, it’s taking a fraction of what I estimated, so I can start to reduce estimates on those types of features. On the other hand, if some of the stories from earlier in the week reveal, I’m underestimating tasks that I have little or no experience in. It’s starting to balance out in this release, but I’d rather be closer on both types of estimates in the next release.

Finally. Well, yesterday I talked about another roadblock I hit for the view profile page. The roadblock is this: I need the UserID of the user, so that I can retrieve just a particular user’s information (first name, last name, alias, etc.). Unfortunately, even though that seems just about the most basic piece of information that ought to be somehow available somewhere in the ASP wizard-like syntax… it isn’t.

Through a bunch of searches online, I found out that there is a programmatic way to get the userid pretty simply, but then how do I specify a dynamic value inside the datasource that sets up the select statement, which is itself a wizard? I couldn’t just specify a programmatic call inside that wizard. So I remembered a technique from an earlier reading, in setting up a hidden label which has the appropriate value.

I did this, but the value wasn’t appearing… I was just getting a blank. I struggled a lot with this until I was just about ready to give up, and just tried for kicks to make the call not in the page itself, but in the page load event handler instead. All of a sudden it works, which means that I don’t have a solid grip on the page lifecycle in ASP, because I had assumed that making the call in the page load event handler was too late for the select statement.

Finally, I had to rewrite the form fields to use a DetailsView. I tried for awhile to just take straight fields, but felt like I was swimming upstream to set up a view and edit for the various fields, so I caved in and used the DetailsView control, which (admittedly) easily sets up a view/edit operation on a data source. If only it didn’t insist on working the way it does.

My Account 2

Regardless, this story is finally done, much longer than I had anticipated. However, I’m not going to pus out other stories from this release, and instead extend the anticipated release date by a few days, especially since last release cut a story short. If I run into more problems in future stories, then I might consider pushing out one or two at the end. We’ll see.

Next Page »