Click here to Skip to main content
Click here to Skip to main content

OPF.Net : An object persistent framework for .NET

, , 3 Nov 2003
Rate this:
Please Sign up or sign in to vote.
Developing an object-oriented application based on an SQL storage using OPF.Net.

Introduction

The Object Persistence Framework for .Net (OPF.Net) is a complete set of classes that implement an object-relational mapping strategy for object oriented access to traditional relational database management systems and other types of persistent storage types such as XML files. OPF.Net has been designed and implemented for practical use in small to medium size projects and is currently being successfully used in several projects.

Background

The basic idea for OPF.Net and some important concepts behind OPF.Net come from "The Design of a Robust Persistence Layer” written by Scott Ambler (http://www.ambysoft.com/). OPF.Net has been written to bridge the traditional gap between object oriented programming and relational data storage. While Object-Databases have made a big progress, relational database management systems are still unrivalled in terms of performance and represent the de facto standard for most new software development projects. Accessing a relational database from an object oriented application can be implemented in two basic fashions:

  • use a persistence mechanism that maps the applications business objects to the database tables and vice versa
  • directly access and manipulate database tables and records using data access layers and components provided by most modern RAD programming environments

The first approach generally leads to well designed, real object oriented applications with a clear separation between user interface and business logic implemented through reusable business objects.

The second approach generally leads to poorly designed applications breaking the object oriented programming style with no clear separation of user interface, business logic and data storage and no encapsulation of business logic in business objects.

OPF.Net implements a simple and robust mechanism for building applications using persistent business objects by completely encapsulating database access and thus helping to build well designed software.

The Object Persistent Framework

OPF.Net distinguishes between domain classes and data manager classes.

Domain classes are the classes, also called business objects, that actually represent the data to be read, manipulated and written to the persistent storage. Domain classes do not know how to read and write to the storage, they contain no storage specific commands (e.g. SQL) but instead offer methods such as Load, Save and Delete to load, save and delete themselves from the underlying physical storage. The user interface of an application and all its business logic is implemented using the domain classes; application developer do not need to know what kind of underlying storage is used.

Data manager classes perform the actual loading, saving and deleting of domain classes from the persistence layer. The data manager classes are the only classes that know about the underlying storage mechanism and hold storage commands (SQL for DBMS storages) to perform the requested persistence operations. Therefore, to port an application from one DBMS to another, only the data manager classes have to be modified. The domain classes along with the business logic and the user interface do not need to be changed at all.

Associating a domain class with its corresponding data manager class is done through the ObjectBroker. The ObjectBroker is the only part of the framework knowing domain and data manager classes. All persistence methods called for a domain class are passed on to the ObjectBroker along with the domain class itself. The ObjectBroker then searches the associated data manager class and instructs it to perform the requested operation passing the domain class as a parameter. The data manager class execute the requested operation and eventually populates the domain class’ properties on a load or uses the domain class’ properties to fill command parameters on save and delete commands.

Each property of a business object generally maps to a single field in the storage. By loading a business object all its public properties get loaded from the underlying storage. Protected and private properties are by default not persistent. Through custom attributes it is however possible to mark properties as persistent, not persistent and read only.

OPF.Net Architecture in a two tier application

Every application needs to process not only single business objects but also collections of business objects. OPF.Net supports loading, saving and deleting collections of business objects through its Collection domain class and the corresponding Collection data manager class. As with single business objects, the data manager controls how a collection actually gets loaded from the underlying physical storage by defining one or more storage commands (SQL queries for DBMS) to load different collections of business objects of one type.

OPF.Net fully supports transactions if the underlying physical storage supports transactions. Using the StartTransaction, Commit and Rollback methods of the ObjectBroker, an application can start and complete transactions on one ore more used storages at the same time. All access to the physical storage is handled by the Storage class and is derivatives. To support a new type of physical Storage in OPF.Net a only new Storage class has to be written. At present OPF.Net offers Storage classes for MS SQL Server, ADO ,OLE DB and ODBC.

Step-by-step

This section will take you through the process of using OPF.Net, step by step.

Set up OPF.Net

First of all we have to create the ObjectBroker, which is one of the central units in your application. For windows applications (single user) it is recommended to use the SingleInProcessObjectBroker located in the OPF.ObjectBroker namespace.

If you are developing a web or a server application you should use the MultipleInProcessObjectBroker. In that case you have to register a class that implements the ISessionRetriever interface. That interface implements a function that returns a unique ID for each session. In web applications normally this function should return the ID of the current session.

Since we are developing an simple single user windows application we are using the SingleInProcessObjectBroker. To set the ObjectBroker we have to instance it and assign it to OPF.Main.ObjectBroker. OPF.Main is a static class that holds a global information required by the OPF.Net. (eg. MinDatValue containing the minimum date value the storage supports, IDFieldName the name of the field containing the id...)

/// <span class="code-SummaryComment"><summary>
</span>
/// Initiates the OPF.Net. Creates a new Storage, 
/// ObjectBroker and IDGenerator.
/// <span class="code-SummaryComment"></summary>
</span>
private static Boolean InitOPF()
{
  // Creates a new Single in Process ObjectBroker. 
  // A Single in Process ObjectBroker
  // is normally used in Windows programs, as there 
  // is always only one thread (user) 
  // working with the program.
  // For web applications use the MultipleInProcessObjectBroker(). 
  // You have to register
  // a SessionRetriever, means a class, that has a method 
  // that returns always a unique 
  // id for each connected user.
  //
  // Example:
  // MultipleInProcessObjectBroker OB = new MultipleInProcessObjectBroker();
  // OB.SessionRetriever = (ISessionRetriever)SessionRetrieverObject;
  // OPF.Main.ObjectBroker = OB;
  // 
  _ObjectBroker = new SingleInProcessObjectBroker();
  OPF.Main.ObjectBroker = _ObjectBroker;  
  
  ...

At this point it would also be possible to set the the IDGenerator. The IDGenerator is a class that generates the IDs for new objects saved to the storage. By default the IDGenerator generated GUIDs which are guarantee to be unique. You have the possiblity to override the class to generate your own IDs.

The next step while setting up the OPF.Net is creating a storage. The storages are located in the OPF.Storages namespace. In our sample we use a OleDbStorage since we are dealing with Access.

We have to set the ConnectionString property or the connection string while connecting to the storage. To immediately check if the storage is available we set the connection string when connecting. It is also possible to not immediately connect because the ObjectBroker detects if a connection to a storage is required.

In the sample application the created storage is set as DefaultStorage of the ObjectBroker. If we are not setting the storage as DefaultStorage, we have to associate (register) each Persistent, Collection or derived classes with the storage.

  ...

  // As next we have to set a default storage. 
  // It is also possible not to do that
  // as we can register each persistent with it's own storage. 
  // If while registering
  // a persistent no storage is set, the default storage is used.
  _Storage = new OleDbStorage();
  OPF.Main.ObjectBroker.DefaultStorage = _Storage;

  // At least in this method we will connect to the 
  // storage. It is also possible only
  // to set the connection string, as the ObjectBroker will 
  // connect automatically if 
  // the storage is needed and the program isn't connected 
  // with the storage.
  // OPF.Main.ObjectBroker.DefaultStorage.ConnectionString 
  // = "connectionstring";
  try
  {
    OPF.Main.ObjectBroker.DefaultStorage.Connect(
      @"Provider=Microsoft.Jet.OLEDB.4.0;
      Data Source=..\db\database.mdb;");
  }
  catch
  {
    MessageBox.Show(
      "Not possible to find the database or to " + 
      "connect with the database! " +
      "(Check Global.cs - line 88)", 
      "Error while connecting with database", 
      MessageBoxButtons.OK, MessageBoxIcon.Error);
    return false;
  }

  ...

Usually in our applications this code is located in a function called InitOPF(). We create this function as we think, that the stuff initiating the OPF.Net should be located in a function and not in Main(). InitOPF() returns a Boolean a successful initialization of the OPF.Net. You can check in the Main() function what InitOPF() returns and proceed in a suitable way.

Registering BusinessObjects

The next step takes is to register the persistents and collections with the corresponding data managers. We also register the storage commands required by the application. A StorageCommand is a class that allows us directly send a query to the storage and return an ArrayList containing the results. Storage commands should generally used for performance reasons only, since the circumvent the business objects. We create normally three functions: RegisterPersistents(), RegisterCollections() and RegisterStorageCommands().

  ...
  
  // Register the persistents.
  RegisterPersistents();

  // Registers the collections.
  RegisterCollections();

  // Registers direct storage commands.
  RegisterStorageCommands();

  return true;
}

RegisterPersistents() registers all persistents and derived classes with the corresponding data manager.

/// <span class="code-SummaryComment"><summary>
</span>
/// Registers the persistents. The following method registers 
/// (connects) the persistents with their 
/// datamanager.
/// The datamanager is the storage side object of the persistent.
/// Only the datamanager knows how
/// to save and load the persistent. So it is possible to change
/// the storage (from sql to xml, 
/// for example) only by changing the registration of
/// the persistents and datamanager.
/// <span class="code-SummaryComment"></summary>
</span>
private static void RegisterPersistents()
{
  // We could register the persistent also with a specified storage. 
  // If we do not perform
  // the storage the default storage of the objectbroker is used.
  // Example:
  // _ObjectBroker.RegisterPersistent(typeof(bo.Book), 
  // typeof(bo.BookDM), _Storage);
  _ObjectBroker.RegisterPersistent(typeof(bo.Book), typeof(bo.BookDM));
  
  // TODO: Add your persistent.
}

RegisterCollections() registers all collections and derived classes with the corresponding data manager. It is not necessary to set a storage, as the storage registered while registering the persistent or derived class with is data manager is taken.

/// <span class="code-SummaryComment"><summary>
</span>
/// Registers the collections. The following method registers
/// (connects) each collection with the 
/// datamanager for that collection. Each datamanager of each
/// collection contains the
/// storage commands (sql, xpath, etc) that are needed to
/// load a lot of persistents.
/// We have also to perform a persistent. This persistent is needed
/// while loading the collection,
/// as the collection has to know which type of persistent is loaded.
/// <span class="code-SummaryComment"></summary>
</span>
private static void RegisterCollections()
{
  // The third parameter is the type of persistent that
  // the collection generates
  // while loading a collection. Each collection has to know which
  // type of persistent 
  // to generate, when loading, as there the datamanager 
  // has only a dataset with data,
  // and he has to know which type of persistent to create an populate.
  _ObjectBroker.RegisterCollection(typeof(bo.BookCollection), 
    typeof(bo.BookCollectionDM),
    typeof(bo.Book));

  // TODO: Add your collections.
}

At the last step we have to register the storage commands. RegisterStorageCommands() registers each StorageCommand with the global ObjectBroker using an alias. To use this command in an application just call ObjectBroker.ExecuteStorageCommand(..) specifying alias and save the result to an arraylist.

The StorageCommand class allows you to set the type of command. There are two types:

  • Retrieve and
  • Execute.

Execute executes a query on the storage and returns nothing. Retrieve normally returns a data record that is mapped to an arraylist. If result should be returned set the StorageCommandType to Retrieve.

/// <span class="code-SummaryComment"><summary>
</span>
/// Registers direct storage commands. Storage commands 
/// allow to execute directely commands 
/// to the storage. Is needed for counting etc.
/// <span class="code-SummaryComment"></summary>
</span>
private static void RegisterStorageCommands()
{
  // Create the new storage command. Set an alias.
  StorageCommand StorCmd = new StorageCommand("NumberOfBooks");
  // We have to set the storagecommandtype. We set the type to retrieve,
  // to get the results.
  StorCmd.StorageCommandType = StorageCommandType.Retrieve;
  // Set the command. Here Sql.
  StorCmd.Command = "select count(*) from Books;";

  // At least we have to register the storagecommand. 
  // In a nTier Application
  // if the nTier ObjectBroker is developed, the storage 
  // commands are registered on the
  // server side of the application (means on the server ObjectBroker).
  OPF.Main.ObjectBroker.RegisterStorageCommand(StorCmd);

  // TODO: Add your storage commands.
}

So far set up the OPF.Net and registered the collections, persistents and storage commands. Now we can create a business object to show you how that is done and how they are used.

Creating business objects and support classes

All business objects must inherit from the Persistent class. In order to load, save and delete business objects from the storage we also need to create a data manger class by inheriting from DataManager. To support collections of this business object the collection and collection data manager have to created respectively inheriting from Collection and CollectionDataManager.

Let's start with the derived persistent class.

Creating a derived Persistent class

Creating a derived persistent class is very easy. We only have inherit from Persistent and add the properties represents the columns in the table of the storage.

As you will see in the persistent data manager we can set a property that tells the data manager to parse the column names while populating the properties of the derived persistent object. The parsing algorithm that we use is very simple, but powerful enough to take a column named LAST_NAME and convert it to LastName. If parsing is enabled in the persistent data manager the property in the persistent class has to be named LastName. If not, we have to name the property LAST_NAME.

How does the parser work?

First of all the parser converts the column name do lower case. Then the parser eliminates the underscores and converts the first letter following an underscore to upper case. The first letter of the column name is also converted to upper case. The only exception were the underscore isn't eliminated is _ID. In this case the parser converts to _ID (brings ID to upper case).

Examples:

- LAST_NAME = LastName
- FIRSTNAME = Firstname
- PICTURE_ID = Picture_ID
- AUTHOR_HOUSE_ID = AuthorHouse_ID
- LAST_MAN_STANDING = LastManStanding

To get property names that are independent of storage field names they PopuplateProperties() and PopulateParams() method of the persistent datamanager have to be overridden or you can use the custom attribute MapField. It allows you to directly connect a property with a field in the storage. It is strongly recommended to use the MapField custom attribute instead let the OPF.Net parse the properties.

The following code snippet shows the book business object. As you see also we override the constructors to have the possibility to use all constructors of the base class.

/// <span class="code-SummaryComment"><summary>
</span>
/// Persistent class for Book. Allows to load, save, 
/// delete one Artist from storage.
/// Holds all properties, that are stored in the storage.
/// <span class="code-SummaryComment"></summary>
</span>
public class Book : Persistent
{
    Gender _Gender = 0;
    String _Title = "", _Author = "";
    Boolean _Released = false;
    DateTime _Releasedate = DateTime.MinValue;

    // Constructors
    public Book() :base() {}
    public Book(PersistentFlags PersistentFlags) :base(PersistentFlags) {}
    public Book(PersistentFlags PersistentFlags, String XmlDefinition) 
        :base(PersistentFlags, XmlDefinition) {}
    public Book(String XmlDefinition) :base(XmlDefinition) {}

    // Properties, that correspond to the columns in the storage. 
    // Properties connected by the custom attribute MapField 
    // to the fields in the storage. 
    // You have to set the fieldname in the constructor of 
    // the custom attribute to connect
    // the property with the field in the storage. 
    // The old parsing algorithm is still 
    // supported. Without the MapField attribute properties 
    // are parsed if set so in the 
    // datamanager. 
    // (Look at ParseProperties = true in the DataManager) 
    // Properties are parsed in this 
    // way: LAST_NAME becomes LastName, LASTNAME become 
    // Lastname LastName becomes Lastname, 
    // User_ID becomes User_ID, USER_ID becomes User_ID, 
    // USER_LAST_ID becomes UserLast_ID.
    // 
    // The properties must match exactely the column names
    // in the storage or the parsed 
    // column names. If there is missing a property, OPF.net
    // will tell you that, cause 
    // it is not possible to load an persistent where properties
    // that the query returns 
    // are missing.
    // 
    // The attribute tells the OPF.Net that this property 
    // is mandatory, means that this 
    // property has to be compiled before saving it. When this
    // property hasn't been compiled 
    // before saving you will get an exception. ZipCompressed
    // compresses the content of the 
    // marked property while saving the persistent to the storage
    // and decompresses the content 
    // while loading from storage. ReadOnly is used if you want
    // join each property ReadOnly 
    // that is only read but never written back to the storage.
    // For more information see 
    // OPF.Attributes.cs. 
    //
    [Mandatory, MapField("Title")]
        public String Title { get { return _Title; } 
            set { _Title = value; } }
    [Mandatory, MapField("Released")]
        public Boolean Released { get { return _Released; } 
            set { _Released = value; } }
    [Mandatory, MapField("Author")]
        public String Author { get { return _Author; } 
            set { _Author = value; } }
    [Mandatory, MapField("Gender")]
        public Gender Gender { get { return _Gender; } 
            set { _Gender = value; } }
    [MapField("ReleaseDate")]
        public DateTime Releasedate { get { return _Releasedate; } 
            set { _Releasedate = value; } }

    // You can also compile properties or calculate properties. 
    // Mark them as 
    // NotPersistent. If done so, they aren't read 
    // or written from/to the storage.
    // Example:
    [NotPersistent]public String TitleAndAutor
    {
        get { return "\"" + Title + "\" written by \"" + Author + "\""; }
    }


    // It is also possible to create nested persistent and collections. 
    // In this case
    // you can use database relations in your application. 
    // For example you have a table where you save the data of a 
    // book and another table
    // where you save the pictures. You can register 
    // the Pictures-Object as 
    // NestedPersistent of the Book-Object. While saving both
    // objects are saved. It is 
    // also possible to implement this using lazy loading etc...
    // For more information 
    // look at the tutorial on the sourceforge homepage of the OPF.Net 
    // (<A href="http://www.sourceforge.net/projects/opfnet/).">http://www.sourceforge.net/projects/opfnet/).
</A>}

Several custom attributes can be used to mark properties that are loaded from storage. If a property is marked with the [Mandatory] attribute the property is checked while saving. If such a property is empty the OPF.Net throws an exception. Mark a mandatory field in the storage with the [Mandatory] attribute. A property marked with [ReadOnly] is only loaded and not saved back to the storage. This is useful if you join tables. [NotPersistent] properties are not loaded nor saved. This attribute can be used for calculated properties that have no corresponding storage field, as the property TileAndAutor in the example above.

If you look at the database coming with this article you will find an additional column named "Dyn_Props". The OPF.Net detects this field as field for dynamic properties.

What are dynamic properties?

Dynamic properties are added dynamically to a persistent or derived class while the program is running. You need a large text field in the table of the persistent in the storage. This field has no corresponding property in the object. We normally use a field named Dyn_Props. Dynamic properties are saved to and read from this field. The OPF.Net will detect this field automatically! Dynamic properties are saved as Xml into the dynamic properties field.

To add a dynamic property to a persistent or derived class you have to call the AddDynamicProperty(..) function of that class. Remove them using RemoveDynamicProperty(..). A DynamicProperty is either of type OPF.SupportedTypes or of DynamicType. A DynamicType is a set of possible values the property value can have. You can see it like an enum. For more information check out the source code of the demo program coming with this article.

Creating a derived DataManager class

As next step we have to create the DataManager for our business object. The data manager contains the queries to load, save and delete the business object from storage. If you set ParseProperties to true the column names are parsed (Look above for more information).

We have to override the SetCommands() function of the data manager and set the queries using SetCommand(..).

/// <span class="code-SummaryComment"><summary>
</span>
/// DataManager. The datamanager is the only component of
/// a business object that knows how
/// to save, load, delete etc a persistent from the storage.
/// <span class="code-SummaryComment"></summary>
</span>
public class BookDM : PersistentSqlDataManager
{
  public BookDM(System.Type ConnectedObjectType) :base(ConnectedObjectType) 
  {
    // If true the properties are parsed while loading
    // and saving them to the storage.
    // For more information see above.
    ParseProperties = true;
  }

  /// <span class="code-SummaryComment"><summary>
</span>
  /// Sets the commands: such as load, save, delete etc.
  /// In this case we set sql commands,
  /// as we work with a sql database.
  /// <span class="code-SummaryComment"></summary>
</span>
  protected override void SetCommands()
  {
    // The words starting with : are parameters, that are replaced 
    // by the values while executing the operation to the storage.
    // As you see there is there is a field named ID. Each persistent has 
    // to have an unique id, that is generated by the 
    // IDGenerator. You have to 
    // create for each persistent (means in each table) a 
    // field named ID and to set that one
    // as primary key. It is also possible to rename this field.
    // Look at OPF.Main.IDFieldName.

    SetCommand(DMOperations.Delete, "delete from Books where ID = :ID;");
    SetCommand(DMOperations.Load, "select * from Books where ID = :ID;");
    SetCommand(DMOperations.Insert,
      "insert into Books (ID, Title, Released, Author," + 
      " Gender, ReleaseDate, Dyn_Props) values" + 
      " (:ID, :Title, :Released, :Author, :Gender," + 
      " :Releasedate, :Dyn_Props);");
    SetCommand(DMOperations.Save,
      "update Books set Title = :Title, Released =" + 
      " :Released, Author = :Author, Gender" + 
      " = :Gender, ReleaseDate = :Releasedate, Dyn_Props" +
      " = :Dyn_Props where ID = :ID");
  }
}

Parameters in the commands are marked with a ':'. Parameters are replaced by the storage class while saving, loading or deleting the object in the storage. By setting the ParameterRecognitionString property of the OPF.Main class it is possible to change the ':' to '@' (.Net standard) or something else. We use the ':' because of a former OPF version written in Delphi.

Creating a derived Collection class

The next step takes us to the Collection class and creating a derived collection class. Collections are lists of objects of a certain type loaded from the storage. Each collection can be loaded in several ways by defining one or more loading functions. Example given LoadAll() to load all objects of the given type. For each load function a corresponding load function and command (sql..) has to be defined in the collection's data manger. Each loading function passes an alias name and a parameter collection to the collection data manager for execution (The data manager identifies the command to use using the alias).

The following BookCollection shows how to create a collection where all objects of the table are loaded and where only object with a certain author name are loaded.

/// <span class="code-SummaryComment"><summary>
</span>
/// Collection that allows to load, save, delete and
/// hold more than one persistent. Here you
/// can perform loads by name etc.
/// <span class="code-SummaryComment"></summary>
</span>
public class BookCollection : Collection
{
  /// <span class="code-SummaryComment"><summary>
</span>
  /// Loads all books in the storage. The Load command calls 
  /// the datamanager of the collection,
  /// and that one has to have the same collection defined.
  /// In this case "Book.LoadAll". With 
  /// this name, the datamanager searches the query string an executes 
  /// the query and loads the collection.
  /// <span class="code-SummaryComment"></summary>
</span>
  public void LoadAll()
  {
    Load("Book.LoadAll", new ParameterCollection());
  }

  /// <span class="code-SummaryComment"><summary>
</span>
  /// Loads all books with a certain autorname. You have to create
  /// a new parametercollection and
  /// add the authorname as parameter.
  /// <span class="code-SummaryComment"></summary>
</span>
  /// <PARAM name="LastName"></PARAM> public void LoadByAuthorName(String
  AuthorName)
    { // Instead we could
    use: // ParameterCollection PCol = new
    ParameterCollection(); // PCol.Add("AutorName",
    AuthorName); // Load("Book.LoadByAuthorName",
    PCol);
    Load("Book.LoadByAuthorName", 
      new ParameterCollection("AuthorName", AuthorName));
  }

  // TODO: Add here your collections.
}

As you see in LoadByAuthorName(..) the ParameterCollection takes one parameter that contains the variable with the name of the author and a name for the parameter - in this case "AuthorName". Attention: LoadAll() contains an empty ParameterCollection!! You have always to set a parameter collection!

Creating a derived Collection DataManager class

/// <span class="code-SummaryComment"><summary>
</span>
/// DataManager for the collection. Holds the sql 
/// statements and dynamic query methods
/// for the collection.
/// <span class="code-SummaryComment"></summary>
</span>
public class BookCollectionDM : CollectionSqlDataManager
{
  String LoadAllSQL = "select * from Books;";
  String LoadByAuthorName = 
    "select * from Books where Author = :AuthorName";
  // It is also possible do compile a query dynamical. 
  // For more information look at the tutorial on the 
  // sourceforge homepage of the OPF.Net 
  // (http://www.sourceforge.net/projects/opfnet/).

  // TODO: Add here your SQL statements for other collections.
  

  /// <span class="code-SummaryComment"><summary>
</span>
  /// Define collections is an abstract method that has to be
  /// overriden. Here you have to
  /// define all collections that can be loaded and called by the
  /// collection class. You have also
  /// the possibility to define a delegate, that points to a method,
  /// that creates a dynamic sql
  /// string.
  /// <span class="code-SummaryComment"></summary>
</span>
  protected override void DefineCollections()
  {
    DefineCollection("Book.LoadAll", LoadAllSQL);
    DefineCollection("Book.LoadByAuthorName", LoadByAuthorName);
    // TODO: Add here your collections.
  }
}

The collection datamanager is very simple. There are only the queries (defined as strings or compiled dynamically using a delegate) and a function called DefineCollections() that must be overridden. In DefineCollections() you have to the connections between the collection names and the queries using DefineCollection(..).

It is also possible to set a Delegate as query. This delegate is called while loading the collection. In this way a query can be assembled dynamically at runtime.

...

/// <span class="code-SummaryComment"><summary>
</span>
/// Compiles the query dynamical.
/// <span class="code-SummaryComment"></summary>
</span>
private String DynQuery(ParameterCollection PCol)
{
  String Query = "select * from table where field = '1' and ";
  
  // You have the possiblity to change the ParameterCollection
  // (add, remove or change paramater). While compiling 
  // a dynamic query try to work only with 
  if (PCol.ContainsParameter("ParameterName"))
  {
    String Parameter = PCol.GetParameter("ParameterName");
    Parameter = Parameter.Replace("*", "%") + "%";
    PCol.SetParameterValue("ParameterName", Parameter);
  
    Query += " field1 = :ParameterName and ";
  }
  
  if (PCol.ContainsParameter("ParameterName1"))
  {
    String Parameter1 = PCol.GetParameter("ParameterName1");
    PCol.Remove("ParameterName1");
    PCol.Add("Parameter2", Parameter1);
    
    Query += " field2 = :ParameterName1 ";
  }
  
  // Compile the rest of the query...
  return Query;
}

DefineCollection("Book.LoadDynamically", 
  new DynamicCommandDelegate(DynQuery));

...

Using BusinessObjects

It's about time to use the business objects generated, don't ya think so?!

Persistent and derived classes

Single business objects are generally not loaded since the ID is generally not known. Normally a collection of objects is loaded.

bo.Book Book = new bo.Book();
Book.Load("1");

To save a business object you have to call the Save() function. To delete it the Delete() function.

try
{
  Book.Save();
}
catch (ConcurrencyException exc)
{
  // Exception is thrown if a concurrency problem occured.
}
catch (ConstraintsException exc)
{
  // Exception is thrown if a constraints problem occured.
  // This type of exception occures, if properties,
  // that are mandatory aren't compiled.
}

Collection and derived classes

A collection or business objects is loaded by using one of the customized load functions. For the book object it's possible to use LoadAll() and LoadByAuthorName(..).

bo.BookCollection BookCol = new bo.BookCollection();
BookCol.LoadByAuthorName("Roddenberry");

To delete an object from the collection you have to call Delete(..).

if (BookCol.Count > 0)
  BookCol.Delete(0); // Delete the first object.

To return an object use the GetObject(..) function or to use the Indexer BookCol[..].

if (BookCol.Count > 0)
{
  bo.Book Book = BookCol[0];
}

The Delete(..) function of the collection does not directly delete an object from storage. It moves the object to a so-called "DeletedList". Only when the Save() function is called all objects on the "DeletedList" are deleted.

if (BookCol.Count > 0)
  BookCol.Delete(0);

try
{
  BookCol.Save();
}
catch (ConcurrencyException exc)
{
  // Exception is thrown if a concurrency problem occured.
}
catch (ConstraintsException exc)
{
  // Exception is thrown if a constraints problem occured.
  // This type of exception occures, if properties, that
  // are mandatory aren't compiled.
}

To loop over a collection you can use the Indexer BookCol[..] or the foreach directive.

foreach(bo.Book Book in BookCol.Objects)
{
  // .. Do something.
}

for(Int32 i = 0; i < BookCol.Count; i++)
{
  bo.Book Book = (bo.Book)BookCol[i];
  // .. Do something.
}

As conclusion a full example that demonstrates how to use the collection:

bo.BookCollection BCol = new bo.BookCollection();
// Load all objects in database in the table "book".
BCol.LoadAll();
// Delete the first object.
if (BCol.Count > 0)
  BCol.Delete(0);

// Loop over all items and do something.
for(Int32 i = 0; i < BCol.Count; i++)
{
  bo.Book Book = (bo.Book)BookCol[i];
  Book.Name = "TestName";
}

// Saves the collection. Deletes the objects in the "deletedlist".
try
{
  BCol.Save();
}
catch (ConcurrencyException exc)
{
  // Exception is thrown if a concurrency problem occured.
}
catch (ConstraintsException exc)
{
  // Exception is thrown if a constraints problem occured.
  // This type of exception occures, if properties,
  // that are mandatory aren't compiled.
}

For more information about the OPF.Net visit the sourceforge page of the framework (http://www.sourceforge.net/projects/opfnet/) or the official homepage (http://www.littleguru.net/ or http://www.sarix.biz/opf).

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

Share

About the Authors

Christian Liensberger
Web Developer
Austria Austria
Studying at the technical university for vienna. Living in Brixen (South Tyrol, Italy) and working at Bozen (also in South Tyrol) during the summer months and holidays...

Martin Geier

Italy Italy
No Biography provided

Comments and Discussions

 
GeneralOpf3 PinmemberChristian Liensberger31-Mar-04 1:40 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141216.1 | Last Updated 4 Nov 2003
Article Copyright 2003 by Christian Liensberger, Martin Geier
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid