asp tutorials, asp.net tutorials, sample code, and Microsoft news from 15Seconds
Data Access  |   Troubleshooting  |   Security  |   Performance  |   ADSI  |   Upload  |   Email  |   Control Building  |   Component Building  |   Forms  |   XML  |   Web Services  |   ASP.NET  |   .NET Features  |   .NET 2.0  |   App Development  |   App Architecture  |   IIS  |   Wireless
 
Pioneering Active Server
 Power Search





Active News
15 Seconds Weekly Newsletter
• Complete Coverage
• Site Updates
• Upcoming Features

More Free Newsletters
Reference
News
Articles
Archive
Writers
Code Samples
Components
Tools
FAQ
Feedback
Books
Links
DL Archives
Community
Messageboard
List Servers
Mailing List
WebHosts
Consultants
Tech Jobs
15 Seconds
Home
Site Map
Press
Legal
Privacy Policy
internet.commerce














internet.com
IT
Developer
Internet News
Small Business
Personal Technology

Search internet.com
Advertise
Corporate Info
Newsletters
Tech Jobs
E-mail Offers

HardwareCentral
Compare products, prices, and stores at Hardware Central!

O/R Mapped Object Persistence Is the Boon
By Sebastian Ware and Mats Helander
Rating: 3.8 out of 5
Rate this article


  • email this article to a colleague
  • suggest an article

    Introduction


    Many developers argue that the true potential of object-oriented development isn't realized until a proper OODBMS is used for persistent storage of data. Of course this is not true, but it touches an interesting issue. There is a huge and largely unnecessary productivity and quality decrease as a result of developing in different paradigms simultaneously. One such common situation is mixing object-oriented development and relational databases.

    The work-around is to introduce a dedicated object-relational mapping layer that transparently maps objects to relational data. Developers using this approach experience a tremendous increase in productivity, escape database vendor lock-in, and simplify maintenance.

    On the down side, it requires substantial effort to create an object persistence layer manually. Fortunately there have emerged excellent code-generation tools that can do the time consuming work for you.

    This article will take a closer look at the benefits of O/R mapping.

    Design Goals of an Object Persistence Layer

    When you decide to implement an object persistence layer, you want to achieve three important goals: decreased coupling, increased cohesion, and increased abstraction.

    By routing all data access through an encapsulating layer you decrease the coupling between your application and the storage solution. In other words, your application becomes independent of the underlying data storage, usually a relational database.

    Low coupling is achieved by delivering transparent persistence services. This allows your application to become agnostic to the "physical" mechanisms of the storage solution, which can be changed in the future without affecting the functionality of the application.

    Typically the object persistence layer exposes a full set of access methods and properties that are mapped to the particular underlying storage solution. If you need to change the storage solution, just update the mapping code.

    Even seemingly small changes, such as database upgrades, can be a big problem with storage solution specific code scattered all over your application. With the specifics confined to the dedicated object persistence layer, upgrades become a much smoother operation.

    Cohesion is about doing one thing great rather than several things poorly. By focusing the persistence code to one separate layer, bugs and performance bottlenecks are easier to isolate and address. Also, the consequences of changes can be predicted with greater certainty.

    The object persistence layer should only CRUD (Create/Read/Update/Delete) data - and that is it! You really want to avoid business rules, communication interfaces, or GUI-elements originating from or performing in this part of your application. The reasons are the same that have been driving structured programming for the past two decades: reuse and maintainability.

    We all agree that a knife is more versatile than an egg slicer, and it never fails as long as we sharpen it now and then. Yet, when developing software we use feature mania to try to achieve the same thing. An object persistence layer that only CRUDs data, but does it great, is more versatile than one that has integrated business rules and other "great" features.

    Finally, but most important, the object persistence layer increases the level of abstraction by hiding the complexity of the underlying data model. You can use advanced object-oriented concepts such as inheritance, polymorphism, and complex relationships without having to worry about how it is implemented.

    Abstraction is one of the most important factors driving software quality and developer productivity. High-level programming languages and development environments such as Visual Basic, ASP and .NET have really empowered developers to create fantastic applications without explicit knowledge about the internals. This is really important because software developers create value by solving business problems, not by patching inefficiencies in the underlying technology.

    There is a performance penalty with abstraction, but the benefits of increased productivity and quality far outweigh the drawbacks.

    Benefits Of Using O/R-Mapping

    I assume that you use relational databases for much of your structured storage needs. Relational databases are very popular, thus, most persistent object layers will have to map to a relational database.

    Relational database technology is mature. All the popular databases, such as Oracle, SQL-Server, DB2 and Sybase, have plenty of tools allowing easy deployment, data maintenance, and analysis. These tools can be used as usual when persisting the objects to a relational database. Relational databases are effectively an open data storage solution allowing enterprise wide data reuse and straightforward integration with future applications.

    Another great advantage is the Structured Query Language that gives access to advanced filter operations that can be used by your object persistence layer. This saves a lot of effort, but care has to be taken if you want to retain portability. There are plenty of proprietary extensions to SQL, and the use of stored procedures also creates vendor lock-in. However, if you use vendor specific SQL and stored procedures carefully, porting can be achieved with reasonable effort.

    The overhead of the object persistence layer is usually a consequence of an advanced class model. Sometimes you really need maximum performance, and if you have mapped the objects to a relational database you always have the option to connect straight to the DBMS and execute specific stored procedures or SQL-statements. Basically, you would write 99.9% of your application using the object persistence layer and then add a few performance critical operations accessing the database directly.

    The Object Relational Impedance Mismatch

    Even though object-oriented and relational theory have fundamental differences, they can be mapped against each other perfectly. The challenge to overcome is the so-called object-relational impedance mismatch. This describes the difficulties that arise due to these fundamental differences.

    Both performance and functionality suffer if the impedance mismatch isn't addressed properly, and the key is in making the right trade-offs. There are a couple of papers that discuss these issues in more depth (see useful links section below).

    Creating Your Class Model

    Creating a class model differs in many ways from ordinary data modeling. Most notably is the higher level of abstraction. You will normally have many tables corresponding to one class, and information regarding relationships will be stored directly in the class model.

    For the sake of these examples we will be using the Component Definition Language (CDL), allowing us a very readable way of defining our class model.

    
    Component CommunityServer
    
    	Class User
    		{Name As String}
    		{HashedPassword As String}
    		{Roles() As Role, <<=Users}
    		{Posts() As Post, <<=Creator}
    		{Articles() As Article, <<=Creator}
    	End Class
    
    	Class Post
    		{Creator As User, <<=Posts}
    		{CreationDate As DateTime, default=Now()}
    		{Header As String}
    		{Contents As BigString}
    		{Checked As Boolean, default=False}
    		{ReplyTo As Object, <<=Replies}
    		{Replies() As Post, <<=ReplyTo}
    	End Class
    
    	Class Article
    		{Creator As User, <<=Posts}
    		{CreationDate As DateTime, default=Now()}
    		{Editor As Role, <<=Items}
    		{Header As String}
    		{Body As BigString}
    		{Checked As Boolean, default=False}
    		{Hidden As Boolean, default=False}
    		{Posts() As Post}
    		{Replies() As Post, <<=ReplyTo}
    	End Class
    
    	Class Role
    		{Name As String}
    		{Items() As Object}
    		{Users() As User, <<=Roles}
    	End Class
    
    End Component
    
    
    We have created a community server that contains the following classes: User, Post, Article and Role. These four classes correspond to 16 tables in the database. When coding your persistence layer manually you would now have to create the tables and map each column to the appropriate entity in the class model. O/R mapping tools do this for you.

    If you end up with a lot of classes mapped to single tables, you should seriously question whether you are actually creating the right level of abstraction. You should definitely redesign the class model. The purpose of the object persistence layer is not to become "the emperor's new clothes" to an ordinary relational data model.

    Note how we have defined a one-to-many inverse relationship between User.Articles() and Article.Creator in the class model above. This means that we can reference a user's articles by looping through the collection property User.Articles(), also we could reference back through the Creator property: User.Articles(n).Creator = User.

    The OO-way of referencing other objects is nice because of the high readability. Also, you will enjoy the added benefit of intellisense and type checking. This will save you from plenty of subtle typos. Another great thing is polymorphism. All derived subclasses of the class Article can be referenced through User.Articles().

    Creating relationships are the key to a usable class model. By choosing carefully when to implement them you are also creating a best practice for working with the objects. The goal is to generate a flow in the business methods that make them straightforward to program and easy to read.

    One important aspect when creating the class model is not to over-engineer it. In this case it might be questionable whether it is absolutely necessary to have a separate Role class. We have decided it is because we want the users to be self-organizing, creating new roles dynamically. This is an important design consideration. Basically you want to think two steps ahead, but only implement the simplest possible version. Keep it simple when you can!

    For the sake of scalability you might wish to put one or more classes in its own component. This way it can be compiled and deployed separately from the rest of the classes. This is really tricky to implement manually. In these cases it is advisable to use an O/R mapping tool. Make sure it supports cross component referencing. You'll notice that this is a great way to get started with component-based development.

    In the example above, the classes User and Role might be placed in a separate UserServer component. Thus, when adding a ShoppingServer you could easily share the same UserServer component. This way you will have the choice to omit the CommunityServer in future projects where it is not required. Without much effort you have created reusable components.

    Coding With Your Persistent Objects in VB

    Now we have reached the fun stuff! You have created your object persistence layer and underlying database, making up the complete logical data tier. It is time to write business methods and start reaping the benefits of the elegant class model. You could write the business methods in ASP, but we'll present the VB code for the sake of diversity.

    The example is implemented against an object persistence layer generated by O/R mapping tool Pragmatier Data Tier Builder. Let's start by showing a really simple example (hint, the class CComponent contains the GetObject method that is used both to fetch and create objects):

    
    ' Instantiate an object factory that does the o/r mapping.
    ' Initialise some variables and create an object with
    ' the GetUser method. The GetUser method with the second
    ' parameter (create if not found) set to true basically means:
    ' get me an object of the class User with a certain key,
    ' and if it doesn't exist create it for me and give me
    ' the created object.
    Dim ObjectFactory As New CommunityServer.CComponent
    Dim User As CommunityServer.User
    Dim EditorRole As CommunityServer.Role
    Set User = ObjectFactory.GetUser("seb@mail.com", True)
    Set EditorRole = ObjectFactory.GetRole("FirstEditor", True) 
    
    
    ' Assign some values to the object and persist the object.
    ' Finish off with the clean-up method that helps release cyclic 
    ' relationships that could fool COM not to release the memory.
    User.Name = "Sebastian Ware"
    User.AddToRoles EditorRole
    User.Persist
    ObjectFactory.CleanUp()
    
    
    The code is pretty easy to read even without any comments. A non-programmer could, with very little instructions, verify that we have created a new user named "Sebastian Ware" and assigned him the role "FirstEditor", which was also created.

    The point to note is how this code focuses entirely on business rules without even considering whether we have SQL-Server or Oracle running beneath. We don't even have to understand the underlying database schema; we concentrate on the more useful class model.

    Let's take a look at a real world example. Imagine that we want to check the write credentials of a user wanting to update an article. That code could look something like this:

    
    Public Function MayEdit(ByRef User As CommunityServer.User, _
    ByRef Article As CommunityServer.Article) As Boolean
    Dim Editable As Boolean
    Dim Role As CommunityServer.Role
    	Editable = False
    
    ' Check whether the user created the article.
    	If Article.Creator.Qualifier = User.Qualifier then
    		Editable = True
    	Else
    
    ' Check all the roles the user has to see if she has
    ' editor rights to this article.
    		For Each Role in User.Roles()
    			If Role.Qualifier = _
    Article.Editor.Qualifier then	
    Editable = True
    			End If
    		Next
    	EndIf
    	MayEdit = Editable
    End Function
    
    
    The code is highly readable and the project manager can easily verify that this was the intended behavior. By using good names the code itself becomes reasonable and accurate project documentation even without any knowledge of the database schema or SQL.

    If we decide to subclass the class User or Article defined in the class model, this method would still work fine courtesy of polymorphism. We can simply reuse basic components without any modification.

    Let's take a look at another example where we start by selecting a subset of objects by means of a filter operation. We want to hide all the articles that haven't been checked by an editor. For this purpose we filter by the property Article.Checked.

    
    Public Sub HideUnchecked()
    Dim ObjectFactory As New CommunityServer.CComponent
    Dim Article As CommunityServer.Article
    Set Article = ObjectFactory.GetArticle("")
    
    For Each Article in Article.FilterByChecked(False, "=")
    		Article.Hidden = True
    	Article.Persist
    Next
    
    ObjectFactory.CleanUp()
    End Sub
    
    
    The Articles.FilterBy[PropertyName] method returns a collection of objects matching the argument provided. If you also supply a collection of objects as a parameter, the filter operation will be performed within that collection. In the example above, the filter operation returns all objects of the class Article where Article.Checked = False. Then it sets Article.Hidden = True and saves the update.

    As we mentioned earlier you really want to use plenty of relationships in your class model so that you can "navigate" between objects. Using filter operations doesn't give you the same abstraction.

    Coding With Your Persistent Objects in ASP

    When you have created your business methods, you'll probably want to create some ASP-pages as an interface to your application. This is just as straightforward, making the pages very easy to maintain. The first example displays a list of users by looping through all the objects and printing the User.Name property:

    
    Users.asp
    <%
    
    Dim ObjectFactory, User
    Set ObjectFactory = Server.CreateObject( _ 
    "CommunityServer.CComponent")
    Set User = ObjectFactory.GetUser("")
    
    ' This is where we loop through all the user objects.
    ' Note that we have sorted the objects by name.
    For Each User In User.SortByName()
    	Response.Write "<a href=""user.asp?UserID=" & _
    		User.ID & """>" & User.Name & "</a><br>"
    Next
    ObjectFactory.CleanUp
    %>
    
    
    Another pertinent example is to display all the articles and posts written by a certain user. We assume that the UserID is supplied in the call, which would make the code look something like this:
    
    User.asp:
    <%
    Dim ObjectFactory, User, UserID, Post, Article
    UserID = Request("UserID")
    
    ' You can't fool us...
    If Len(UserID) < 1 Or Then
    	Response.Write "You must supply a user id!"
    	Response.End
    End If
    Set ObjectFactory = Server.CreateObject( _ 
    "CommunityServer.CComponent")
    
    ' Get the User object.
    Set User = ObjectFactory.GetUser(UserID)
    
    Response.Write "ID:" & User.ID & "<br>"
    Response.Write "Name:" & User.Name & "<br>"
    
    ' We use the relationship between class User and
    ' Article rather than a filter operation in order
    ' to find the articles written by the user.
    Response.Write "Articles by this user:<br>
    For Each Article In User.GetAllArticles
    	If Article.Checked And Not Article.Hidden Then
    Response.Write & _
    "<a href=""article.asp?aID=" & _
    		Article.ID & """>" & _
    Article.Header & "</a><br>"
    	End If
    Next	
    
    ' Dito to posts.
    Response.Write "<br>Posts by this user:<br>
    For Each Post In User.GetAllPosts
    	If Post.Checked Then
    Response.Write & _
    "<a href=""post.asp?pID=" & _
    		Post.ID & """>" & _
    Post.Header & "</a><br>"
    	End If
    Next	
    
    ObjectFactory.CleanUp
    %>
    
    
    The code is pretty straightforward and can easily be changed to get a new appearance. As long as the interfaces aren't changed, the GUI will work fine, even if the object persistence layer is updated to map to another database or if business methods are rewritten. Adding new properties, such as first name and last name to the class User, can easily be done, and by updating the mapping code of the property User.Name to concatenate the contents of these new properties, the application needs no further modification.

    Conclusion

    Creating a separate object persistence layer using object-relational mapping is a great way of improving developer productivity and application quality, and simplifying maintenance. Object-oriented features such as inheritance and polymorphism are great enablers of code reuse, if you choose to use it, and reusing the class model doesn't require much overhead. If you haven't explored the possibilities of object-relational mapping technology yet, today is a good day to start. You might never want to write another line of SQL again.

    Useful Links

    Link to the tool used:

    http://www.pragmatier.com

    Links to useful resources:

    http://www.objectarchitects.de/ObjectArchitects/orpatterns/index.htm
    http://www.ambysoft.com/mappingObjects.html
    http://www.objectmatter.com/vbsf/docs/maptool/ormapping.html

    About the Authors

    Sebastian Ware and Mats Helander founded and run the software development company Pragmatier. The company specialises in object-relational mapping tools -- the art of storing persistent objects in ordinary relational databases.

    The flagship product, Pragmatier Data Tier Builder, incorporates industry leading code generation technology allowing developers to maximize productivity while retaining flexibility. The tool supports COM/COM+ and .NET.

    Sebastian Ware can be reached at sebastian.ware@pragmatier.com

  • Rate This Article
    Not HelpfulMost Helpful
    1 2 3 4 5
    Supporting Products/Tools
    Proposion N2N
    Proposion N2N connects Microsoft .NET applications to Lotus Notes and Lotus Domino databases. This ADO.NET managed data provider allows you to perform blindingly fast queries and updates of Notes data from ASP.NET pages, .NET web services, Windows, or Mobile applications. An innovative SQL-like query language leverages the unique features of Notes and makes collaborative software accessible to relational database programmers.
    [Top]
    Other Articles
    Sep 15, 2005 - Building an Image Keyword System
    Unlike text-based file formats image files aren't made up of words, which makes searching for an image file by keyword difficult. Instead of being able to simply open the file to see what it contains, we're stuck looking at the text around it and other metadata to determine the image's meaning. In this article, Ziran Sun shows you how to build a simple database-based image keyword system that allows you to associate keywords with images and use these keywords to make finding images easier.
    [Read This Article]  [Top]
    Apr 7, 2005 - A Step-by-Step Guide To Using MySQL with ASP.NET - Part 2
    In the second part of of his article on using MySQL with ASP.NET, Ziran Sun covers how to add a new MySQL user to the database server, assign the user the appropriate permissions, connect to the database, and build a simple ASP.NET page to perform a query.
    [Read This Article]  [Top]
    Feb 10, 2005 - A Step-by-Step Guide To Using MySQL with ASP.NET - Part 1
    Back in the days of classic ASP, if you were building a database-driven web site, your choice was either to invest a lot of money to get a copy of Microsoft SQL Server (or some other enterprise-ready database) or invest a lot of time finding a way to deal with the performance and scalability limitations of Microsoft Access. Luckily these days there's another viable alternative: MySQL.
    [Read This Article]  [Top]
    Jan 27, 2005 - Moving a Database from SQL Server 7.0 to SQL Server 2000
    Moving or copying a SQL Server database from one machine to another requires a lot of preparation in order to ensure a smooth transfer. In this article, Dina Fleet Berry examines the different methods and highlights the different issues associated with each of them.
    [Read This Article]  [Top]
    Jan 6, 2005 - Debugging a SQL Stored Procedure from inside SQL Server 2000 Query Analyzer
    There are many times when using SQL Server 2000 Query Analyzer to debug SQL statements is a better choice than debugging in Visual Studio .NET. In this article, Dina Fleet Berry explains why and walks you through the debugging process step-by step.
    [Read This Article]  [Top]
    Nov 24, 2004 - Persisting .NET Objects to SQL Server Using SQLXML and Serialization
    As a follow up to his article on retrieving objects from SQL Server using SQLXML and serialization, Gianluca Nuzzo discusses saving objects back to SQL Server using a schema definition file and updategrams.
    [Read This Article]  [Top]
    Sep 14, 2004 - Transaction Processing in ADO.NET 2.0
    One area that stands out when comparing ADO.NET 1.x to ADO.NET 2.0 is transaction processing. Bill Ryan shows just how easy transaction processing has become with the TransactionScope object in ADO.NET 2.0.
    [Read This Article]  [Top]
    Sep 8, 2004 - Custom Object Data Binding with .NET
    Developers often use brute force coding to marshal data between the GUI and application objects. In this article, Luther Stanton explains how to use .NET's out-of-the box data-binding functionality to make this job much easier.
    [Read This Article]  [Top]
    Sep 2, 2004 - Queue MSMQ Messages from SQL Server
    Learn how to create a console application to queue a message in Microsoft Message Queuing (MSMQ) and then use an extended stored procedure to call the console application from a SQL Server trigger.
    [Read This Article]  [Top]
    Aug 30, 2004 - Tuning Up ADO.NET Connection Pooling in ASP.NET Applications
    Connection pooling increases the performance of Web applications by reusing active database connections instead of creating a new connection with every request. This article shows how to monitor the connection pool, diagnose a potential problem, and apply the appropriate fix.
    [Read This Article]  [Top]
    Mailing List
    Want to receive email when the next article is published? Just Click Here to sign up.

    Support the Active Server Industry