Part 1: Developing an object oriented database in less than 140 lines of C#
Takeaway: One of the best benefits of the .NET Framework over lower level programming is that it enables developers to create very complex, custom solutions, without writing low-level code. Zach Smith explores how you could take advantage of the built-in .NET Framework functionality to develop a simple object oriented database in less than 140 lines of code.
This article is also available as a TechRepublic download, which includes all of the Visual Studio project files.
Our goal here is to create a simple object oriented database (OODB) in C# with less than 140 lines of code. Obviously this won't be the easiest project; however C# and the .NET Framework provide many built in functions that we can take advantage of to help us reach our goal. The requirements for the database are as follows:
- Must be able to save an object's state without any extra information being applied to the object, and without telling the database exactly how to save the object.
- Must be able to get an object from the data store and provide a full business object to the user, without the user mapping fields of the database into the object.
- Must be able to be queried using first class C# representations. For instance, a query should not have to be contained within double quotes such as SQL queries require, and should be type-safe.
- Must have an ad-hoc query mechanism. This mechanism will require double quotes around the query, and will not be type-safe.
- Must be able to delete objects from the database, with an option to "deep delete".
- Must be able to update objects in the database.
Obviously the requirements for an enterprise level OODB would be much stricter, and would have other features such as indexes, triggers, and concurrency. What this article demonstrates is the effective use of built-in .NET Framework functionality to create a simple OODB. We are not trying to take the OODB world by storm here, just trying to show what can be accomplished with a little creativity.
How the data will be stored
We have many options for storing our objects in our database. One of the most obvious is to serialize them using binary serialization. This allows us to take advantage of the built-in binary serialization (System.Runtime.Serialization namespace), and would make saving/getting an object trivial. However, if we take that path we are left with the task of writing a search routine to search the data. We also would have to come up with a way to segregate the objects and object types from one another. Due to these limitations and the goals of the project binary serialization isn't the best option for us.
The second choice is XML serialization. XML serialization provides many of the advantages of binary serialization, with a couple interesting extras. One of the best advantages XML serialization has over binary serialization is that the objects are stored in XML. This means that the searching of those objects and the segregation of object/object types is, for the most part, already taken care of. For these reasons XML serialization is what we will use in this project, which takes care of Requirements #1 and #2, and enables us to easily accomplish the other requirements.
One thing to know about XML serialization is that it is only meant to serialize/deserialize one parent object at a time. This means that we will need to come up with a XML layout to store multiple parent objects in the same file. This issue is solved by using StringReader and StringWriter objects as the stream instead of a file. This will allow us to serialize an object into a string. Once we have the serialized object in a string we will use an XmlDocument object to add the object's data to the database's XML file. For more information on XML serialization check out my XML Serialization article.
A simple database with two Customer objects in it would look like Listing A.
Listing A
<Database><XmlDB.Order>
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Identity>76a0558b-a8c7-42e3-8f1d-c56319365787</Identity>
<CustomerIdentity>6f5e9a2b-b68f-4b6d-9298-fbe5f135dd25</CustomerIdentity>
<DatePlaced>2006-11-21T07:12:16.3176493-05:00</DatePlaced>
</Order>
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Identity>16d8f0b8-46c6-47c3-ac6b-a0b0e0852970</Identity>
<CustomerIdentity>61cf2db4-0071-4380-83df-65a102d82ff2</CustomerIdentity>
<DatePlaced>2006-11-21T07:12:26.0533326-05:00</DatePlaced>
</Order>
</XmlDB.Order>
<XmlDB.Customer>
<Customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Identity>6f5e9a2b-b68f-4b6d-9298-fbe5f135dd25</Identity>
<LastName>Cunningham</LastName>
<FirstName>Marty</FirstName>
</Customer>
<Customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Identity>61cf2db4-0071-4380-83df-65a102d82ff2</Identity>
<LastName>Smith</LastName>
<FirstName>Zach</FirstName>
</Customer>
</XmlDB.Customer>
</Database>
As you can see, this format allows us to quickly parse through the XML and find the different groups of object types.
Now that we have decided on XML serialization to store the objects, we need to find a way to hold that data in memory to allow the user to interact with it. The obvious choice for this is the XmlDocument class, as it provides simple searching and updating capabilities for XML files.
Searching the data store
The project goals require two types of searching, and these must be addressed using different methods in the database. The first goal is to have queries ran as first-class C# statements. This means the queries must be type-safe, provide IntelliSense, and be compiled into IL code. An obvious choice for this is to use Predicate methods as the query mechanism. This allows you to take advantage of built-in C# functionality and keeps you from having to write a query mechanism from scratch.
Predicate methods work on real objects, not serialized data (Requirement #3). Due to this, we must first deserialize the objects which are being queried before the Predicate methods are run on them. This is the main downside to this method of query execution, and is the reason why ad-hoc queries are a goal of the project. After the objects to be searched are deserialized, we then run a predicate method on them. The following is an example query which searches for the first Customer object that has a FirstName of "Zach":
Customer customer = Customer.GetSingle<Customer>(delegate(Customer search)
{
return search.FirstName == "Zach";
});
As you can see, there is no direct reference to the OODB in this query. This is because the Customer object inherits from the object XmlDBBase, which provides searching, saving, and delete functionality.
The other query mechanism required in the goals of the project is ad-hoc queries (Requirement #4). These queries will not be type-safe, and should be faster than the type-safe queries. The easiest way to accomplish this is through the use of XPath. With the XPath solution we will query the XML database file with an XPath expression, receive a set of object nodes as a result, and then deserialize the object nodes into real objects. This provides the advantage of not having to deserialize all of the objects that are being searched on, and is much faster than the Predicate method approach. In testing, it took the Predicate method approach roughly one second to search over 10,000 objects. The XPath query, searching the same objects and returning the same result, only took one hundredthsecond.
Listing B shows an ad-hoc query which finds the first Customer object with a FirstName of "Zach."
Listing B
Customer customer =Customer.GetSingle<Customer>("FirstName = 'Zach'");
This qeury would return the exact same result as the Predicate query shown earlier if they were both ran on the same datastore.
Deleting and updating objects
With all of our data held in XML and accessible via the XmlDocument class, it is relatively simple to update and delete data. For deleting data (Requirement #5) we will use XPath to find the node which holds the group of objects which have the same type as the object we're deleting. After we find the group's node, we use the XmlNode.RemoveChild method, passing the current object's Node property as a parameter. This removes the object's node from the XML document that is in memory. To make the change final, we simply write the XML document back out to disk using the XmlDocument.Save method.
A sample object deletion is shown in Listing C.
Listing C
Customer customer = Customer.GetSingle<Customer>(delegate(Customer search)
{
return search.FirstName == "Billy";
});
if (customer != null)
customer.Delete();
Requirement #5 also requires that we provide a mechanism to do a "deep delete" of an object. What is meant by this is that when deleting an object that contains children, we must provide a way to delete the parent object and the child objects without the user having to do this manually. To implement this we use reflection and loop through the properties of the parent object, find serialized child objects and delete them.
Updating objects (Requirement #6) is a simple procedure of first deleting the current object stored in the database, and then inserting the updated object. The method of deletion is the same as described above. The updated object is then saved the same way a new object would be saved. This methodology does, however bring up an important issue with our database that would need to be addressed if you were to make a serious attempt at creating an enterprise level OODB. The issue here is that there are no checks being done to make sure that the object being deleted is not needed by another object in the system.
A sample update is shown in Listing D.
Listing D
Customer customer =Customer.GetSingle<Customer>(
delegate(Customer search)
{
return search.FirstName == "Brad";
});
customer.LastName = "Cunningham";
customer.Save();
Requirements for objects stored in the database
One of our requirements for this project states that the objects must not tell the database how they should be stored. There are, of course, some requirements that an object must meet to be stored in the database. These requirements are enforced via an interface called "IXmlDBSerializable." This interface requires that any object to be stored in the database must have the following properties:
System.Guid Identity { get; set; }System.Xml.XmlNode Node { get; set; }
And also the following method:
void Save(bool persistData);The Identity property is required so that we can identify each object uniquely within the database. The Node property simply holds the XML Node object in which the object is held in the database, if the object is already in the database. The Save method is required so that the object can be saved to the database.
For Part 2 in the series
In part two of this series I will show the code behind these concepts and explain how it is implemented in the sample project. The individual concepts are simple, however matching them up into a working codeset can be somewhat confusing. The code I will explain in the next article is included in the sample project which is included in the download version of this article.
Print/View all Posts Comments on this article
SponsoredWhite Papers, Webcasts, and Downloads
- The OSI Model: Understanding the Seven Layers of Computer Networks Global Knowledge
- Troubleshooting SQL 2005: Opening the Database Administrator's Toolbox Global Knowledge
- What Is Micromanagement? And What You Can Do To Avoid It. Global Knowledge
Article Categories
- Security
- Security Solutions, IT Locksmith
- Networking and Communications
- E-mail Administration NetNote, Cisco Routers and Switches
- CIO and IT Management
- Project Management, CIO Issues, Strategies that Scale
- Desktops, Laptops & OS
- Windows 2000 Professional, Microsoft Word, Microsoft Excel, Microsoft Access, Windows XP,
- Data Management
- Oracle, SQL Server
- Servers
- Windows NT, Linux NetNote, Windows Server 2003
- Career Development
- Geek Trivia
- Software/Web Development
- Web Development Zone, Visual Basic, .NET
