This document discusses data validation techniques in ADO.NET. It compares data readers and datasets, explaining that data readers provide real-time querying while datasets allow for disconnected and typed data access. The document then covers using events and exceptions to validate data and reject invalid changes, ensuring data integrity when committing updates to the database.
2. Introduction
Our coverage of the ADO .NET framework
in the last lecture focussed on the use of
the data set.
Disconnected, memory resident
representations of underlying data
structures.
In this lecture we are going to look at the
data reader.
Real time query of data.
3. Data Set Problems
Data sets are not an ideal system of
accessing underlying databases.
But then, there’s no such thing as an ideal
solution.
Data sets are memory hungry.
The data you have accessed remains resident
in memory as long as your data set is in scope.
Data sets can be wasteful.
They execute a full query each time, when you
may not actually need that.
Data sets are blocking.
They need to fully complete before you can do
anything else.
4. Data Readers
Whereas a dataset is a disconnected
representation, data readers allow for
real-time querying of the underlying
database.
However, at a cost of flexibility.
Data readers are forward only, and read
only.
It accesses data a record at a time.
Stream-based I/O
5. Data Readers
Data readers are platform specific.
They aren’t generic representations like a data
set.
As such, they vary depending on whether
there is a managed provider framework.
As in, is there a supported database engine.
It cannot access data in more abstract
formats.
It doesn’t allow for access to XML, or allow you
to build your own representation if you needed
it.
6. Using A Data Reader
Data readers can take over the step
where we normally create a data
adapter and data set.
As usual, it’s created from an SQL
command:
OleDbCommand
SqlCommand
The object you will need is a Data Reader
object:
OleDbDataReader
SqlDataReader
8. Reading From A Reader
We use the read method of a data
reader to have it read the next record in
the query.
This returns a true or a false to indicate success.
myReader.read();
Once we’ve read in a record, we access
it in the reader directly:
txtId.Text =
myReader[“customer id”];
9. Reading From A Reader
If we need to read all records, we put it in
a loop:
while(myReader.Read())
{
cmbItems.Items.Add
(myReader["CustomerName"]);
}
10. Data Reader Mechanics
Data readers only ever go forward.
Each invocation of the read method moves
us on one record through the results.
The data stream persists until the data
reader is disposed of.
Data readers are useful for quick and
easy access to data.
And for circumstances where real time
access is important.
11. Data Sets Revisited
In our examples to data, we have been
used what are known as untyped
datasets.
These are data-sets with no compile-time
sanity checks.
These often cause problems:
Array out of bounds
Misspelled fields
12. Typed Data Sets
We can actually make .NET handle a lot
of these problems for us at compile time.
We use Typed Data Sets for this.
These are a little more complicated to set
up than simple data sets.
They require us to use the server explorer.
When we use connection strings, we don’t
need to do that.
13. Untyped Data Sets
This is perfectly valid in an untyped Data Set:
myData.Tables[1].Rows[6]
[“Customer Name”]
What if there’s only one table?
What if there are only five rows?
What if there’s no customer name
field?
14. Typed Data Sets
Typed data sets put a compile-time check on
element access.
Plus, you get to use intellisense on them!
Right click on your project, and choose ‘add new
item’:
15. Typed Data Sets
Typed data sets have an extension of xsd.
Xml Schema Data
Once you have added that to the
project, you can drag and drop tables
onto the xsd file to create the schema.
You can also add necessary data relations to
the schema if needed, via the toolbox.
This creates a class that represents a data
set.
You use it in place of DataSet.
This is what happens when you create a data
connection through the wizard.
Hence why all your data sets end with a number.
16. Using A Typed Data Set
Once you’ve gone through the steps of
setting up a type-safe data set, you use it
like a normal data set:
MyDataSet myData;
myAdapter.fill (myData);
It gets more useful when you actually
come to access the data...
17. Using a Typed Data Set
Now, your data set will contain an
enumeration representing the table:
myData.Customer[0];
And each element of that enumeration
will have a property reflecting a field
name:
myData.Customer[0].CustomerName;
18. Typed Data Set
Typed data sets also expose a range of
other useful methods.
Specifically, each field gets an ‘is null’ method:
for (int i = 0; i < myData.Customer.Count - 1; i++)
{
if (myData.Customer[0].IsCustomerNameNull()
== false)
{
cmbItems.Items.Add
(myData.Customer[i].CustomerName);
}
}
19. Disadvantages of Strong
Typing
A typed data set is a layer on top of an
untyped data set.
As such, it comes with a small performance hit.
However, this comes with a corresponding
increase in productivity.
You shouldn’t have to do things that a compiler can
do for you – if a compiler can do them, they are
trivial.
They are limited by their implementation.
You don’t want to have to roll one by hand!
Some people don’t like strong typing.
These people are wrong.
20. Advantages of Strong Typing
Compile-time sanity checking.
You trade run-time errors for compile-time
errors.
That’s always a smart trade.
Intellisense support.
Seriously, this is major.
Provision of standard functionality.
Ease of data access
Ease of data comparison
21. Data Readers versus Typed
Datasets
Which do you want to use?
Data readers offer performance improvements.
Data sets are implemented internally with a data
reader.
This performance gain is usually marginal.
Data readers require you to build a data
representation yourself.
When manipulating data, a data set is
more appropriate.
When doing a quick ‘query then forget’ a
data reader may suffice.
Your needs will dictate which is best.
22. Data Validation
Data validation serves to meet two
requirements.
Ensure that invalid changes to the
database can be stopped before they are
committed.
Deal with run-time errors to provide a
seamless user interaction experience.
Data is valuable.
Indeed, usually more valuable than any
other element of a system.
23. Two Mechanics
Two mechanics exist to allow you to
control data as it enters the system.
Give the user a chance to validate
information for correctness.
Validate editing as it is done.
The data set object gives you a number
of methods for commiting changes.
RejectChanges
AcceptChanges
24. Reject Changes
if (myData.HasChanges()) {
response = MessageBox.Show("Do you want to
“+ “commit these changes?", "Commit
Changes?",
MessageBoxButtons.YesNo);
if (response == DialogResult.No)
{
myData.RejectChanges();
}
else
{
myData.AcceptChanges();
}
}
25. Change Committal
Committing changes to the database
is done using update().
This requires the HasChanges method to
evaluate as true.
Otherwise, nothing happens.
We can call it on the data set itself.
Or on a specific data row if we need finer
grained control.
Calling it on the data set trickles down
to all internal objects.
Each DataRow has a RowState
property that holds its internal state.
26. Accepting Changes
When you call update, as part of its
standard Operating Procedure, it calls
Accept Changes.
And this sets the RowState property
appropriately.
One or the other should be used, rather
than both.
They don’t Play Nicely together.
27. Accepting Changes
Changes must be committed to be
persistent.
If you have a function that adds a new row
each time you click the button, every time
you click it you’ll lose the last changes.
This is handled internally by a Data
Versioning system.
Each row in a table has a property which
handles its current ‘version’.
28. Data Versioning
Each row of a table contains multiple
versions of the changed data.
This is useful for revision control.
Each row contains the following
properties:
Current – the current version of the data (after
your changes)
Original – the data before you started meddlin’
Proposed – The data that you have modified.
This is only available temporarily
Default – The default value for the data (usually
the original, or set from the data structure).
29. Version Control
This gives you fairly fine-grained control over
committing data to a database.
You can attach event handlers to various controls
to allow you to control this.
We can register an event for a changing
row like so:
myTable.RowChanged += new
DataRowChangeEventHandler(Change_
Row);
Change_Row is the method that will be
executed when this event is triggered.
30. Event Handlers
Each event has a special event handler
class to deal with it.
It serves as a wrapper around a method.
They stem from either DataRow or
DataColumn
DataColumnChangeEventHandler
DataRowChangeEventHandler
Throw exceptions in these methods if you
want to prevent data being committed.
31. Event Handler
private static void Change_Row(object sender,
DataRowChangeEventArgs e) {
if ((int)e.Row["CustomerId"] < 0)
{
throw new ArgumentException("Customer ID must “ +
“be positive");
}
}
Why do this?
Why not let the database itself handle it?
This way we can provide meaningful user
feedback.
And take advantage of strong typing to provide
context and convenience.
33. Preventing or Compensating
Exceptions allow you to catch errors that
you don’t have any explicit control over.
Users typing into a data grid, for example.
You also have the chance to validate
within code before you ever trigger an
exception.
Deal with things in if statements or validation
functions before they get that far.
There are plusses and minuses to both
approaches.
34. Conclusion
ADO .NET gives us the best of both worlds with
database driven applications.
Strong syntactically correct typing.
Fine-grained control over updating, removing
and changing data.
As part of good interface design in any
language, you must commit yourself to
meaningful error messages.
These are only possible if you have fine-grained
control over validation.