Beth's Chinese blog
Even though Visual Studio 2008 has been available since mid-November, today is the official Visual Studio 2008, Windows Server 2008, and SQL Server 2008 launch event down in Los Angeles.
If you aren't at the event in L.A. you can tune into the virtual event online. On the Heroes Happen Here web site you also can find more information about the releases and view profiles of customers doing amazing things with Visual Studio in the Heroes Profile Gallery.
This marks the beginning of hundreds of events worldwide that will touch hundreds of thousands of people. I'll be presenting at the San Francisco event on March 13th and then in Reno on May 22nd. Hope to see you there.
Enjoy!
I just added another webcast for March 28th on VB6 migration to .NET to the list of March madness webcasts I'll be doing.
Rob Windsor and I will be presenting a bonus webcast on VB6 to .NET migration. If you're involved in a migration push and don't know where to start this webcast is for you. We'll show some proven strategies as well as walk through the Interop Forms Toolkit which can dramatically speed up development in a phased migration approach.
Live From RedBethmond: Migrating Your Visual Basic 6 Investments to .NET 3/28/2008 9:00 AM Pacific Time (US & Canada)- 3/28/2008 10:00 AM | Duration:60 Minutes Visual Basic (VB) 6 was used by millions of developers world-wide to build applications ranging from thousands to millions of lines of code representing significant organizational investments. The path from VB 6 to .NET has not always been clear, there is no one size fits all approach. We’ll cut to the chase, exploring the pros and cons of each option using real world examples. You will leave this webcast with the framework and tools to develop the right strategy for your organization to leverage your existing investments while taking advantage of the power and productivity the .NET Framework provides. Presented By Beth Massi, Visual Studio Community PM With special guest Rob Windsor, Visual Basic MVP.
Check out the other webcasts in March on Visual Basic 9 and LINQ.
In the last few posts on LINQ to SQL I've showed how to set up an object model using the O/R designer and how to handle a couple data binding scenarios with Comboboxes here and here. Last post on this topic we implemented a one-to-many data entry form and I showed how to work with stored procs as well as how to properly configure delete behaviors. In this post I want to explore how to easily add validation rules to our LINQ to SQL classes and how we can get these rules automatically displayed in the UI.
LINQ to SQL Classes -- A Closer Look
Classes that implement the IDataErrorInfo interface in conjunction with INotifyPropertyChanged are able to automatically notify UI objects like the ErrorProvider and the DataGridView to display validation errors. LINQ to SQL classes that are generated for you when you create your object models already implement the INotifyPropertyChanged interface and using partial methods we can easily add validation to our classes. For this example we'll expand the One-to-Many data entry form we built to include some business rules.
First let's open up the generated LINQ to SQL classes by opening the .Designer.vb file under the dbml file (if you don't see the designer file, just click the "show all files" button on the Solution Explorer tool strip first). If we take a look at our Order class we will see the following class definition:
<Table(Name:="dbo.Orders")> _ Partial Public Class [Order] Implements System.ComponentModel.INotifyPropertyChanging, _ System.ComponentModel.INotifyPropertyChanged
As you can see, this class is just a plain old CLR object (POCO) that implements interfaces that notify when properties are changing or changed on the class. That's it. It's the DataContext that does the heavy lifting, knowing what objects have changes, which ones were added and removed and how to persist these to the database. It does its simple mapping via the attributes on the class and it's properties. Here's the property for CustomerID on our Order class:
<Column(Storage:="_CustomerID", DbType:="Int NOT NULL", UpdateCheck:=UpdateCheck.Never)> _ Public Property CustomerID() As Integer Get Return Me._CustomerID End Get Set If ((Me._CustomerID = value) _ = false) Then If Me._Customer.HasLoadedOrAssignedValue Then Throw New System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException End If Me.OnCustomerIDChanging(value) Me.SendPropertyChanging Me._CustomerID = value Me.SendPropertyChanged("CustomerID") Me.OnCustomerIDChanged End If End Set End Property
Partial Classes and Methods
In order to add validation and business rules we can add code to the OnCustomerIDChanging and OnCustomerIDChanged methods. But we don't add them here in this generated file, instead we add them into the Partial Class. This is possible because of a new feature in Visual Studio 2008 called Partial Methods. Partial Classes were introduced in Visual Studio 2005 as a way to extend generated classes with additional functionality by allowing you to split classes across physical files. You can add new methods or properties in order to extend a generated class easily by using the Partial Class keyword and then writing your own code. The compiler will "merge" these files into one class.
In Visual Studio 2008 we can go a step further using Partial methods. Instead of raising and handling private events, partial methods can be used instead as a better performing and cleaner alternative. They are declared by creating a private method with an empty body and decorating it with the Partial keyword. The method may then be "re-implemented" elsewhere within its containing class. If the method is implemented, then the compiler will redirect all calls to the partial method to the implementing method. If the method is not implemented in its containing class, then the compiler silently removes any calls to it from the program.
If we take a look at the generated LINQ to SQL Order class again you can see there is a region called "Extensibility Method Definitions" that contain Partial methods. There will be On...Changed and On...Changing partial methods here for each of the properties on the class.
#Region "Extensibility Method Definitions" Partial Private Sub OnLoaded() End Sub Partial Private Sub OnValidate(action As System.Data.Linq.ChangeAction) End Sub Partial Private Sub OnCreated() End Sub Partial Private Sub OnCustomerIDChanging(value As Integer) End Sub Partial Private Sub OnCustomerIDChanged() End Sub ...
Notice that the Changing and Changed methods are called at the appropriate times in each of the property setters. This allows us to write clean business rules on these properties in our partial class by defining the partial method. However if we don't write any code for these partial methods, the calls to them are removed from the IL. To access the partial class code open the model (dbml file) in the O/R designer, select a class, right-click on the class name and select "View Code". This will create a file named after your model where the partial class code that we write can reside.
So to write some business rules for our classes in this example we're going to place code in the On...Changing methods and then implement IDataErrorInfo so that we can have the UI automatically display the validation messages. To make it easier to implement IDataErrorInfo on all our LINQ to SQL classes I'm going to create a base class that we can inherit from called BaseBusiness.
Implementing IDataErrorInfo
IDataErrorInfo requires us to implement only two properties, one called Error and one default property Item that both return a string. Error is used to describe what is wrong with the entire object. For instance, if we are displaying this object in a row of a DataGridView this property indicates what error message appears on the row header. If you use DataSets, this corresponds to the DataRow.RowError property. The Item property is used to determine the error message for a specific property (or column) that is passed in. In order to collect these messages for each property on our object (i.e. column in our table) we can use a generic dictionary of strings and either add or remove messages from the dictionary depending on the validation rules. Here's an example implementation of IDataErrorInfo on our base class:
Imports System.ComponentModel ''' <summary> ''' Base class for our LINQ to SQL classes. ''' This class demonstrates one way to implement the IDataErrorInfo ''' interface so that the ErrorProvider and DataGridView can display ''' validation errors in the UI. ''' </summary> ''' <remarks></remarks> Public Class BaseBusiness Implements IDataErrorInfo 'This dictionary contains a list of our validation errors for each field Private validationErrors As New Dictionary(Of String, String) Protected Sub AddError(ByVal columnName As String, ByVal msg As String) If Not validationErrors.ContainsKey(columnName) Then validationErrors.Add(columnName, msg) End If End Sub Protected Sub RemoveError(ByVal columnName As String) If validationErrors.ContainsKey(columnName) Then validationErrors.Remove(columnName) End If End Sub Public Overridable ReadOnly Property HasErrors() As Boolean Get Return (validationErrors.Count > 0) End Get End Property Public ReadOnly Property [Error]() As String _ Implements System.ComponentModel.IDataErrorInfo.Error Get If validationErrors.Count > 0 Then Return String.Format("{0} data is invalid.", TypeName(Me)) Else Return Nothing End If End Get End Property Default Public ReadOnly Property Item(ByVal columnName As String) As String _ Implements System.ComponentModel.IDataErrorInfo.Item Get If validationErrors.ContainsKey(columnName) Then Return validationErrors(columnName).ToString Else Return Nothing End If End Get End Property End Class
Next we need to inherit from this class in all our LINQ to SQL Classes. Back in our partial class file we can inherit all our LINQ to SQL classes from BaseBusiness:
Partial Class Customer Inherits BaseBusiness End Class Partial Class Product Inherits BaseBusiness End Class Partial Class OrderDetail Inherits BaseBusiness End Class Partial Class Order Inherits BaseBusiness End Class
Writing the Business Rules
Now we need to write our business rules into these classes. Let's take Order as an example. There are a couple rules I want to implement here:
There are a couple places that we need to run these rules. One is when any of these fields change and the other is before the changes are submitted to the database. We need to handle both places because the user doesn't necessarily change all the properties and raise the On...Changing event. We need to handle the case when the user doesn't fill out a field and immediately attempts to save. This partial method is called OnValidate on our LINQ to SQL classes. This method is called automatically by the DataContext right before it attempts to submit the changes to the database when SubmitChanges is called.
Because you have to run the rules in both of these situations it's probably easier to create a private method that checks each rule and call that method from both Partial On...Changing and OnValidate methods. If the rule is broken then we just call AddError to add the error message to the dictionary, otherwise we call RemoveError to remove it from the dictionary. Here's the code for my Order Partial class:
Partial Class Order Inherits BaseBusiness Private Sub OnCustomerIDChanging(ByVal value As Integer) Me.CheckCustomerID(value) End Sub Private Sub OnOrderDateChanging(ByVal value As Date?) Me.CheckOrderDate(value) End Sub Private Sub OnShipDateChanging(ByVal value As Date?) Me.CheckShipDate(value) End Sub Private Sub OnValidate(ByVal action As System.Data.Linq.ChangeAction) Me.CheckCustomerID(Me.CustomerID) Me.CheckOrderDate(Me.OrderDate) Me.CheckShipDate(Me.OrderDate) If Me.HasErrors Then Throw New ValidationException(Me.Error) End If End Sub Private Sub CheckCustomerID(ByVal value As Integer) If value < 1 Then Me.AddError("CustomerID", "Customer cannot be empty.") Else Me.RemoveError("CustomerID") End If End Sub Private Sub CheckOrderDate(ByVal value As Date?) If value.HasValue Then If value > Me.ShipDate Then Me.AddError("OrderDate", "Order date cannot be after the ship date.") Else Me.RemoveError("OrderDate") End If End If End Sub Private Sub CheckShipDate(ByVal value As Date?) If value.HasValue Then If value < Me.OrderDate Then Me.AddError("ShipDate", "Ship date cannot be before the order date.") Else Me.RemoveError("ShipDate") End If End If End Sub Public Overrides ReadOnly Property HasErrors() As Boolean Get If Not MyBase.HasErrors Then 'Returns True if any order details are invalid For Each detail In Me.OrderDetails If detail.HasErrors Then Return True End If Next End If Return MyBase.HasErrors End Get End Property End Class
So here we are calling our validation methods from the On...Changing as well as the OnValidate partial methods. If you throw an exception from OnValidate then that will halt the SubmitChanges from going further. I created my own ValidationException class that we can use to check from our save code on the form. I also included any Order Detail errors in the HasErrors property of this Order class. This means that if any order details have errors then this Order also reports that HasErrors is True.
Displaying Validation Errors in the UI
Now that we have our business rules implemented we can hook up the UI to display these messages. Objects that are being displayed in a DataGridView will automatically pick up our validation messages but for simple controls like textboxes we need to hook up an ErrorProvider. Just drag the ErrorProvider from the toolbox onto your form and then specify the BindingSource as the DataSource property, for this example it's the OrderBindingSource. That's it. The ErrorProvider will look for any error messages on our objects through the IDataErrorInfo interface. This occurs when a property changes. For instance, if I run the form now and change the Order Date to be after the Ship Date, an error will be displayed as I tab off of the Order Date:
In order to display the messages after validation fails when we attempt to submit changes to the database, we need to write some code to refresh the display. In this scenario we also need to check the OrderBindingSource's position to make sure that the user is sitting on the invalid Order so the visuals are clear as to what needs fixing. This is why I created my own exception class called ValidationException and added a property to our BaseBusiness class called HasErrors, so that we could easily handle this case.
So back in our form we'll write the following Save code:
Private Sub OrderBindingNavigatorSaveItem_Click() _ Handles OrderBindingNavigatorSaveItem.Click Me.Validate() Me.OrderBindingSource.EndEdit() Me.OrderDetailsBindingSource.EndEdit() Try db.SubmitChanges() MsgBox("Your data was saved.") Catch ex As ValidationException Me.DisplayErrors() MsgBox("Please correct the errors on this form before saving.") Catch ex As Exception MsgBox(ex.ToString) End Try End Sub ''' <summary> ''' Displays any error information and navigates to the first error row ''' </summary> ''' <remarks></remarks> Private Sub DisplayErrors() Me.ErrorProvider1.UpdateBinding() Me.OrderDetailsDataGridView.Refresh()
If Me.OrderBindingSource.Position > -1 Then Dim currentOrder As Order = CType(Me.OrderBindingSource.Current, Order) If Not currentOrder.HasErrors Then 'The error is not in view so navigate to it For i = 0 To Me.OrderBindingSource.Count - 1 Dim order As Order = CType(Me.OrderBindingSource(i), Order) If order.HasErrors Then Me.OrderBindingSource.Position = i Exit Sub End If Next End If End If End Sub
To test this, add a new Order then navigate away from it. When you click Save the validation will fail and you will be positioned back on the order that failed.
I placed the code for this article (including the previous article code on this topic) into a Code Gallery project for you to play with.
Check it out, on the ADO.NET Team blog Faisal Mohamood, a PM on the LINQ to SQL team, posted on the new features you can expect to see added to LINQ to SQL. This includes support for new data types coming in SQL 2008.
I'll be speaking tonight at the grand re-opening of Bay.NET at their new location near Macy's downtown (Powell St. BART station).
When:
Wednesday, 2/20/2008 at 6:30 PM
Where:
Microsoft San Francisco Office, 835 Market Street, Suite 700, San Francisco, CA 94103
I'll be showing all that is new and cool in Visual Basic 9 including an explanation of all the new language features and how to use them. I'll be there for over 2 hours so expect a lot of demos and a lot of interaction with the attendees. I like to involve people in discussions at user groups because you have a lot more time and usually the crowd is 50 people or under so you can be more engaged with everyone. I promise it will be worth your while so come on out tonight! We can all grab a beer afterwords.
Registration is required but it's FREE. But... if you can't make it, here's the presentation and most of the demo code I'll be showing.
Also check out the EastBay.NET user group where I run a pre-talk talk every month called FUNdamentals. It starts before the main talk from 5:45pm until 6:30pm. This year we're building a software-as-a-service application for pizzerias and we're trying to touch on all the awesome (and overwhelming) features in .NET 3.5 and Visual Studio 2008 so that we can all walk away with a clear picture of how all the pieces fit together and how to get started using them.
In previous posts this month I showed how to use LINQ to SQL classes with a couple different Combobox data binding scenarios. (You can read those articles here and here.) Today I'm going to show you how to create a one-to-many data entry form (and we'll use a couple Combobox lookup lists as well). I'll show you what you need to do to enable proper insert, update and deletes of the hierarchical data. I'll also show how we can specify that these operations happen via stored procedures.
The Database Model
For this example I'll be creating my own database and not using Northwind. This is because Northwind isn't a very typical database especially when it comes to referential integrity. I want to create tables that have non-nullable foreign keys as well as use a timestamp field for concurrency checking.
So here's the database diagram of what we'll be building off of:
I've also specified Update, Insert and Delete stored procedures for each of the tables. This is so we can lock down the database security a bit and disallow UPDATE, INSERT and DELETE SQL statements from executing against it. All we need to grant is SELECT and EXECUTE permissions. This is a typical configuration for databases because it helps prevent against malicious code executing on the database by stopping changes from happening outside the stored procs.
So to get started building my form, I'll start by adding a new item to my project called "LINQ to SQL Classes" which will open the O/R designer and allow me to drag tables in my database from the Server Explorer onto the model's design surface. I'll drag all of the four tables above and since the database is called OMS I'll name the model OMS.dbml. This will create our LINQ to SQL classes and infer the associations from the database relationships. I'll also drag all the stored procs onto the Methods pane.
Next we want to associate the stored procs with the update, insert and delete behaviors for each class. Select the class then in the properties window you will see three properties; Delete, Insert and Update and they are all set to "Use Runtime". Select the properties and you then can specify the procedures in the methods pane to use for each behavior. You can also simply right-click on the class and select "Configure Behavior". On this screen you can specify the behavior for all the classes by selecting the Customize radio button and then selecting the corresponding procedure shown in the method pane.
After you got all of these set up, save the model and this will generate all the LINQ to SQL classes plus the DataContext which is used to manage the connection and communication to our database.
Data Sources and Data Binding the Form
Next we need to get these into our Data Sources window so that we can quickly get our form designed. I showed how to do this before in the previous posts against Northwind. This time I want to create a master-detail form of Orders and related OrderDetails in a grid and I want to show the Customer and Product as lookup lists. Select Add New DataSource from the Data menu and select Object as the Data Source Type. Next, select the Order class and click Finish. This will populate your Data Sources window with the Order and also its related OrderDetails because of the association. We'll also want to add Customer then Product to our Data Sources window as well because we'll need those for our lookup lists.
When you inspect the properties of the classes in the Data Sources window you will see that the associated parent object is also visible along with the child collections. For instance, if you expand Order you will see the parent Customer as well as the child OrderDetails. This indicates the associated parent Customer object for that Order object. I'm going to want to display that information as a lookup list so change the drop control for Customer to "None" and change the CustomerID to a Combobox. I also do not want to display the Modified field so also set that to "None". Then I'll set the drop control of the Order to "Details".
Drag the Order onto the form to set up the controls as well as the BindingNavigator and BindingSource for the Order. Next drag the Customer from the Data Sources Window onto the top of the CustomerID Combobox to set up the CustomerDataSource for the list of items. In the properties for the Combobox set the ValueMember = CustomerID and the DisplayMember = LastName in order to finish setting up the lookup list for Customer.
Next drag the OrderDetails listed under Order onto the form to drop down a DataGridView. You will notice that this will also pull in the parent objects, Product and Order in this case. Just edit the columns and remove those as well as the Modified field. For this example, I'll still display but set the OrderDetailID and OrderID to ReadOnly since these will be filled in automatically for us after we save the data. We'll also need to change the ProductID column type to a DataGridViewComboBoxColumn and then select the Product as the DataSource by selecting Other Data Sources --> Project Data Sources --> Product. This process will create a ProductBindingSource in the component tray.
Also we will need to specify the DisplayMember = Name and ValueMember = ProductID on the column here.
Loading the LINQ to SQL Classes with Data
Now that we have our form designed and the data binding all set up we're ready to create our objects and fill them with data. Unlike when we are using DataSets on our forms, the Form Designer will not generate any loading or saving code for us when using LINQ to SQL classes. But the code we need to write is very straightforward and we can write LINQ queries to limit our result sets. For this simple example I will select all Orders and also all of the Customers and Products into our lookup lists but keep in mind this may be a bad design if there are hundreds of rows in your database. In that situation it's better to write a search form.
So in the Form's Load event handler we need to set up the BindingSource's DataSources with data returned from our tables. First we'll fill the form with all the orders from the Orders table.
Public Class Form1 Dim db As New OMSDataContext Private Sub Form1_Load() Handles MyBase.Load Me.OrderBindingSource.DataSource = db.Orders
Next we want to populate the Customers list. We can get cute and can specify a LINQ query here in order to select the customer names in "LastName, FirstName" format.
Me.CustomerBindingSource.DataSource = From c In db.Customers _ Let LastName = c.LastName & ", " & c.FirstName _ Select LastName, c.CustomerID _ Order By LastName
Finally we want to select our list of Products. Here's a trick that will place an "empty" product at the top of the list so that it can indicate to the user to select a value. We can add validation later to check that the selection has been made by checking that the ProductID > 0.
Dim emptyProduct As Product() = _ {New Product With {.Name = "<Select a product>", .ProductID = 0}} Me.ProductBindingSource.DataSource = (From Empty In emptyProduct).Union( _ From Product In db.Products _ Order By Product.Name) End Sub
You might be wondering why we're not explicitly setting the OrderDetailBindingSource's DataSource. This is because the OrderDetails are loaded from the database automatically only when we access the OrderDetails collection on the Order object. This happens when the OrderBindingSource moves position and the OrderDetailsBindingSource needs to display the OrderDetail objects for the Order. If you want to see the T-SQL statements being run just put a call to db.Log = Console.Out in the Load to display the statements in the Debug Output window. Just make sure to remove it before building your release.
Saving Hierarchical Data
Next we need to add the save code by enabling the save button and handling the click event. In my previous post when we built a single-table entry form with a lookup list I showed how to do this:
Private Sub OrderBindingNavigatorSaveItem_Click() _ Handles OrderBindingNavigatorSaveItem.Click Me.Validate() Me.OrderBindingSource.EndEdit() Me.OrderDetailsBindingSource.EndEdit() Try db.SubmitChanges() MsgBox("Your data was saved.") Catch ex As Exception MsgBox(ex.ToString)
End Try End Sub
Okay so let's give this form a try. Run the form and make a change, and/or insert a new Order, click save, and you should see that everything worked out smoothly. The keys are properly populated on insert and the relationship works correctly. However if we try to delete an OrderDetail (child) row from our grid we get the following error:
System.InvalidOperationException: An attempt was made to remove a relationship between a Order and a OrderDetail. However, one of the relationship's foreign keys (OrderDetail.OrderID) cannot be set to null.
To fix this we need to indicate to the model that we want to delete the OrderDetail when it's OrderID is set to null. Unfortunately this cannot be done in the O/R designer so you have to open the model in an XML editor. Fortunately, once you change it the designer won't mess with it again unless you remove the class completely. Open the dbml file with the XML Editor (just right-click on it an select "Open with...") and locate the XML that describes the OrderDetail class. Notice the association under the OrderDetail table:
<Table Name="dbo.OrderDetail" Member="OrderDetails"> <Type Name="OrderDetail"> <Column Name="OrderDetailID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" /> <Column Name="OrderID" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" /> <Column Name="ProductID" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" /> <Column Name="Quantity" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" /> <Column Name="Price" Type="System.Decimal" DbType="Money" CanBeNull="true" /> <Column Name="Modified" Type="System.Data.Linq.Binary" DbType="rowversion NOT NULL" CanBeNull="false" IsVersion="true" /> <Association Name="Order_OrderDetail" Member="Order" ThisKey="OrderID" Type="Order" IsForeignKey="true"/> <Association Name="Product_OrderDetail" Member="Product" ThisKey="ProductID" Type="Product" IsForeignKey="true" /> </Type>
We need to add an attribute here called DeleteOnNull and set it to true in order to be able to delete a child row independently in the database when calling SubmitChanges(). Once we make this change we can now delete just a single OrderDetail from the grid and save normally:
<Association Name="Order_OrderDetail" Member="Order" ThisKey="OrderID" Type="Order" IsForeignKey="true" DeleteOnNull="true"/>
The other option to fix this issue is to modify the Delete Rule to "Cascade" on the relationship in the database. In that case the designer correctly infers this attribute on the association.
Okay let's run the form again and now when you try to delete an OrderDetail child from the grid and click save, it saves without error. But if you try to delete an entire order by clicking the delete button on the ToolStrip and then save we now get a database error:
System.Data.SqlClient.SqlException: The DELETE statement conflicted with the REFERENCE constraint "FK_OrderDetail_Orders". The conflict occurred in database "OMS", table "dbo.OrderDetail", column 'OrderID'.The statement has been terminated.
This is because unlike DataSets, you can't specify in the model that when you delete a parent, it should cascade to the children automatically. So we need to write some code to do this. There's a variety of ways you can do this and one way I already showed in a previous post by adding code to the DataContext that works nicely if we are not using stored procs. The basic idea is that we need to tell the DataContext to delete the child objects anytime an Order is deleted. In this example, I chose to do this by calling DeleteOnSubmit before we call SubmitChanges. I added this code into the Click event handler for the Delete button on the ToolStrip of the form:
Private Sub BindingNavigatorDeleteItem_Click() _ Handles BindingNavigatorDeleteItem.Click If Me.OrderBindingSource.Position > -1 Then 'Grab a reference to the currently selected order Dim order As Order = CType(Me.OrderBindingSource.Current, Order) 'Ensure that children are deleted when the parent is deleted For Each detail In order.OrderDetails db.OrderDetails.DeleteOnSubmit(detail) Next End If End Sub
Now run the form and try a variety of Update, Insert and Delete operations on the data and you will have a smooth ride. If you enable logging on the DataContext or run SQL profiler on the database you will see our stored procedures being called in the proper order.
Next time I'll show you how we can add simple validation to our LINQ to SQL classes by creating a base business class to inherit from and using the IDataErrorInfo interface along with the ErrorProvider.
UPDATE: I placed the code for this article (including the previous article code on this topic) into a Code Gallery project for you to play with.
We just released a new article by VB MVP, Maurice DeBeijer on getting started with Windows Workflow called Windows Workflow 101. This is the first in a series of Workflow articles by Maurice. If you're struggling with how to use this technology in your applications this is a great place to start. The next couple articles will explore more advanced senarios and building custom activities. Thanks Maurice!
Jim Wooley, VB MVP, has rewritten the original Personal Web Starter Kit using LINQ for the data tier rather than stored procedures and ADO.NET and has put it up onto Code Gallery. The starter kit manages images in a SQL database by album.
You can download the LINQ-enabled Personal Web Starter Kit here: http://code.msdn.microsoft.com/LinqPersonalWeb
By the way, you can see all the latest Visual Basic Code Gallery and CodePlex projects as well as Jim's ThinqLinq blog -- plus all things cool in VB on the Visual Basic Developer Center!
I'll be doing a series of webcasts next month that you should check out if you're trying to get started with the new features in Visual Basic in Visual Studio 2008. (BTW, if you haven't downloaded Visual Studio 2008 or Visual Basic 2008 Express what are you waiting for!?)
If you're interested in seeing what's new in the Visual Basic 9 language then join me for the always popular: Live From RedBethmond: VB9 - What's New in Visual Basic 93/14/2008 9:00 AM Pacific Time (US & Canada)- 3/14/2008 10:00 AM | Duration:60 MinutesVisual Basic is evolving in dramatic ways to help people be more productive when developing enterprise, data-aware applications. In this Webcast we'll go over the major new features in Visual Basic 9, and how these new features can help you write applications much more rapidly than ever before. You will be introduced to XML Literals, Object Initializers, Anonymous Types, Type Inference, Extension Methods, Lambda Expressions and much improved IntelliSense. We'll take a look at how these features tie into Language Integrated Query (LINQ) and how working with XML in Visual Basic provides ultimate performance and productivity. If you keep saying "Man, I really should learn LINQ" or "I wonder if I'm writing this LINQ query correctly?", then you'll want to join me for this one (UPDATED NEW DATE/TIME:)
Live From Redmond: VB9 - Introduction to LINQ in Visual Basic 3/14/2008 11:00 AM Pacific Time (US & Canada) | Duration:60 Minutes LINQ stands for Language Integrated Query and it allows you to query over things like objects, databases and XML in a standard way with a new syntax available in the latest versions of Visual Basic and C#. In this Webcast you will learn how to get started writing LINQ queries using the simple but powerful query syntax available in Visual Basic. We'll walk through a variety of basic queries as well look at aggregates and groups over different data sources. You will also see how writing queries over XML using specific Visual Basic syntax like XML literals and axis properties can help you be much more productive when working with XML.
UPDATED 2/26: Webcast added! Rob Windsor and I will be presenting a bonus webcast on VB6 to .NET migration. If you're involved in a migration push and don't know where to start this webcast is for you. We'll show some proven strategies as well as walk through the Interop Forms Toolkit which can dramatically speed up development in a phased migration approach.
Live From RedBethmond: Migrating Your Visual Basic 6 Investments to .NET 3/28/2008 9:00 AM Pacific Time (US & Canada) | Duration:60 Minutes Visual Basic (VB) 6 was used by millions of developers world-wide to build applications ranging from thousands to millions of lines of code representing significant organizational investments. The path from VB 6 to .NET has not always been clear, there is no one size fits all approach. We’ll cut to the chase, exploring the pros and cons of each option using real world examples. You will leave this webcast with the framework and tools to develop the right strategy for your organization to leverage your existing investments while taking advantage of the power and productivity the .NET Framework provides. Presented By Beth Massi, Visual Studio Community PM With special guest Rob Windsor, Visual Basic MVP
Do you wade through the XML DOM all day? Interested in dumping XSLT for a real language? Or are you just interested in some highly productive language syntax? Then join me for this webcast: (UPDATED NEW DATE/TIME:)
Live From RedBethmond: VB9 - Working with XML in Visual Basic4/4/2008 9:00 AM Pacific Time (US & Canada) | Duration:60 MinutesXML permeates every modern application today from XHTML, XAML, RSS, SOAP, Office Open XML just to name a few. Even your Visual Studio project files and configuration settings are XML files. The latest version of Visual Basic in Visual Studio 2008 supports a new language syntax aimed at making you much more productive when working with XML. In this Webcast we'll walk through language features like XML literals, embedded expressions and axis properties in order to create, query and transform XML with this powerful but easy to use syntax. Say goodbye to XSLT and hello to Visual Basic 9.
I hope to see you all there! It'll be fun to see if I recognize your user names from the comments here on the blog. If you can't make it live, the On-Demand downloads will be available the day after the webcast.
A while back I showed a very simple way you could use XML Literals to generate code. In my previous life we used XSLT to do our code generation of different layers in our applications but as I worked with XML literals in Visual Basic 9, I came to realize quickly how much easier this is using VB.
But don't take it from me, Kathleen Dollard is the expert in code generation and has a dnrTV episode where she shows us how she's using XML literals and LINQ to generate not only VB code, but also C# code, all from the same template!