IHeart Interfaces
- Part One: IDataReader and IDataRecord
by Chad Finsterwald
This code sample applies to ASP.NET in the .NET 2.0 framework. Examples are written in C#.
Contents
- Introduction
- IDataReader (and DbDataReader)
- IDataRecord
- Conclusion
Introduction
Note: This article assumes that you have a basic grasp
of interfaces, abstract classes, polymorphism, and of OO
programming. If you are not familiar with these concepts, I have listed
below a few good resources to get you started.
You've likely heard the principle Program to the interface, not the implementation.
Just about every book on application design that I've
read covers this idea at some point and often
devotes a chapter or two to its exploration.
As is often the case, however, there is a wide gap between knowing
the principle and knowing how to put it into practice.
This gap reveals itself not only in our own class and application design, but often
in our use of the .Net framework itself. My hunch is that
part of the difficulty of putting the principle into practice when using the .Net
framework is that many of us are not as familar as we should be with the wealth
of interfaces (and abstract classes) the framework contains.
This article,
the first in a 435 part series1,
will catalog, explain, and provide examples of the key interfaces in the .Net
framework2 in the
hopes of bridging that gap and making it easier
for us to put in practice what we know in theory.
For the series premiere I decided not to cover an enigmatic, exotic,
or universally applicable interface.
Instead I chose two interfaces that will appeal to the heart of
the working man: IDataReader and IDataRecord.
These interfaces are not the sort to be fêted by technorati fancy lads or
glamorized on the cover of MSDN magazine.
They are more behind the scenes types, the sort you might see
in your database abstraction layer or
find in the signature of some data utility method.
Used properly they can help promote reuse and further decouple your code
from a specific database implementation. Intrigued? Read on...
IDataReader (and DbDataReader)
Description:
IDataReader is part of the System.Data namespace.
It provides an interface, both methods and properties, for reading one or more
foward-only streams of data retrieved from a database.
DbDataReader
is an abstract
class which inherits from MarshalByRefObject and implements
IDataReader as well as IDisposible, IDataRecord, and IEnumerable.
Like IDataReader, DbDataReader provides an interface for doing a foward-only read
of records from a data source.
Most Important Properties/Methods: These are not all the properties or
methods of the Interface, just the ones that I think are most important.
IsClosed: Gets a value indicating if the reader is closed.
Read: Advances the IDataReader to the next row (record). For example
to advance the IDataReader to the first record you would type: reader.Read().
Read is most frequently used in a while loop.
NextResult: Advances the IDataReader to the next result set. A single
IDataReader can hold multiple results set from a batched query. Batch queries like this
can dramatically speed up database calls. For example, from the Northwind database
imagine you want to return all records from the Customers, Employees, Suppliers,
and Shippers table. This can be batched as a single query and each of the
result set (tables) can be iterated through in turn.
Classes that Implement It: IDataReader is implemented by SqlDataReader
and by the abstract class DbDataReader. DbDataReader is in turn implemented by
OdbcDataReader, OracleDataReader, and all other XXXDataReader classes.
How to Use It: The chief use of IDataReader and DbDataReader
is to decouple your code from the particular XXXDataReader you are actually using.
For example, rather than the following:
SqlCommand command = new SqlCommand(queryString, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
You would now code:
...
IDataReader reader = command.ExecuteReader();
In fact, if you use
Microsoft's
Data Access Application Block
it is an instance of IDataReader
that gets returned from the ExecuteReader method.
Why is this important?
Well, the main reason is that when you use an instance of IDataReader your code embodies the
very principle stated at the begining of this article: Program to the interface, not the
implementation.
It promotes reuse by decoupling your code
from a specific implementation. For example, a method like the
one below can be used regardless of whether my database
was Oracle, MYSQL, or SQL Server.
private SomeDomainObject MapObject(IDataReader reader)
{
SomeDomainObject o = new SomeDomainObject();
//In the section on IDataRecord, I will show a better
//way to get the fields from a reader. For now this will suffice.
//For clarity, I assume in the method the data reader gets closed elsewhere
o.SomeField = reader[0];
o.SomeOtherField = reader[1];
return o;
}
Another important reason, and this goes beyond the use of just IDataReader, is
that your code expresses your intent. If you intend for a method to rely on a
specific implementation then your method should reflect that. But
if the contract is more general --for example, it does not matter that
it is a SqlDataReader, only that it is some data reader then your method
should reflect that too! Always expose in your methods the interface
of the type that is the most general in that object's hierarchy to solve
the problem you are trying to solve. Exposing IDataReader and not SqlDataReader
in the above example illustrates this.
IDataRecord
Description: IDataRecord
provides access to the
column values for each row that was retrieved using a DataReader. An
instance of IDataRecord is not created explicitly, but obtained
as part of a DataReader.
Classes that Implement It: The most important class that implements
it is DbDataReader as that class is the base class for all the XXXDataReaders.
Other classes in the framework that implement IDataRecord are
DbDataRecord, SqlDataRecord, SqlDataReader, and IDataReader.
Most Important Properties/Methods: As with IDataReader,
these are not all the properties or methods of the Interface,
just the ones that I think are most important.
Item: Item is an indexed property. It provides
an easy way to get at the field values for the current row. For instance,
reader[0] or reader["theField"]. Item returns an
Object so the returned value will likely need to be cast to the desired type.
GetXXX: Like Item, the GetXXX methods --e.g.,
GetBoolean, GetString, etc.-- give you access to the requested field
for the current row. Unlike
Item, each GetXXX method returns the type appropriate for it.
IsDBNull: Just like it says, this method checks to see
if the field is set to null --IsDBNull(0)
How to Use It: When you retrieve a row of data from
a data reader and access its values, you are using the IDataRecord interface.
Likewise when you have a DataReader dr and you write
string s = dr.GetString(0), that method, and the other GetXXX
methods, are part of the IDataRecord interface. You use this interface all the
time when working with data readers,
even if you are not always aware that you are doing so.
One of the best uses of IDataRecord is
Shawn Wildermuth's
Field class from his excellent, but now dated, book
Pragmatic ADO.Net.
The Field class is a collection of static methods
that provide a simple way to determine if a field is null and if so,
provide acceptable default values for those fields. Its use is illustrated below:
Rather than writing the following when getting values back from a data reader
if (r.IsDBNull(r[0]) == false)
someString = r.GetString(0);
With the Field class you would access the columns via the appropriate
static method corresponding to the underlying field type. If the column
contained a database null value for that field, a type appropriate default
value would be returned instead --e.g., an empty string for strings, zero for
ints, etc. For example,
someString = Field.GetString(r, 0);
or, because it uses the field name and not the index, the even more readable
someString = Field.GetString(r, "theField");
I've included a modified version of the Field class below --which can downloaded by
clicking the "Download the Code" button at the top of the page. Because of the changes
I've made, any fault with the code surely lies with me and not its original author. Given
the topic at hand you should note the extensive use of IDataRecord in the
method signatures. Also, I know that you can do something similar with
nullable types
using the GetValueOrDefault method, but depending on your domain
model this may not be the best approach.
Conclusion
Finally a few
related interfaces that are germane to this topic, but which I am unlikely
to catalog are:
IDataAdapter,
IDataParameter,
IDbCommand,
IDbConnection,
and IDbTransaction.
You should check them out if you are so inclined.
As this is the first article in what I hope will be an ongoing series,
I would welcome any comments, critism, advice that you think would make
future installments more useful. (Give me your worst!!!) I am planning
on covering some of the new Generic Interfaces next time so stay tuned.
Toggle Footnotes Display
1A paraphrase
of Stephen Colbert. In truth, I have no idea how many interfaces there are and
certaintly do not plan to write an article on each as many are likely to be
incredibly boring. Out of curiosity, if someone does know the real number of
interfaces in the framework please
drop me an email. Go back to the reference.
2Because my career and experience has
focused mainly on web application development, the interfaces
I discuss will have a Asp.Net bias.
Go back to the reference.