22 March, 2012

WP7 DatabaseSchemaUpdater multiple version update

While testing the Windows Phone 7 DatabaseSchemaUpdater I started getting the following error: "A column ID occurred more than once in the specification."

I searched the web for a while and got nowhere.

So, analizing my code for the 10th time i noticed something. On the second update (version 1 to version 2) I was adding a new table:

schemaUpdater.AddTable();

And on the third update (v2 to v3) I was adding a new column to that same table:

schemaUpdater.AddColumn< ClassName >("PropertyName");

If the user updates the app regularly everything goes smoothly, but if the user doesn't update for some time and then has to go through all the updates the app crashes.

The problem is that when the new table was added, since the user is already at the newest version, the table already has the column that the next update will try to add. At that point it will crash, and continue crashing at every update unless the DatabaseSchemaVersion is properly updated.

This can lead to a lot of frustration and data loss for the user and must be taken into account when updating.

Since I couldn't find a way to get the current schema to check for table columns before adding them I made a simple workaround.

When updating keep a list of added table names in that update and when adding columns only add them if the table where the column is being added doesn't have its name on the list.
Check the example below



private const int LatestDatabaseVersion = 2;

private List tablesAddedInThisUpdateSession = null;
public List TablesAddedInThisUpdateSession
{
get
{
if (tablesAddedInThisUpdateSession == null)
{
tablesAddedInThisUpdateSession = new List();
}

return tablesAddedInThisUpdateSession;
}
}

public void UpdateIfNeeded()
{
// create an instance of DatabaseSchemaUpdater
DatabaseSchemaUpdater schemaUpdater = this.CreateDatabaseSchemaUpdater();

int version = schemaUpdater.DatabaseSchemaVersion;

// if current version of database schema is old
while (version < LatestDatabaseVersion)
{
switch (version)
{
case 0:
version = UpdateFromVersion0ToVersion1();
break;
case 1:
version = UpdateFromVersion1ToVersion2();
break;
default:
break;
}
}
}

private int UpdateFromVersion0ToVersion1()
{
DatabaseSchemaUpdater schemaUpdater = this.CreateDatabaseSchemaUpdater();

schemaUpdater.AddTable<Contact>();
TablesAddedInThisUpdateSession.Add("Contact");

// IMPORTANT: update database schema version before calling Execute
schemaUpdater.DatabaseSchemaVersion = 1;
schemaUpdater.Execute();

return schemaUpdater.DatabaseSchemaVersion;
}

private int UpdateFromVersion1ToVersion2()
{
DatabaseSchemaUpdater schemaUpdater = this.CreateDatabaseSchemaUpdater();

if (!TablesAddedInThisUpdateSession.Contains("Contact"))
{
schemaUpdater.AddColumn<Contact>("Phone");
}

schemaUpdater.DatabaseSchemaVersion = 2;
schemaUpdater.Execute();

return schemaUpdater.DatabaseSchemaVersion;
}

06 March, 2012

Asp.NET Concurrent AJAX Calls

In Asp.NET "if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished." (ASP.NET Session State Overview)

The keywords here are "same session".

When an Ajax call to an ASP.NET Web method is made:

[WebMethod(true)] // EnableSession = true
public static void MethodName()
{
}

If the "EnableSession" is set to true there can be only one ajax call at a time. Further calls will wait in pending state until the call is finished. Once it's finished the order of previous calls is not guaranteed.

If EnableSession is set to false you can make concurrent calls but will not have access to the Session. The number of calls that can be made depends on each browser.

However, if you're user ASP.NET MVC 3 and above there is an attribute named SessionState that enables you to make concurrent calls and still access your session (in read-only mode)

Concurrent calls :
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]

Sequencial calls:
[SessionState(System.Web.SessionState.SessionStateBehavior.Required)]