When I added the Pingbacks and Trackbacks, I changed the implementation of the Comments to use an inheritance model (TPH) where Comments, Trackbacks, and Pingbacks all inherit from the abstract CommentBase class. To refresh your memory, here's the appropriate part of the DBML designer surface:
While this works fine with minimal changes when viewing data, it can cause problems if you are using the LinqDataSource for editing values. When trying to update or delete a record, you may encounter a message similar to the following:
Cannot create an abstract class.Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.MissingMethodException: Cannot create an abstract class.
So what's happening here? When the page is posted back, the LinqDataSource tries to re-create the object based on the incoming values so that it can reattach it to the context to perform the update. In the case of our polymorphic CommentBases collection, the data source doesn't know how to recreate the necessary object and thus throws the exception.
Normally when trying to override CRUD behaviors with the LinqDataSource, you would use the Deleting, Inserting, Updating, and Selecting methods. However in this case, the datasource has already tried to hydrate the object in order to pass it into the method handler as part of the event args. Thus handling it here is too late.
As an alternative, we can intercept the request earlier in the process. In the case of the ListView control, we can intercept this in the ItemDeleting, ItemUpdating and ItemInserting event handlers. The key here is that when we're done intercepting the request on this level that we need to keep the LinqDataSource from receiving the request by setting the Cancel property of the EventArgs and cleaning up ourselves.
When deleting a record, this is a simple process. First, we grab the key of the record being updated from the e.Keys collection. We then use that to search for the comments that have that ID passing the results to DeleteAllOnSubmit. Once the change is saved, we block future notifications setting the cancel. Here's a sample implementation:
Protected Sub ListView1_ItemDeleting(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.ListViewDeleteEventArgs) _
Handles ListView1.ItemDeleting If e.Keys.Count > 0 Then Using dc As New LinqBlog.BO.LinqBlogDataContext dc.CommentBases.DeleteAllOnSubmit(From c In dc.CommentBases _ Where c.CommentId = CInt(e.Keys(0))) dc.SubmitChanges() 'Suppress the default behavior of the binding source 'The binding source doesn't know how to instantiate the 'original value type on post-back due to the MustInherit inheritance ListView1.EditIndex = -1 e.Cancel = True End Using End If End Sub
Updating follows a similar process. In this case, we get the object we are editing based on the value in the Keys and then replay the changes based on the arg's NewValues collection.
Protected Sub ListView1_ItemUpdating(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.ListViewUpdateEventArgs) _
Handles ListView1.ItemUpdating If e.Keys.Count > 0 Then Using dc As New LinqBlog.BO.LinqBlogDataContext Dim id As Integer = CInt(e.Keys(0)) Dim item = dc.CommentBases.Where(Function(c) c.CommentId = id).FirstOrDefault If Not item Is Nothing Then 'Set the values For i = 0 To e.NewValues.Count - 1 CallByName(item, e.NewValues.Keys(i), CallType.Set, e.NewValues(i)) Next dc.SubmitChanges() 'Suppress the default behavior of the binding source 'The binding source doesn't know how to instantiate the 'original value type on post-back due to the MustInherit inheritance ListView1.EditIndex = -1 e.Cancel = True End If End Using End If End Sub
Since we're not implementing inserting from the grid for comments, we don't need to code that piece. I'll leave it to you, the reader to try that one. Realize that you will need to determine the CommentType prior to creating the appropriate instance object before setting the values on insert. Otherwise, the process is basically the same.
One other thing to keep in mind is handling concurrency. Because we are refetching the object as part of the CUD operation, we are effectively throwing away the true original values. Of course we can take care of that if we use a database issued timestamp (rowversion) for concurrency.