Hi David,
It's nice to find someone else that's thinking along these lines. Not
everyone has SQL-92 data, or binds directly to relational database
tables, after all.
Is it possible to use user-defined types and not only system types when generating a typed DataSet?
I've found that yes, this is possible - I'm doing it in my
application. It's not easy, but if you can get your DataSet to reflect
real types you'll get a big payoff further down the line where your
data-bound controls can start looking at the type information through
reflection and acting accordingly. It's actually a shame MS didn't go
the extra mile to cater for this by default, but maybe we'll see it in
a future framework release.
Specifically, Is it possible to specify a user-defined type, capable of implicit conversions to/from the corresponding DB type in the XSD schema, so it will be there in the generated typed DataSet?
The answer to this is no, not directly, if you intend to rely on the
standard .NET DataAdapters. But if you're willing to write your own...
If you're like me you started out by editing the dataset schema file
directly, and by trial-and-error you found out about the DataType
attribute of the msdata namespace
("urn:schemas-microsoft-com:xml-msdata"). For example:
<xs:element name="Key" msdata:Caption="ID" msdata:ReadOnly="true"
msdata
ataType="MyNamespace.MyDataType" />
I can't find any documentation on this attribute, but as it turns out
you can put virtually any type you like in here, and the
MSDataSetGenerator _should_ generate a strongly-typed dataset that
reflects that type. The generated DataSet, after all, just contains a
bunch of managed code property getters/setters that give access to
your data - why shouldn't they return real types instead of only
SQL-92-bindable types? However, if you try this, you'll probably find
that the MSDataSetGenerator chokes on any type that's not part of the
Framework, or indeed on any type that does not yet exist or is not
currently loadable.
I punted on this whole issue by simple replacing the
MSDataSetGenerator with one of my own that does almost exactly the
same thing as the original, but isn't so picky about the types it's
given. If you mis-type a name or pick a type that doesn't exist,
you'll get compile-time errors. Also don't expect any help from the
IDE to edit your schema using the drag-and-drop editor - you're going
to be maintaining these schemas in the text editor.
What this gets you is a generated class representing your
strongly-typed dataset that does everything the MSDataSetGenerator one
would do, except it's truly strongly-typed because you've got the
_real_ types in there. Your types.
Now you've got another problem, however. The SqlDataAdapter doesn't
know anything about your types, or how to construct them from the data
that's in the database, so it'll certainly choke if you try to Fill
your DataSet with one. So - you guessed it - you write your own
DataAdapter:
public class MyDataAdapter : DataAdapter
{
....
}
This can be hard or easy, depending on where your real data is. In my
case I _have_ to write my own adapter anyway because my data comes
from a middle-tier of legacy COM objects, not directly from a SQL
database. In my adapter I'm just reading lists of objects from the
server and building up the tables of the DataSet but in this case I
have control over how that happens and I can perform whatever
conversions are necessary from the data objects to the DataSet. This
also gives me a layer in which I can re-shape or re-map data however I
like if the database guys or the server guys haven't done a
particularly good job of object design (compound keys can be
simplified in this way). If you're accessing a DB directly, you need
to get into DataReaders, Connections and Commands.
So far so good, but if you rely on DataSet.WriteXml and
DataSet.ReadXml for serialization, you'll find that the serialization
algorithm will serialize your types by just calling ToString() on your
object, so you'll need to address that if necessary. I haven't found a
need for this yet, so you're on your own there. Again, it's a shame
that the serialization algorithm doesn't look for some interface (eg.
"XmlSerializable") and call ToXml(), FromXml() to service custom
types.
That seems like a lot of work to get real types in a DataSet, but the
payoff comes when you bind to controls. Everything will work when you
bind to a standard control like the DataGrid, and your custom types
will be rendered as the ToString() of the object, which is probably
exactly what you want. The real payoff comes when you derive your own
control from the standard one, and add some intelligence based on your
custom column types. In my application I have a control derived from a
third-party data-bound grid control that looks at the schema of the
bound DataSet and configures the columns accordingly, eg:
- Correct column headings (from msdata:Caption instead of the element
name)
- Editability of columns (based on msdata:ReadOnly)
- For date-type columns my grid can pop-up a date picker to edit the
cell and the grid can validate accordingly
- I have a Type called User which represents a user in my system -
those columns can present a drop-down of the user list
- I have many columns that represent enumerations - the correct
dropdown can be configured for each column
- I can distinguish between Money types and regular Floats and
validate them accordingly, or present a picker that allows the user to
pick the currency at the same time as the amount
All of this is centralised in the Control - my application just
performs the filling and binding - the grid is smart enough to infer
what it needs from the types in the Schema. This is crucial for an
application that has hundreds of grids across dozens of screens - you
don't want to be writing this code over and over again.