
Introduction
Wouldn't it be great to drag your database model into Visual Studio designers, add your own code to the generated datasets, bind your business objects to forms just like the built-in ADO.NET DataSets?
Anyone who has tried to create a subclass of a Typed DataSet will have discovered that this is very limited. You can create a subclass of the DataSet itself, but it is impossible to create a usable subclass of the DataTables or DataRows.
If you look at the code that the MSDataSetGenerator generates, you see that most of the interesting stuff is not overridable. Shawn Wildermuth, who calls himself The ADO Guy, created his own DataSetGenerator to solve this problem. You can find a description of the how and why of this great tool on his web site. To be able to understand the rest of this article, I urge you to read his explanation first.
Do use my version of the generator, though, because I made some small changes that make it possible to put the base classes in a different assembly than the sub-class.
Installing the DataSet Generator
When you add a DataSet to your Visual Studio project, you get an .xsd file, with MSDataSetGenerator as the associated Custom Tool. Select the file in the Solution Explorer and look at the Property Grid. To create a Typed DataSet that you can inherit from, you have to replace this with AGDataSetGenerator. But first, you have to tell Visual Studio where it can find this tool. For this, you have to run reg.cmd and VSDataSetGenerators.reg. If you want to put AGDataSetGenerator.dll in a different folder, you may have to change reg.cmd. I did not make a fancy installer, but if anyone cares to build one for me, send it over and I'll add it to this page.
Creating Base DataSets
Creating inheritable DataSets is almost exactly like normal typed DataSets. Just add a DataSet to your project and drag your tables from the Server Explorer onto the design surface. To be able to add annotations, you add xmlns:codegen="urn:schemas-microsoft-com:xml-msprop" inside the <xs:schema> tag. To read about this, search for "annotations with typed DataSet" in the Visual Studio help files.
These are the two lines in the XSD file that I changed to create my base class:
<xs:element name="Orders" codegen:typedName="OrderBase"
codegen:typedPlural="OrdersBase">
<xs:element name="OrderDetails" codegen:typedName="OrderDetailBase"
codegen:typedPlural="OrderDetailsBase">
Creating DataAdapters
DataAdapters need to be hosted somewhere. This can be a Form or a Component. In my applications, I create a separate Component for each set of tables. In the example, I have one component that contains DataAdapters for both Orders and OrderDetails. I use multiple DataAdapters for the same table, if I want to use different selection criteria for filling a DataSet from the database.
I have my own version of optimistic concurrency control. For this, I have to change the stored procedures that are generated by the DataAdapter wizard. After changing the Stored Procedure, I have to re-run the wizard, to update the DataAdapter with the new Stored Procedure.
UPDATE Orders
SET Customer = @Customer, OrderDate = @OrderDate
WHERE (Id = @Original_Id) AND (Customer = @Original_Customer)
AND (OrderDate = @Original_OrderDate) AND (Timestamp = @Original_Timestamp);
... becomes:
UPDATE Orders
SET Customer = @Customer, OrderDate = @OrderDate
WHERE (Id = @Id) AND (Timestamp = @Timestamp);
These are the steps to follow:
- Make
DataAdapters by dragging tables from Server Explorer onto the design surface of a Component.
- Choose menu option "Configure Data Adapter".
- Choose "Create new stored procedures".
- Edit generated stored procedures: remove
@Original_... parameters and do optimistic concurrency by means of Timestamp field.
- Go to "Configure Data Adapter" again, and refresh the data adapter with the changed stored procedures ("Use existing stored procedures").
Derive from the Base DataSet
Most of the functionality of the DataSet Generator is covered by Shawn Wildermuth. He describes how you have the option to derive from the base DataTable, or use the base class directly. He explains the few extra lines of code you have to put in every subclass to glue it all together.
The things I will explain here are:
- Handling identity fields
- Optimistic concurrency
Identity fields: both SQL Server and ADO do autonumbering. If they both start at 1 and increase by 1, chances are that you get collisions when updating a DataSet to the database. To prevent this, simply set both the AutoIncrementSeed and AutoIncrementStep to -1 in the XSD designer. This is what you get in the XML:
<xs:element name="Id" msdata:ReadOnly="true" msdata:AutoIncrement="true"
type="xs:int" msdata:AutoIncrementSeed="-1" msdata:AutoIncrementStep="-1" />
New records in the DataSet get a temporary ID value. When they are INSERTed in the database, the DataAdapter takes care of writing the ID value that the database generated into the DataRow in memory. This all works great, but if you want to separate between the DataSet and the Service that communicates with the server, you need a temporary DataSet to hold only the changes. To merge the changes back into the original DataSet, you can't use the ID field, because they no longer match for INSERTed rows. To circumvent this problem, you have to add a temporary identity field to the DataSet. That's why we have this code in the DataSet:
Protected Overrides Sub InitClass()
MyBase.InitClass()
Dim Column As DataColumn
For Each Table As DataTable In Me.Tables
Column = New DataColumn("ClientSideID", GetType(System.Int32), Nothing,
System.Data.MappingType.Element)
Column.AutoIncrement = True
Column.ColumnMapping = MappingType.Hidden
Table.Columns.Add(Column)
Next
End Sub
In the Update method of the DataSet, where we get back the temporary DataSet from the DataService, the ClientSideID is used to match the original row with the updated row. You'll find this code in OrderSet.vb.
Optimistic concurrency is handled by adding a Timestamp field to every table in the database. Updates for records that have been changed by another user will fail. Provided you have set ContinueUpdateOnError = True on the DataAdapter, the update for other records will succeed. After the update operation, you can inspect the rows that are returned by DataTable.GetErrors.
Ideas for Using the DataSets
The framework I presented here solves some common issues, such as mapping database fields with object members, identity fields, optimistic concurrency. It also separates between the objects and communicating with the database. The next step could be to hide the Service objects containing the DataAdapters, in a web service. I tried this, and it works beautifully.
People fond of design patterns could apply the Factory pattern to build a DataAdapter for a DataSet on-the-fly. You could also create your own DataAdapterGenerator, or your own StoredProcedureGenerator. In my opinion, it's best not to use too many code generators, though. You might end up spending more time on tweaking your generators than building the software you are paid for.
Summary
- Use the built-in designer tools of Visual Studio as much as possible.
- Save on O/R mapping tools - they aren't perfect either.
- Try to limit the number of necessary manual changes (e.g., the stored procedures).
- Make the necessary changes by hand and do not try to reinvent the wheel.
| You must Sign In to use this message board. |
|
|
 |
|
|
 |
|
 |
I have not switched over to 2.0 (VS 2005), so I can not test it myself, but I think a few changes to reg.cmd and VSDataSetGenerators.reg will suffice. Look for the version numbers in the path names and change them to the correct numbers for your system. If you succeed, it would be nice if you could post the required changes here. Thanks in advance.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
this reg file will register the dataset generator with visual studio(VB & C#)...
<begin> Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Generators\{164B10B9-B200-11D0-8C61-00A0C91E29D5}\AGDataSetGenerator] @="ADOGuy DataSet Generator" "CLSID"="{87BF1B1C-E1DF-4f24-A07E-2BD9B8CAD316}" "GeneratesDesignTimeSource"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Generators\{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}\AGDataSetGenerator] @="ADOGuy DataSet Generator" "CLSID"="{3C13044D-394D-45cd-89FF-51C885BFBCD9}" "GeneratesDesignTimeSource"=dword:00000001
<end>
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
 i used the AGDataSetGenerator.dll, but it always prompt "can't find the customer tool AGDataSetGenerator in your system."
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Had a requirement to provide typesafe access to column names in typed DataSets (for databinding etc.) so changes in the XSD could be caught at compile time instead of breaking at runtime.
The change exposes column names in a struct, so: row("Id") becomes row(TypedDS.TypedTable.ColumnName.Id)
In case anyone finds it useful - there are three modifications to ImprovedDataSetGenerator.cs:
1) add new method GenerateTableColumnNameStruct -
protected virtual CodeTypeDeclaration GenerateTableColumnNameStruct(DataTable table) { CodeTypeDeclaration columnNameStruct = new CodeTypeDeclaration("ColumnName"); columnNameStruct.IsStruct = true; CodeMemberField dummy = new CodeMemberField(typeof(byte), "dummy"); dummy.Attributes = (dummy.Attributes & ~MemberAttributes.AccessMask) | MemberAttributes.Private; columnNameStruct.Members.Add(dummy); foreach (DataColumn column in table.Columns) { CodeMemberField columnNameConst = new CodeMemberField(typeof(string), column.ColumnName); columnNameConst.InitExpression = new CodePrimitiveExpression(column.ColumnName); columnNameConst.Attributes = (columnNameConst.Attributes & ~MemberAttributes.AccessMask & ~MemberAttributes.ScopeMask) | MemberAttributes.Public | MemberAttributes.Const; columnNameStruct.Members.Add(columnNameConst); } columnNameStruct.Comments.AddRange(GetComments("Column name struct")); return columnNameStruct; }
2) add a call to GenerateTableColumnNameStruct in GenerateTable;
3) replace TableColumnFieldName with:
protected virtual string TableColumnFieldName(DataColumn column) { return "_column" + this.RowColumnPropertyName(column); }
- in case there's a field in the XSD called ColumnName...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Each table already has a read-only property of type DataColumn for each column. Why not use these? Please explain.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Mainly because the DataColumns are instance properties, whereas the struct gives you access to column names without needing an instantiated object - useful for setting up table mappings and generally avoiding hardcoded column names.
Also a matter of preference - in a typed table the predefined column names are static and relate to all instances, so I find row(TypedDS.TypedTable.ColumnName.Column1) more logical than row(row.Table.Column1Column.ColumnName).
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Some new thoughts on the subject: would it be interesting to have an enum of all column names? The enum would translate the strongly typed column name to an integer, to be used as index into the Columns collection. I haven't tried it, so I don't know if the code generator supports creating enums.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi,
I've been looking at the code for this project, as well Shawn's original version and his ADO.NET power toys. There's one thing that bothers me about all these projects. In GeneratorBase.cs, you can find code such as this: CodeCatchClause local0;
local0 = new CodeCatchClause(); local0.CatchExceptionType = Type(type); Together with the method names, and so on, every indication is this file was a straight disassembly of System.Data.TypedDataSetGenerator. I don't believe Microsoft gives people the license to do this. Did Shawn or anyone else get explicit permission to do this? If so, this permission should be well documented. Othewise, I don't see how this code could be legal. 
Thanks.
|
| Sign In·View Thread·PermaLink | 1.20/5 |
|
|
|
 |
|
 |
I don't know about all the details, but in the version for Studio 2002 Shawn could inherit from the .NET framework GeneratorBase. In version 2003 this was no longer possible (the class had been made private), so Shawn had to write his own copy of the class. I think Shawn has good contacts with Microsoft, so I assume this is all legal. But I do agree it would be more elegant if this were documented.
On Shawn's site you can find a discussion (in his blog, I think) he had on inherited datasets. Anyway, if Microsoft really had a problem with this, they would have contacted me by now  Until then, enjoy the tool.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Check this code snippet...
<Obsolete("TypedDataSetGenerator class will be removed in a future release. Please use System.Data.Design.TypedDataSetGenerator in System.Design.dll."), HostProtection(SecurityAction.LinkDemand, SharedState:=True, Synchronization:=True)> _ Public Class TypedDataSetGenerator
...etc, etc!
...how else are we supposed to learn? if not from the best(i.e MS)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi, I just found this artical today and I am interested but the referenced sample chapter has moved, can you help me find the new location? Thanks.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
If you mean the sample chapter from Shawn Wildermuth's book: go to his web site www.adoguy.com, and click on "Sample chapter" in the menu on the left.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
There is no "click-and-run" installer. You just put the files in a folder of your choice and run the VSDataSetGenerators.reg and reg.cmd.
|
| Sign In·View Thread·PermaLink | 4.00/5 |
|
|
|
 |
|
 |
This is good, but your installer doesn't work on Windows 2000 or any installation which has not be installed to C:\WINDOWS
A simple fix would be to change the reg.cmd from
C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\regasm
to
%WINDIR%\Microsoft.NET\Framework\v1.1.4322\regasm
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
finally the typed dataset generator has a new "home" in the internet 
thanks for your effort to putting things together.
regards,
manuel
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Pinx,
Thanks for putting this together. The typed data sets have too many limitations right out of the box. Being able to extend them and add our custom logic is great!
Thanks!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
 | Great  Oskar Austegard | 5:47 27 Oct '04 |
|
 |
Pinx,
Thanks for continuing the work that Shawn started and then regretfully abandoned.
Could you perhaps edit your article to expand on both Shawn and your changes to the XSD generator? I think that would make people realize the annoyances and limitations of the original tool, and give a better example of how Shawn's and your work improves the situation.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Often you would not want to use (waste?) your single inheritance in inheriting from a persistance class, but inheriting from a business base class and attaching your persistance behaviour by composition
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Hi Syd, I have been thinking what you mean exactly, so I could have misunderstood you. The first step is to inherit from dataset, creating a Strong Typed DataSet. The next step is to inherit from this base class, to add your own business specific behaviour. If you want, you can inherit from this class again, to add behaviour (e.g. instance variables) that is relevant to only a part of your application. I do this in my own application, too, in a few places. If you want to create a tree structure, e.g. Person, having subclasses Manager and Employee, which both have their own table in the database, that would not be possible. You could, however, create these subclasses and make the type of subclass dependent upon some field in the table Person. The factory pattern could be used here, I guess. So, in my opinion, you're not wasting single inheritance at all, just using as it is supposed to.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
|
 |
|
 |
I don't know what the exact definition of a business object is. Usually your database model maps to the important objects in your problem space. With a mapping from the database to your programming environment by means of Strong typed datasets, you have easy access to the data. If you can add functionality to the DataSet objects, you're very close to business objects. Anyway, the point of my 'tool' is that it works, that it's easy, and that it conforms to the .NET built-in objects. If you're familiar with ADO.NET, you can use it straight away.
|
| Sign In·View Thread·PermaLink | 2.33/5 |
|
|
|
 |
|
|