An Overview of ObjectSpaces



An Overview of ObjectSpaces

ObjectSpaces is probably one of the most revolutionary and exciting of the new techniques being introduced into .NET as far as data access is concerned. At the moment it is less than fully mature, but it already provides a useful framework for building applications that access real-world data.

The concept of ObjectSpaces is to allow you to access, manipulate, and update data in a relational database using standard .NET "objects," rather than having to write stored procedures or build SQL statements that specifically match the data you want to work with to the structure of the source table in the database. And, in the future, the technology will be extended to support access to information in other types of data stores, not just relational databases. However, in the current release, support extends only to SQL Server 2000 and SQL Server "Yukon."

The objects you use to store your data are ordinary .NET classes that can contain public and private methods and properties and expose one or more constructors. The ObjectSpaces technology then maps these objects to the tables and columns in the database and automatically wires them up so that you can read, manipulate, and update the data in the tables simply by calling methods within the ObjectSpaces interface.

For all this to work, ObjectSpaces exposes a series of classes that include the following:

  • A base class called ObjectSpace that performs the basic operations on the data and from which the class instances you require to manipulate the data can be created

  • A class named ObjectSources that contains the connection and transaction information to be used when accessing databases

  • A class named ObjectQuery, together with a language called OPath (broadly based on XPath), which is used to select objects from a results set

  • A class named ObjectReader that follows the same broad principles as other Reader classes and is used to fetch objects from the database and expose them as a stream

  • A class named ObjectSet that broadly mirrors the DataSet but is used to hold copies of objects, allowing them to be remoted or accessed in a disconnected environment

  • Two classes named ObjectList and ObjectHolder, which are used to manage delayed loading for objects and hence maximize performance

Basic ObjectSpaces Techniques

The basic principles for using ObjectSpaces are much the same as the existing .NET data access approaches. You create an ObjectSpace instance, specifying the mappings to the data and the ObjectSources connection and transaction information:

Dim oSpace As ObjectSpace
oSpace = New ObjectSpace(mappings-file, connection)

The mappings-file parameter can be the path to a file containing the mappings or a MappingSchema instance that defines the mappings. The connection parameter can be a standard database connection (Sql Connection or DbConnection) or an instance of an ObjectSources object that defines connection and transaction information.

Then you can create an ObjectReader instance from the ObjectSpace. Any filtering or selection operation you need to accomplish is specified in an ObjectQuery instance that indicates the object type and uses OPath as the query language. The following code retrieves objects of type Customer, where the region value is Europe:

Dim oQuery As New ObjectQuery(Customer, "region='Europe'")
Dim oReader As ObjectReader = oSpace.GetObjectReader(oQuery)

The Customer object instances can then be retrieved from the DataReader using the Read method. Each instance is cast (converted) to the correct Customer type as it is retrieved from the ObjectReader (see Listing 3.16).

Retrieving and Accessing a Customer Object
...
Dim oCust As Customer

While oReader.Read()

  ' retrieve object and cast to Customer type
  oCust = CType(oReader.Current, Customer)

  ' display or use values in Customer object
  lblResult.Text &= oCust.CustomerName

End While
oReader.Close

In future releases, ObjectSpace will also be able to return an Object Reader that uses paging, rather like the new ExecutePageReader method does for a standard DataReader. However, the exact details of how or when this will be implemented are still under discussion.

Using an ObjectReader

An ObjectReader provides stream-based access to objects, rather like a DataReader does to data rows when executing a normal SQL query. You can use it for data binding to Windows Forms and Web Forms controls. However, bear in mind that, like all Reader objects, it works in the connected scenario only, and you must be sure to close it when you have finished using it.

Filling and Manipulating an ObjectSet

To hold copies of objects for remoting, processing, and updating, you use an ObjectSet. The ObjectSet is basically a collection of your objects, but it also performs other tasks like tracking the original values when objects are updated and allowing the collection to be sorted.

An ObjectSet is created using the GetObjectSet method of the current ObjectSpace instance. The objects you require can be specified using an existing ObjectQuery instance:

Dim oSet As ObjectSet = oSpace.GetObjectSet(oQuery)

or by specifying the object type as a Type instance, and an OPath query as a String:

Dim oSet As ObjectSet = oSpace.GetObjectSet(object-type, query)

If you only want to retrieve a single instance of an object from the database, you can use the GetObject method of the ObjectSpace instead. It uses the same combination of parameters as the Get ObjectSet method but just returns a new instance of the object. However, GetObject will raise an error if the query results in more than one object being returned.

The ObjectSet also exposes methods that allow you to add objects to the collection or remove them from the collection. The Add method takes either a single object instance or a collection of object instances and adds them to the ObjectSet collection. To remove an object you use the MarkFor Deletion method. As the name suggests, the object remains in the ObjectSet but is marked as being deleted. When the changes to the ObjectSet are persisted to the database, the object is removed from the database (this is, of course, just the same as the way the standard ADO.NET DataSet works).

Saving the Changes to an ObjectSet

One of the features that make ObjectSpaces such an exciting technology is that the ObjectSet, like the ADO.NET DataSet, provides methods that push changes to the objects back into the database. The PersistChanges method of the ObjectSpace pushes the changes made in an ObjectSet back into the database, and the Resync method synchronizes the Object Set and the database. Both methods can be applied to a single object or to a collection of objects (or the complete ObjectSet contents).

Listing 3.17 shows a simple example of the ObjectSet in action. It collects a set of Customer objects from the database into an ObjectSet using the GetObjectSet method. Then, for each one, it updates the value of the Status field to Checked. Next, one of the Customer objects is marked for deletion, and a new one added, before the changes are persisted to the database.

Manipulating Customer Objects
' get ObjectSpace using mappings and connection
Dim oSpace As ObjectSpace = New ObjectSpace(mappings-file, connection)

' create ObjectSet from ObjectSpace using OPath query
Dim oSet As New ObjectSet _
    = oSpace.GetObjectSet(Customer, "State='WA'")

' iterate through objects in ObjectSet setting the Status field
For Each oCust As Customer In oSet
  oCust.Status = "Checked"
Next

' mark the Customer object at index 7 for deletion
oSet.MarkForDeletion(oSet(7))

' add a new Customer object
oSet.Add(New Customer("name", "address", "status"))

' persist the changes into the database
oSpace.PersistChanges(oSet)

Object Mappings

The ObjectSpaces technology seems like a natural progression from existing ADO.NET data management techniques, and indeed it is. The power comes from the mappings that are created between the objects and the columns and tables in the source database. These mappings are defined in XML and take advantage of a new approach that Microsoft has introduced to help blur the distinctions between different types of data and different types of data stores.

This new approach uses a three-part mapping system to map different types of data sources and data consumers. The details of the generic three-part mapping technology are described in Chapter 7, where they are applied to mapping XML to relational data. However, exactly the same approach is taken to map objects to relational data stores.

The process involves three mapping files. The relational database schema model describes the way data is stored in the relational database, and an object schema model describes how the objects themselves store and view the data they hold. The third file actually maps the elements in each of the other two to each other, to provide the "bridge" between the two disparate models.

Hierarchical Data

In this brief introduction we've concentrated on the way that ObjectSpaces can be used and what the technology offers to developers. We haven't spent time looking at the actual details of the objects it can manipulate because in essence they are just standard .NET classes. The important feature of ObjectSpaces is that it is designed from the ground up to make it much easier to work with hierarchical or related data in the form of objects.

What Are Hierarchical Objects?

As an example of hierarchical objects, a Customer object may be related to one or more Contact objects, each of which describes a person at the company defined by the Customer object. A property exposed on the Customer class—for example a Contacts property—can define this relationship. The Contacts property is simply a reference to a collection of Contact objects.

Looked at from the relational database point of view, however, the Customer object is the parent in a one-to-many relationship with child Contact objects. ObjectSpaces provides methods to work with related objects like these; for example (using the description of the objects in the previous paragraph), you can add a new Contact object as a child of an existing Customer object using the following code:

Dim oCust As Customer = oSpace.GetObject(Customer, "ID=2756")
oCust.Contacts.Add(New Contact("name", "position", "phone"))

More than that, ObjectSpaces is designed to handle graphs of interrelated objects. Even in cases where an XML-type hierarchy is not natural (because of multiple parents for a given object), you can easily represent and build the data model using ObjectSpaces. Likewise, it can handle many-to-many relationships that are not always expressed naturally as a hierarchy, for example, a Customers to Products relationship.

Loading Child Objects

One issue that springs to mind where hierarchical or related-object data access is concerned is performance. By default, a request that fetches objects matching an OPath query (such as "State=NV") will return only the objects that match the query, not any child or related objects. However, this means that you have to go back to the database each time you want to access a child object.

ObjectSpaces addresses this by using the concept of a span. Basically a span is a comma-delimited list of the objects you want to retrieve and is used in conjunction with the ObjectQuery class. So, if you specify Customer, Customer.Contact as the span for the operation, the related child objects are extracted and returned as well, without requiring a further query to the database.

A third option, called lazy loading, is also worth considering. In this case, where objects are stored in an ObjectHolder and ObjectList, a request for an object will be intercepted and all of the related target objects are automatically retrieved as well.