Since then I’ve had similar questions about how to do the same thing but using a SQL CE (Compact Edition) database so I thought I’d present the updated code to work with this type of database. Let’s take a look at how to do that, but first some background. (If you don’t care, just skip to the end for the code and sample download ;-))

To add a local database file to your Visual Studio 2008 project just select Add –> New Item, choose the Data category and then select Local Database. This will add a SQL CE database file with an .sdf extension to your project.

This will trigger the Data Sources wizard to start but first we need to create some tables in our database so cancel the wizard. Next open up the Server Explorer (or Database Explorer if you are using VS Express) and you should see your SQL CE database listed under Data Connections. Expand the database node and then right-click on Tables and select New Table. A dialog will open that allows you to design your table schema.

For this example I’ll create a parent Categories table and a child Products table with just a few fields to illustrate the concepts here. It’s important when you create a primary key that you choose the int data type, set Unique to Yes and then below in the column properties you set Identity to True. This will create an auto-incrementing, unique primary key.

Click OK when you’re done and then repeat the same process to add a new Products table. This time though we need to specify a field for the CategoryID foreign key and I’m going to make this a required field by setting Allow Nulls to No.

Now we need to add a relationship between these tables so that our little database will maintain referential integrity for us. We’re saying that a Product cannot exist without specifying a Category. We want the database to enforce this so that if we try to delete a Category it will prevent us from doing so if there are any Products. SQL CE 3.5 can maintain this kind of referential integrity for us, just go back to the Server Explorer and right-click on the Products table again but this time select Table Properties. Select the Add Relations page.

Type in the name of the relation you want to create and then select the Foreign Key Table Column, in my case I select CategoryID. Notice that you can also set up cascading or set null/default update and delete rules as well, but for this example we want to leave the rules as NO ACTION. Click Add Columns button then Add Relations button then click OK to save and close.

Setting up the Parent-Child DataSet

Now that we have the database set up we can design our DataSet. This is going to be almost exactly the same as how we set up our Access DataSet here so take a look at that post for the screen-shots, they’ll be the same here. To recap, you need to make sure you set up the relationship on the DataSet properly so that the primary key on the parent will cascade to the foreign key on the child. Right click on the relation in the DataSet designer and select "Both Relation and Foreign Key Constraint" and then set the Update and Delete rules to Cascade.

The other important thing you need to do is set the Primary Key fields on both DataTables to ReadOnly False. We need to do this so we can set them in code and have the DataRelation cascade rules work correctly.

The DataSet designer will set all the rest of the properties and statements up correctly so you don’t have to modify anything else, though I do encourage you to take a look through them. One important thing to notice is the AutoIncrement, AutoIncrementSeed and AutoIncrementStep properties here. These are set to True, –1 and –1 respectively. This means that on the DataSet (client side) the referential integrity on new rows is maintained between the products and categories DataTables using temporary primary keys that are negative integers. These do NOT correspond to the keys in the database for new rows. It’s not until we send the updates to the database that we get the real identity keys so keep that in mind. (And these properties are the same regardless if you are using SQL CE, Access, or SQL Server identity keys.)

Okay so now we are ready to design our master-detail form. This should be a familiar process at this point but just in case here’s a recap. Open the Data Sources window (Main Menu –> Data –> Show Data Sources) and you should see the Categories and Products DataTables that are in the DataSet we just created. Design your Master-Detail form like normal. For this example I drag the Categories as details and then select the related Products by expanding the Categories node and dragging the Products table under there. This will set up a relationship on the form as well so that when we select a Category, it will only show those related products. This is also important to get our inserts to work correctly because the temporary identity key (-1, –2, –3, etc) on the CategoryID will automatically cascade to the Product’s CategoryID.

Loading and Editing the Parent-Child DataSet

Now that the DataSet is set to enforce the foreign key relationships, this means that you must have a parent for every child so you have to load the data in parent then child order. Remember, you have to make sure that every row in the child DataTable will have a corresponding parent row in the parent DataTable. This also means that you have to make sure to call EndEdit on any new parent BindingSource before any children can be added. I’m doing this by adding a handler to the grid’s Enter event. So the code-behind for this form is the same as the Access sample I showed before:

We need a way to set the primary key on the parent right after the row is inserted into the database and before any children are inserted. Now that we have keys cascading we just need to write code to handle the RowUpdated event on the DataAdapter inside the TableAdapter partial class. TableAdapters are generated classes that Visual Studio creates for us from the DataSet designer. These classes are declared as Partial Classes so that means we can add code to the same class even if it’s in a separate file. Right-click on the TableAdapter class in the DataSet Designer and select View Code and the partial class file that you can edit will be created for you.

Now we can write code to automatically query the database for the identity key because SQL CE supports the @@IDENTITY command just like Access. We need to execute this query after each new row has been inserted into the database, but before any children. If you’re using Visual Studio 2008 then the TableAdapterManager will handle sending parents first then children for insert operations so all we need to do is handle the DataAdapter’s RowUpdated event. Here’s the complete code listing for the DataSet and TableAdapter partial classes which includes code to set default values on the new rows. Notice it’s very similar to the Access code. We’re just working with a different data access client library by importing the System.Data.SqlServerCe instead of System.Data.OleDb.

Now when we run our form, click the Add button on the ToolStrip to add a new Category and then enter new Products in the DataGridView below. Click Save and you will see the identity keys refresh back into the DataTables from our SQL CE database.

So to recap:

Create your SQL CE tables, set the PKs to an Identity Integer column and then set up the relation to enforce referential integrity

Create your DataSet and edit the relation and set it to a “Relation and Foreign Key Constraint” then set the Update and Delete rules to Cascade

Make sure the PKs on the DataTables in the designer are set ReadOnly False

Make sure to design your form so that the parent and related children BindingSources are set up properly

Call EndEdit on the ParentBindingSource before you attempt to insert any child rows into the child DataTable so that the parent row is committed to the parent DataTable

Use the TableAdapterManager to handle updating parents and children in proper order (this happens by default if you use the designer)

Add a handler to the DataAdapter’s RowUpdated event to query the database for the new identity (SELECT @@IDENTITY) and set the PK on the DataRow to that value and this will cascade to any related children automatically

I hope that clears up the confusion on how to work with file-based databases like Access and SQL CE that don’t support batch statements. Once you understand how ADO.NET is working with your DataSets then it’s much easier to understand how to configure things like this. SQL CE is a great FREE database for single-user applications and I encourage you to have a look at it if you’re building these types of client applications.

SQL CE also supports the Entity Framework so that would probably be a good follow-up post to this one… next time! ;-)

Thanks for your sharing and the information is valuable. I'm using SQL CE as local database as the same as the scenerio as yours except I'm not using the data binding control. How can I retrieve the ID (Auto-generated) after I call the Insert command from the Strongly-Typed Dataset?

I install VB 2008 Express and use XP SP2 for the OS. I hope you can answer my difficulty.

Thanks for the attention.

Elainw

29 Mar 2010 1:35 PM

Hi,

Thank you for this great information. I am not new to programming, but I am new to Visual Basic. I am trying re-create what you've done, as part of a learning exercise, but with using details, as opposed to gridview, to populate my form. In order for this to work, I know that I need to change the ProductsDataGridView_Enter() sub routine, but I'm not sure how. Would you mind giving me a pointer?

You just need to call the parent bindingsource's EndEdit method before you enter the child data.

HTH,

-B

Diogo

28 Apr 2010 3:11 AM

Hi Beth,

I'm having a small problem on my program and i hope you can help me out.

I'm using an SQL CE that already has some values in it, and i have one table that it's PK is AutoIncrement but when i try to use the TableAdapterManager to insert a new value on that table the first time i try to do that it gives me an error of duplicate primary key but the second time it works fine..

I'm new to VB.Net, coming from VB6. Is there some significance to the leading underscore in the "_adapter_RowUpdated" event? Why isn't it named "CategoriesTableAdapter_RowUpdated" or something similar instead?

Jon

15 Nov 2010 6:18 PM

I was haing this problem in C# and having a difficult time solving after reading all of the above.