Click here to Skip to main content
15,867,765 members
Articles / Programming Languages / SQL
Article

RAPPTOR.Persistence - Transparent object persistence the easy way

Rate me:
Please Sign up or sign in to vote.
4.67/5 (15 votes)
31 Aug 200510 min read 69.3K   46   19
An example on how to use the object persistence framework.

Introduction

In application development - as every developer knows - there is always a point in time where he has to deal with object-persistence. So if you don't want to write SQL-code or stuff like that all the time, you need a general solution - an object persistence framework. Such frameworks have very different approach and are often specialized for different tasks and environments.

I'm a contributor to an Open source-project that implements a web-application-server called RAPPTOR[^]. There, we had to deal with that nasty persistence-thing. But when we took a look at the market, there was no solution that satisfied all our needs.

The needs in short

  • Open source-license (LGPL to enable commercial projects based on our work)
  • Automatic schema-evaluation (prevents from writing SQL-code or things like that)
  • Runtime schema-alteration (we want runtime-loadable plugins empower to establish their own needed data structures)
  • Database-independence (we want to be able to host our server in as many environments as possible)
  • Mono-compatible

There are many powerful frameworks around, but we couldn't use any of them without any restrictions and so we decided to realize it ourselves. Because our web-application-server is called RAPPTOR, we decided to name our persistence framework RAPPTOR.Persistence. It's completely independent from the application-server, so everybody is invited to use it. In this article, I want to give an overview on how to use that thing.

How it works

The general philosophy of our framework is that the storage-schema follows our object-model and not vice versa. So, it would not be possible to set an object model on top of an existing SQL-schema. We first want to write our code and then the framework will do the data-storage stuff for us.

I will now explain in short the general structure of the framework. It is divided into three layers:

  • the data-model layer
  • the general database layer
  • the relational database layer

The data-model layer is a very abstract thing that enables us to implement different drivers to deal with data from our code in different ways. Currently, there is only one implementation called DataModelREF that reflects an object-model to retrieve the schema and uses specific member declaration helper-classes to read and write the data of our object-model. That data-model (that's the way we call such drivers) is the one I will introduce in a later example. So it is not that schema information must come from a class hierarchy - e.g. an XML-schema with associated data could be persisted by a suitable model-driver, too.

The general database layer is used by the data-model drivers to access stored data in general. It is designed in a way that enables us to write a driver for an object-oriented database system. General object-management and caching are realized here.

The relational database layer sits on top of the general database layer and specializes it towards relational databases. The design-goal was to be able to write drivers for relational databases in a very easy way. Currently there are three drivers supporting MySQL 4.1, PostgreSQL and MSSQL.

The whole system is implemented by using the new .NET concepts from the 2.0 Framework. So you need either mono SVN[^] or the .NET 2.0 (beta) Framework.

Now, I will show you a small example. With the help of an easy object I will show you what happens in the database. After that I will change the structure a little bit. Finally, in the third part we will enhance complexity by expanding the data model using inheritance. By using such a persistence layer, life becomes much easier for developers.

The beginning

Before we can work on the persistence layer and build our object model we have to prepare the database. We need PostgreSQL in our example, the actual version is 8.0. In the PostgreSQL admin tool or the phpPgAdmin I'll generate an empty database and call it "CarData". Consider that you have all the needed privileges to that database, especially CREATE, DROP and ALTER rights on tables. Preparation on the database side is finished with that.

The easiest way to demonstrate the example is to take a console-project and add references to "RAPPTOR.Persistence.dll" and "RAPPTOR.Persistence.PostgreSQLDriver.dll".

Define the persistent class

We have to take a nice example. So we have to store something that everybody knows: cars. Objects from this class have certain properties, e.g. manufacturer, a color, max speed and the number of gears.

In our project we add a new class "cars.cs". In the using directive we add:

C#
using Rapptor.Stage0.DataModel.REF;

In order to make the class Car persistent, we have to inherit from the class ManagedObject. Furthermore our class is complete with maintenance functionally and becomes an object ID (ObjID) - in RAPPTOR this is a global unique identifier (GUID).

If you want to store members in the database you define them by special data types. These generic classes are defined in the DataModel.REF namespace and are inherited from the MemberManager class. You can use the following data types:

EV<T>Embedded value; simple value type
RO<T>Referenced object; reference of a complex object
EO<T>Embedded object; embedded complex object
COL<T>Collection; list of embedded complex objects

As we use .NET generics we will work with their type secure members. We have to use the methods Value and Object to read and write data (we will see that later in an example). Complex objects are the ones that are inherited from the class ManagedObject.

We define the following members: the Manufacturer and Color as string, the MaxSpeed as float and the number of Gears as byte. In the code it looks like this:

C#
public class Car : ManagedObject
{
  public EV<string> Manufacturer;
  public EV<string> Color;
  public EV<float> MaxSpeed;
  public EV<byte> Gears;
}

The main program

In our main program we make an instance of the class Car. We have to use the following namespaces:

C#
using Rapptor.Stage0.DataModel;
using Rapptor.Stage0.DBDriver.PostgreSQL;

First, we define a handle to the database. In the static GlobalDataModel we join the driver with the class member DB. Then we make an instance of DBDriverPostgreSQL and call the Open method. The connection string is the parameter; use it like an ADO.NET string:

serverinstance of PostgreSQL server
databasedatabase name ("CarData")
uidusername, who has rights on the database
pwdpassword of the user

Finally the PostgreSQL driver object has to be set on the static member DB of the class GlobalDataModel.

C#
DBDriverPostgreSQL db;
db = new DBDriverPostgreSQL();
db.Open(
  "server=yourserver;database=CarData;uid=testuser;pwd=");
GlobalDataModel.DB = db;

Here comes our car. We create a green Porsche which is stored in the variable car:

C#
Car car = new Car();
car.Manufacturer.Value = "Porsche";
car.Color.Value = "green";
car.MaxSpeed.Value = 250;
car.Gears.Value = 5;

You have to use the property Value, it is mandatory (handled in DataModelREF). The reward is a type secure programming. Now you can run the program and - nothing happens visibly. The local object car in the memory is forgotten when the program ends. Look at the next chapter and you will see some interesting things in the background.

Behind the scene - a look at the database

The tables in the database "CarData" are automatically generated by the framework. At the moment the PostgreSQL driver object is associated with the GlobalDataModel. The database structures are compared and if the database is empty, the system creates all the necessary tables. Every persisted class has one table (look at "dn_car"). The columns represent the members of the class. Every object is also stored in the table "object" which saves further information, e.g. the column "TypeID" saves the class membership. Finally the "globalvalues" table holds the entire data model structure as a serialized SOAP stream in order to compare the structures faster.

Take a look at the database:

Image 1

All columns in the table "dn_car" represents the class Car. What I declared before the member "OID" is the system administrated unique object ID. So every object is identified clearly:

Image 2

If the green Porsche is instantiated, the object is stored in a memory cache managed by the framework. Later the object is stored in the database, managed by the GarbageCollector. This happens when there is no reference to the object, like when the program terminates. In the table "dn_car" you will see:

Image 3

Even the table "object" has an entry:

Image 4

The next step: changing the structure

I said that cars have a model description. Now we can expand the class structure and insert a string member Model, as you can see in the next code block:

C#
public class Car : ManagedObject
{
  public EV<string> Manufacturer;
  public EV<string> Model;
  public EV<string> Color;
  public EV<float> MaxSpeed;
  public EV<byte> Gears;
}

In the main program where we create the new car we will set the model to "911" and make it red.

C#
Car car = new Car();
car.Manufacturer.Value = "Porsche";
car.Model.Value = "911";
car.Color.Value = "red";
car.MaxSpeed.Value = 260;
car.Gears.Value = 5;

We know that we have to look directly into the database after the program starts. The table "dn_car" has changed; a new column "model" has been inserted by the framework. This happens by comparison of the data structures:

Image 5

What happens to our values in the table? The green Porsche still exists; the red one has been added. Only the red Porsche has a model description, formerly the member wasn’t known.

As you can see, changing the data structure lately has no problem. You don't have to care about that, the database structures are updated automatically. The algorithm will do that by the principle "keep the values of the members that still have the same name and data-type".

Look at the table "dn_car":

Image 6

Inheritance and complex structures

Now we will enlarge the car model.

Referenced objects

In the model, we save the car driver. Every car refers to a driver. Add the class Driver as shown:

C#
public class Driver : ManagedObject
{
  public EV<string> Name;
  public EV<DateTime> Birthday;
}

Now we add a reference to the Car class:

C#
public class Car : ManagedObject
{
  ...
  public RO<Driver> CarDriver;
}

Inheritance

We also want to save racing cars with a notable engine in the model. We inherit the class RacingCar from Car because a racing car has all the properties of a car. As Car inherits from ManagedObject, RacingCar is also a persistable class.

Embedded objects

We want to embed an engine object in our racing car. So define the class Engine as follows and use the class RacingCar as declared by inheritance:

C#
public class Engine : ManagedObject
{
  public EV<float> Cylinder;
  public EV<float> HorsePower;
}

public class RacingCar : Car
{
  public EO<Engine> CarEngine;
}

Collections

You know, racing cars are built using advertisement-money. All the sponsors of the car will be managed by a collection:

C#
public class Sponsor : ManagedObject
{
  public EV<string> Name;
  public EV<double> AdvertisementMoney;
}

Now complete the class RacingCar and give it a chief designer:

C#
public class RacingCar : Car
{
  public COL<Sponsor> Sponsors;
  public EO<Engine> CarEngine;
  public EV<string> ChiefDesigner;
}

The object model shows the structure, the code block demonstrates how to define the objects:

Image 7

C#
Driver cd = new Driver();
cd.Name.Value = "Michael Schumacher";
cd.Birthday.Value = System.DateTime.Parse("01/03/1969");

Engine engine = new Engine();
engine.Cylinder.Value = 10;
engine.HorsePower.Value = 900;

Sponsor s1 = new Sponsor();
s1.Name.Value = "Red Flash";
s1.AdvertisingMoney.Value = 1000000;

Sponsor s2 = new Sponsor();
s2.Name.Value = "MoneyBank";
s2.AdvertisingMoney.Value = 5000000;

RacingCar rc = new RacingCar();
rc.Manufacturer.Value = "Ferrari";
rc.Model.Value = "F2005";
rc.Color.Value = "red";
rc.MaxSpeed.Value = 380;
rc.Gears.Value = 6;
rc.CarDriver.Object = cd;
rc.ChiefDesigner.Value = "Rory Byrne";
rc.CarEngine.Object = engine;
rc.Sponsors.Add(s1);
rc.Sponsors.Add(s2);

The database-schema that establishes while the program runs, represents our object-model. Every embedded and referenced object is persisted with its actual values.

Image 8

The RacingCar-object rc is now stored in the tables "dn_car" and "dn_racingcar" (look at the OID). To store objects using inheritance, every inheritance-layer of an object (a class) is stored in a table. To collect all the members of a RacingCar object, the tables "dn_car" and "dn_racingcar" are connected 1:1 by the OID.

Every object is represented in the "object"-table. Here the two sponsors and the engine are very interesting, because all of them are referencing to their parent object by the column "ParentID". The column "MemberInParent" additionally holds the name of the embedding member of the parent object. The multiple entry "Sponsors" indicates a collection as the holding member.

Image 9

What's next?

In the next tutorial I will explain, how to load stored objects from the database. This tutorial only shows you the general function and the central idea of the framework.

At the moment RAPPTOR.Persistence is in its development phase and uses - as it is very young - very new approaches from the .NET 2.0 Framework - especially generics. To use it you need to have .NET 2.0 (at the moment a beta 2) or mono 1.1.8. The persistence layer is in alpha-phase and is growing fast. For example there will be many more service-functions in the DataModelREF-classes, a query language and so on. We cannot guarantee that it will work without any problems. It has a huge optimization potential. If you are interested in helping us to extend, polish or test the framework please contact us[^] or Novell Forge[^]. Every contribution is welcome.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Germany Germany
Ingo lives in Berlin (Germany) and has a long experience in handling relational databases. He also programs in .net and is mainly responsible for software design and project managment. At the moment he contributes to a open source web application-server.

Comments and Discussions

 
QuestionIs the project still in development? Pin
tp_gmfoto14-Jun-07 22:28
tp_gmfoto14-Jun-07 22:28 
QuestionTransactions? Pin
Sean Winstead1-Sep-05 6:29
Sean Winstead1-Sep-05 6:29 
AnswerRe: Transactions? Pin
Ingo Tippold2-Sep-05 2:46
Ingo Tippold2-Sep-05 2:46 
QuestionHow do I...? Pin
Sean Winstead1-Sep-05 3:01
Sean Winstead1-Sep-05 3:01 
AnswerRe: How do I...? Pin
Ingo Tippold1-Sep-05 6:22
Ingo Tippold1-Sep-05 6:22 
GeneralRe: How do I...? Pin
Sean Winstead1-Sep-05 6:27
Sean Winstead1-Sep-05 6:27 
QuestionHave you seen DB4O? Pin
Giles1-Sep-05 0:49
Giles1-Sep-05 0:49 
AnswerRe: Have you seen DB4O? Pin
TK_One1-Sep-05 2:06
TK_One1-Sep-05 2:06 
GeneralRe: Have you seen DB4O? Pin
Carl Rosenberger1-Sep-05 6:36
Carl Rosenberger1-Sep-05 6:36 
General.net 2.0 Pin
jim bodine31-Aug-05 10:17
jim bodine31-Aug-05 10:17 
GeneralRe: .net 2.0 Pin
Ingo Tippold31-Aug-05 23:32
Ingo Tippold31-Aug-05 23:32 
GeneralProblem using MySQL Pin
apagnier2330-Aug-05 3:29
apagnier2330-Aug-05 3:29 
GeneralRe: Problem using MySQL Pin
TK_One30-Aug-05 7:19
TK_One30-Aug-05 7:19 
GeneralRe: Problem using MySQL Pin
Ingo Tippold31-Aug-05 10:11
Ingo Tippold31-Aug-05 10:11 
Questionwhen will you release full source code? Pin
Huisheng Chen27-Aug-05 3:30
Huisheng Chen27-Aug-05 3:30 
AnswerRe: when will you release full source code? Pin
Ingo Tippold28-Aug-05 0:19
Ingo Tippold28-Aug-05 0:19 
GeneralThanks for the intro, worried about performance... Pin
Marc Brooks26-Aug-05 6:10
Marc Brooks26-Aug-05 6:10 
I'm more than a little concerned by the use of the central object table in this design. That thing is going to get VERY large and will be pivotal in every query.

I'm not sure what you are using it for, but if it is to track the class-type information, you might consider using a [row-number,class-id] pair for the OID instead. Then you can automatically manage the object class-type management to allow knowing the class-id and thus what object to create in the factory. We used a similar approach to the Raptor thing in a C++ (and later C#) persistence framework. In our case, the OID was a decimal value with four digits of precision after the decimal point... thus if a "car" has class-id == 2, then the first "car" created got the OID of 1.0002. Alternately, you can just make the OIDs a segmented key with a long for the row number and a short for the class-id.
AnswerRe: Thanks for the intro, worried about performance... Pin
Ingo Tippold28-Aug-05 0:45
Ingo Tippold28-Aug-05 0:45 
GeneralRe: Thanks for the intro, worried about performance... Pin
Marc Brooks28-Aug-05 4:37
Marc Brooks28-Aug-05 4:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.