Database Concepts  «Prev  Next»

Lesson 4

Data Access Components: ODBC, ADO, and Modern Alternatives

Lesson 2 looked at how a single table is built, and Lesson 3 compared the database systems a table might live inside. This lesson asks a different question: once VirtualBookShelf's Customer and Inventory tables exist inside SQL Server 2025, how does a Razor Page actually reach them? The answer runs through a layer of technology called middleware, and understanding where today's layer came from makes it much easier to use well.

How Does a Web Application Reach Its Database?

VirtualBookShelf's course project requires a Razor Pages application to read from and write to a SQL Server 2025 database. That raises a natural question:
"How does the web application actually access the database?"
Visual Studio 2026 is good enough at wiring an application to a database that it hides most of the mechanics from view, which is convenient, but it can also leave a developer unsure what's actually happening underneath a working connection string and a successful query. Understanding a little about the technologies involved makes that hidden work far less mysterious. Middleware is the general name for the software layer that sits between a web application and whichever database it talks to, and three terms are worth defining up front:
  1. Web application: a collection of pages that together make up a web site.
  2. Interface: a defined way of accessing an application, an object, a piece of hardware, or, in this lesson's case, a database.
  3. Middleware: the software layer that enables communication between an application and the database it needs to reach.

Where This Terminology Comes From: MDAC and WDAC

The specific technologies this lesson covers trace back to a Microsoft technology stack called Microsoft Data Access Components, MDAC, later renamed Windows Data Access Components, WDAC. MDAC and WDAC are themselves retired today, but the layered structure they established is still a useful way to organize the technologies this lesson covers, and the vocabulary they introduced is still very much in use:
  • Low-level APIs talk to a database close to the metal: ODBC and OLE DB, covered below, both fall into this category.
  • A high-level object model wraps a low-level API in an easier-to-use set of objects: classic ADO, also covered below, is exactly this kind of wrapper.
  • Later successors, outside MDAC's original scope entirely: ADO.NET and, built on top of it, Entity Framework Core. Neither one is "part of MDAC" in any historical sense; both were built later, for a different platform, with a different architecture underneath.
That last point is worth sitting with for a moment, since it's easy to assume ADO.NET is simply a newer version of ADO because the names look so similar. It isn't. ADO.NET was designed from scratch for the .NET platform, and it doesn't sit on top of OLE DB the way classic ADO does. The shared name is a historical accident of naming, not a sign of shared ancestry.

The Low-Level Layer: ODBC and OLE DB

ODBC

Open Database Connectivity, ODBC, is a standard protocol for accessing relational databases. Application programmers use ODBC to pass SQL queries to a relational database without writing separate code for every different database system they might need to reach. Companies that build database systems write ODBC drivers specific to their own product; because every driver exposes the same standard programming interface, an application such as VirtualBookShelf could, in principle, reach databases from several different vendors through the same ODBC calls. As Lesson 1 noted, ODBC is still a real, actively maintained standard today, most often encountered now in reporting tools and business intelligence software rather than in a typical ASP.NET Core application's own data access code. A piece of ODBC vocabulary still floating around today is the Data Source Name, or DSN, a named configuration that bundles together a driver, a server address, and authentication details, so an application can refer to "the VirtualBookShelf database" by name rather than repeating its full connection details everywhere it's needed. ADO.NET's connection string accomplishes much the same goal in a more modern form.

OLE DB

Object Linking and Embedding, OLE, is an older Microsoft technology for building reusable objects. OLE DB is a collection of objects built on that foundation, letting an application access data as objects rather than through raw protocol calls. OLE DB organizes itself around data providers, such as a database, which hold the actual data and make it available, and data consumers, such as classic ADO, which use that data. Unlike ODBC, OLE DB was designed to reach both relational and non-relational data sources through the same object model, which is part of why it could sit underneath a general-purpose object layer like ADO rather than being tied to one specific kind of data source.

Other VB-Era Technologies: DAO and RDO

Two more names are worth recognizing, even though neither plays any role in this course. Data Access Objects, DAO, was Visual Basic's original object model for working with Microsoft Access databases specifically, predating ADO. Remote Data Objects, RDO, filled a narrower niche: reaching a remote ODBC data source efficiently from classic Visual Basic, before ADO eventually absorbed most of what both DAO and RDO did into one more general-purpose object model. None of these three, DAO, RDO, or classic ADO, has any direct role in an ASP.NET Core application; they're worth recognizing mainly because older documentation, and older developers, still use these names.

The High-Level Layer: Classic ADO

ActiveX Data Objects, ADO, is a collection of objects that sit on top of OLE DB, giving an application a simpler way to connect to a database, run commands, and work with the results, without dealing directly with OLE DB's own lower-level interfaces. For its era, ADO genuinely was efficient and easy to use, and it was the practical, default choice for a VBScript or Classic ASP application reaching a database.

It's worth being precise here, since Lesson 1 already flagged this exact naming trap: classic ADO is not ADO.NET, despite the almost identical name. Classic ADO is COM-based, built for VBScript and Classic ASP, and predates .NET entirely. ADO.NET, covered next, is a ground-up redesign for a completely different platform that happens to share a name with its predecessor. If an older document, or an older version of this course's own material, mentions "ADO" without the ".NET," it almost always means this classic, COM-based API, not the technology this course actually builds on.

Seeing the Difference: Classic ADO vs. ADO.NET

Lesson 1 compared Classic ASP and Razor Pages code side by side; the same comparison is worth making here for data access specifically. A Classic ASP page reaching a database through classic ADO's Recordset object, in VBScript, looked roughly like this:
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider=SQLOLEDB;Data Source=localhost;Initial Catalog=VirtualBookShelfDb;"

Set rs = Server.CreateObject("ADODB.Recordset")
rs.Open "SELECT CustomerID, FirstName, LastName FROM Customer", conn

Do While Not rs.EOF
    Response.Write rs("FirstName") & " " & rs("LastName") & "<br>"
    rs.MoveNext
Loop

rs.Close
conn.Close
The same query through ADO.NET, in C#, inside a Razor Pages code-behind file, looks like this:
using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    var command = new SqlCommand(
        "SELECT CustomerID, FirstName, LastName FROM Customer", connection);

    using (var reader = command.ExecuteReader())
    {
        while (reader.Read())
        {
            string fullName = $"{reader["FirstName"]} {reader["LastName"]}";
            Customers.Add(fullName);
        }
    }
}
The shape is strikingly similar, open a connection, run a command, loop through the results one row at a time, because that shape reflects what talking to a database actually requires, not a choice either technology happened to make. What changed between the two isn't the concept; it's the language, the platform, and, not incidentally, the `using` blocks that guarantee the connection and reader both close automatically, even if something goes wrong partway through, something VBScript's manual .Close calls never guaranteed.

What This Course Actually Uses: ADO.NET and Entity Framework Core

Lesson 3 explained why this course builds on SQL Server 2025: it's the database with the most direct, first-party integration into the rest of this course's stack. ADO.NET is the other half of that reasoning. It's a managed, .NET-native data access API, not an OLE DB consumer the way classic ADO is, built specifically for applications like the Razor Pages VirtualBookShelf will become. A connection object opens a link to SQL Server using a connection string, a command object carries the SQL text to run, and a data reader reads the results back row by row, the same CRUD operations Lesson 2 wrote directly against VirtualBookShelf's Customer table.

Entity Framework Core, introduced later in this course, sits on top of ADO.NET rather than replacing it: it lets C# code work with objects, a Customer or a Book class, rather than writing SQL text by hand, while ADO.NET still does the actual work of talking to SQL Server underneath. Learning ADO.NET first, before EF Core, means a later EF Core query is never a mystery: it's SQL and a data reader, generated automatically rather than typed out by hand.

Neither ADO.NET nor EF Core descends from MDAC's classic ADO or OLE DB lineage, even though the vocabulary, connections, commands, providers, carries over conceptually. That's the real throughline of this lesson: the concepts middleware was built to solve (opening a connection, sending a command, reading a result) are decades old and still apply, even though the specific technology this course uses to solve them is entirely new.

Once Entity Framework Core enters the picture later in this course, the same query from the code comparison above collapses even further. Rather than writing a SQL string and reading a SqlDataReader row by row, EF Core lets that same request be written as a LINQ query against a Customer class:
var customers = dbContext.Customers
    .Select(c => $"{c.FirstName} {c.LastName}")
    .ToList();
EF Core translates that LINQ expression into SQL and runs it through ADO.NET underneath, exactly the same connection-command-reader pattern shown above, just generated automatically rather than typed out by hand. Nothing about the underlying mechanics changes; only how much of it a developer has to write directly.

Data Access Across a Network

One more concept rounds out this lesson: data remoting, the general idea of storing data on a server separate from the application that uses it, reached across a network rather than read from a local disk. The remote server could be down the hall or on the other side of the world; from the application's point of view, the access pattern looks the same either way.

This is directly relevant to where VirtualBookShelf is headed. Lesson 3 described SQL Server's path from a developer's own machine to a production deployment on Azure, and once that deployment happens, VirtualBookShelf's Razor Pages application and its SQL Server database are no longer necessarily running on the same machine, or even in the same data center. ADO.NET's connection string is what makes that transition manageable: the same code that opens a connection to localhost during development opens a connection to an Azure-hosted SQL Server instance in production, with nothing in the application's own data access code needing to change.

That manageability isn't automatic, though; it depends on the application being written to treat the database's location as configuration rather than as something baked into the code itself. A connection string stored in appsettings.json, the way Lesson 1 introduced, is exactly what keeps that location swappable. An application that hardcoded localhost directly into its data access code would need a code change, and a new deployment, every time it moved to a different server; one that reads its connection string from configuration just needs that one setting updated.

A Few Common Questions

If ADO.NET isn't built on OLE DB, why does it still have "connections," "commands," and similar vocabulary? Because those concepts, open a connection, send a command, read a result, aren't specific to any one technology. They're the basic shape of what talking to a database requires, and ADO.NET's designers kept that shape because it's a sound one, even while rebuilding the implementation underneath it from scratch.

Does this course ever need ODBC or OLE DB directly? No. Both are covered here mainly so the vocabulary is recognizable, since they still show up in reporting tools, older applications, and Microsoft's own documentation. VirtualBookShelf's own Razor Pages code goes straight through ADO.NET and, later, Entity Framework Core.

Is it worth learning classic ADO at all today? Only as historical context, the same way this lesson covers it. A developer maintaining an existing Classic ASP application would still need it, but nothing in this course, or in a new ASP.NET Core project, calls for writing classic ADO code.

What actually happens if a connection to the database fails? ADO.NET's connection and command objects raise an exception when something goes wrong, a database that's offline, a bad connection string, a network interruption, rather than failing silently. Handling that exception properly is part of writing a reliable data access layer, a topic a later lesson in this course covers once ADO.NET itself has been introduced in full.

Why did the VBScript example above need to call rs.Close and conn.Close manually, but the C# example didn't? VBScript and classic ADO have no built-in equivalent of C#'s using block, so closing a connection or recordset was entirely the programmer's responsibility; forgetting to call .Close was a common source of leaked, unclosed database connections in Classic ASP applications. C#'s using statement guarantees the object inside it gets disposed, and its underlying connection closed, as soon as the block ends, whether the code inside ran successfully or threw an exception partway through. This is a small example of a larger pattern: a lot of what ADO.NET and the C# language add isn't new capability so much as removing an entire category of mistakes that classic ADO left up to the programmer to avoid.

Is OLE DB still used for anything in a modern .NET application? Rarely, and not in this course. Microsoft's own OLE DB driver for SQL Server still exists and is still updated, mainly for legacy applications and certain specialized tools that were never rewritten against ADO.NET, but a new ASP.NET Core project has no real reason to reach for it when ADO.NET and Entity Framework Core exist specifically for this platform.

If MDAC and WDAC are both retired, why does any of this history matter for a course being built today? Mainly so the vocabulary doesn't feel arbitrary. "Connection," "command," "provider," and "data source" all trace back to this lineage, and understanding where they came from makes it much easier to read older documentation, older job listings that still mention ADO, or an existing Classic ASP application a future employer might ask for help maintaining. The goal isn't to use MDAC; it's to recognize its fingerprints.

What's Ahead

This lesson traced the path from MDAC's retired low-level and high-level APIs, ODBC, OLE DB, and classic ADO, to the technologies this course actually builds on, ADO.NET and Entity Framework Core, and explained why the two aren't the same lineage despite ADO.NET's familiar-sounding name. The next lesson in this module turns to the language every one of these access technologies ultimately speaks to the database: SQL itself, what it is, and what it does.

SEMrush Software 4 SEMrush Banner 4