Monday, February 5, 2007

Part 1 - an Introduction Object Relational Mapping using OPF3

Object Relational Mapping (ORM) begins with creating and using objects that map to tables in a data base via its public and private properties. ORM objects are also called persistent objects. What are the benefits of ORM? Most ORM tools abstract away the back end database and leaves a simplified way to perform CRUD operations on the tables in the database (CRUD = Create, Read, Update and Delete). From this, we find considerable time savings and enhanced code reuse. Not having to write stored procedures and SQL queries to CRUD data plus gaining reusable objects lowers cost and time to market. This series will focus on OPF3. Of all of the ORM tools I have investigated, this is the fastest, simplest and most flexible (extensible).

Often an application needs to save its information (DATA) has to place it somewhere so that it can be accessible in the future. Locations such as XML files, ascii files and SQL Tables are commonplace. Today the most common location to persist data is within SQL Databases. For the remainder of this article, when I refer to Mapping I am referring to a SQL Database or SQL Table The “O” in Object Relational Mapping means Objects. These objects need to have relationships to the tables which they are representing.

Please note: all of the code snippets in this series uses C# and will be.Net 2.0 compatible.

Let’s take a look at a simple table:





There are several important points to notice about this table:

  1. There is a primary key field called Recordid. This field must be unique for all records added to this table.
  2. There are various field types. This means that when mapping persisted objects to a table, you must be able to convert to data types that are compatible with target fields in the table.
  3. Field lengths. Objects that map to this table must make sure that the data being passed to each field has a length that is not longer their matching fields in the target table.
  4. Objects must have some mechanism that prevents passing null values to fields where these fields do not accept nulls (such as the recordid in the table above).


Persisted objects are derived from a class. Try to keep these objects as clean as possible. Do not add business logic code unless it’s same for all persisted objects and must always be true when using the persisted objects. Try to use only code that will clean or ensure the data format and length is correct.

Using OPF3 as our example, OPF3 persisted objects contain several parts:

  1. They contain private fields
  2. They can have 1 or more defined relationships to other persisted objects
  3. Properties with getters and setters (get{}, set{}) for reading values in from the database and sending updates and new information to the database.
  4. Interfaces to extend the objects.
  5. OPF3 Persisted objects do not derive from a base class. This means you can add a base class that is shared by all of your persisted classes to support validation or basic business logic to ensure field data is formatted etc. This is not a place to put validation that should be placed in your Business Logic Tier.


Persisted objects should have no code within them that tells them how to create, read, update and delete to a table (CRUD). Persisted objects are just containers to hold information and get passed to the OPF3 engine for processing.


Let's see how to create a persisted class that is a representation for the table listed above:

Class Declaration


First step is the class declaration:

[Serializable]
[Persistent("Employee", PoolSize=250, Rights=PersistentRights.All)]
public class Employee : IObjectNotification, ISelfContainingObject, INotifyPropertyChanged
{

Notice the class can be serialized. This means it can be transported across the internet, web services or networks natively. This attribute: [Persistent( "Employee" )] tells OPF3 engine that this class is associated with a table named Employee in the database. Note to reduce the number of records initially loaded for any parent persistent, you can add the following: PoolSize=###. ### represents how many objects of this type maximum will be returned from the database. Use this when you have a lot of objects to speed up loading of large lists. Pooling will activate when there is a loading of many objects of the same type in a repeated manner. It does not mean that only 250 records will be returned.

The second item above is Rights. This can be defined as follows:

  • None No right is granted to the persistent object. I can't be loaded, inserted, updated or deleted.
  • Load The persistent object is only allowed to be loaded.
  • Insert The persistent object is only allowed to be inserted.
  • Update The persistent object is only allowed to be updated.
  • Delete The persistent object is only allowed to be deleted.
  • All The persistent object has all rights.



FIELDS


The next portion of the Employee Persisted class are the fields:


private Guid _recordid = Guid.NewGuid();
private string _fname;
private string _lname;
private string _location;
private string _title;
private string _department;
private string _email;
private string _vmail;
private Blob _picture = new Blob();
private string _pictureType;
private string _jobResponsibility;
private string _supervisor;
private bool? _allowUserEdit;
private string _sid;
private string _picturePath;

When I mentioned the object must have a data type for each field that can be converted to a matching field type in the table. The list above shows this in action. In our database we have a field of type image. OPF3 maps the C# type of Blob to Picture field (private Blob _picture = new Blob()). Picture field can hold binary data and Blob is an OPF3 type of Binary array.

Relations


The next part of the Employee Class are the Relations:


[Relation( "Recordid = Parentid" )]
private ObjectSetHolder<Address> _addresses = new ObjectSetHolder();

///


/// Gets and sets a set of connected "Address" persistent objects.
///

[XmlIgnore]
public ObjectSet<Address> Addresses
{
get { return _addresses.InnerObject; }
}


The Relation attribute ([Relation( "Recordid = Parentid" )]) tells OPF3 that there is a relationship between Employee object and Address object via the Employee.Recordid and Address.Parentid fields. There are other properties in the Relation attribute such as Name which is used to assign a unique name for the relationship. You can also set the WeakPersistent and WeakRelations for defining a one-to-many or a relationship between a weak persistent and the target child object. Please refer to the OPF3 documentation for more details on these 2 properties.

The next part of the relationship gives access to the parent to the child collection of ObjectSet. In the above example, Employee will store the related address records in a ObjectSetHolder collection. This will be done automatically by the OPF3 engine for all employee objects that are pulled from the database. To access the Address collection, you would do the following:

ObjectSet<Employee> curEmployees = currentContext.GetObjectSet<Employee>();
ObjectSet<Address> curEmpAddresses = curEmployees[2].Addresses;

Or

ObjectSet<Employee> curEmployees = currentContext.GetObjectSet<Employee>();

foreach(Employee curEmployee in curEmployees)
{
foreach(Address curAddress in curEmployee.Addresses)
{
Do something here with curAddress ……..
}
}


Don’t worry about the syntax in the previous example. Pay attention to how we access Address via Object notation and referring to Addresses which was defined in the relation under Employee.

Properties


This is the complete property list for the Employee Persistent Object mapped to the Table listed above


///


/// Returns the identifier of the persistent object. Don't set it manually!
///

[Field("Recordid", AllowDBNull = false, Identifier = true, AutoNumber = false)]
public Guid Recordid
{
get
{
return _recordid;
}
private set
{
_recordid = value;

OnRowChanged("Recordid");
}
}

///
/// This property is mapped to the "Fname" field. Length must be between 0 and 50 characters.
/// Storage comment: .
///

[Field("Fname")]
public string Fname
{
get
{
return _fname;
}
set
{
if (value != null && value.Length > 50)
throw new ArgumentException("Value length must be between 0 and 50 characters.");

_fname = value;

OnRowChanged("Fname");
}
}

///
/// This property is mapped to the "Lname" field. Length must be between 0 and 50 characters.
/// Storage comment: .
///

[Field("Lname")]
public string Lname
{
get
{
return _lname;
}
set
{
if (value != null && value.Length > 50)
throw new ArgumentException("Value length must be between 0 and 50 characters.");

_lname = value;

OnRowChanged("Lname");
}
}

///
/// This property is mapped to the "Location" field. Length must be between 0 and 50 characters.
/// Storage comment: .
///

[Field("Location")]
public string Location
{
get
{
return _location;
}
set
{
if (value != null && value.Length > 50)
throw new ArgumentException("Value length must be between 0 and 50 characters.");

_location = value;

OnRowChanged("Location");
}
}

///
/// This property is mapped to the "Title" field. Length must be between 0 and 50 characters.
/// Storage comment: .
///

[Field("Title")]
public string Title
{
get
{
return _title;
}
set
{
if (value != null && value.Length > 50)
throw new ArgumentException("Value length must be between 0 and 50 characters.");

_title = value;

OnRowChanged("Title");
}
}

///
/// This property is mapped to the "Department" field. Length must be between 0 and 50 characters.
/// Storage comment: .
///

[Field("Department")]
public string Department
{
get
{
return _department;
}
set
{
if (value != null && value.Length > 50)
throw new ArgumentException("Value length must be between 0 and 50 characters.");

_department = value;

OnRowChanged("Department");
}
}

///
/// This property is mapped to the "Email" field. Length must be between 0 and 50 characters.
/// Storage comment: .
///

[Field("Email")]
public string Email
{
get
{
return _email;
}
set
{
if (value != null && value.Length > 128)
throw new ArgumentException("Value length must be between 0 and 50 characters.");

_email = value;

OnRowChanged("Email");
}
}

///
/// This property is mapped to the "Vmail" field. Length must be between 0 and 15 characters.
/// Storage comment: .
///

[Field("Vmail")]
public string Vmail
{
get
{
return _vmail;
}
set
{
if (value != null && value.Length > 15)
throw new ArgumentException("Value length must be between 0 and 15 characters.");

_vmail = value;

OnRowChanged("Vmail");
}
}

///
/// This property is mapped to the "Picture" field.
/// Storage comment: .
///

[Field("Picture")]
public Blob Picture
{
get
{
return _picture;
}
set
{
if (value != null && value.Length > 2147483647)
throw new ArgumentException("Selected Employee Picture is too large. Please choose a smaller one");

_picture = value;

OnRowChanged("Picture");
}
}

///
/// This property is mapped to the "PictureType" field. Length must be between 0 and 250 characters.
/// Storage comment: .
///

[Field("PictureType")]
public string PictureType
{
get
{
return _pictureType;
}
set
{
if (value != null && value.Length > 250)
throw new ArgumentException("Value length must be between 0 and 250 characters.");

_pictureType = value;

OnRowChanged("PictureType");
}
}

///
/// This property is mapped to the "PictureType" field. Length must be between 0 and 250 characters.
/// Storage comment: .
///

[Field("PicturePath")]
public string PicturePath
{
get
{
return _picturePath;
}
set
{
if (value != null && value.Length > 1024)
throw new ArgumentException("The Path line is too long, Please set a shorter path.");

_picturePath = value;

OnRowChanged("PicturePath");
}
}

///
/// This property is mapped to the "JobResponsibility" field. Length must be between 0 and 2147483647 characters.
/// Storage comment: .
///

[Field("JobResponsibility")]
public string JobResponsibility
{
get
{
return _jobResponsibility;
}
set
{
if (value != null && value.Length > 2147483647)
throw new ArgumentException("Value length must be between 0 and 2147483647 characters.");

_jobResponsibility = value;

OnRowChanged("JobResponsibility");
}
}

///
/// This property is mapped to the "Supervisor" field.
/// Storage comment: .
///

[Field("Supervisor")]
public string Supervisor
{
get
{
return _supervisor;
}
set
{
if (value != null && value.Length > 60)
throw new ArgumentException("Value length must be between 0 and 60 characters.");

_supervisor = value;

OnRowChanged("Supervisor");
}
}

///
/// This property is mapped to the "AllowUserEdit" field.
/// Storage comment: .
///

[Field("AllowUserEdit")]
public bool? AllowUserEdit
{
get
{
return _allowUserEdit;
}
set
{
_allowUserEdit = value;

OnRowChanged("AllowUserEdit");
}
}

///
/// This property is mapped to the "SID" field. Length must be between 0 and 60 characters.
/// Storage comment: .
///

[Field("SID")]
public string SID
{
get
{
return _sid;
}
set
{
if (value != null && value.Length > 60)
throw new ArgumentException("Value length must be between 0 and 60 characters.");

_sid = value;

OnRowChanged("SID");
}
}

Properties are where a lot of the action in a persisted object occurs. Let’s break down the following property:


[Field("Recordid", AllowDBNull = false, Identifier = true, AutoNumber = true)]
public Guid Recordid
{
get
{
return _recordid;
}
private set
{
_recordid = value;

OnRowChanged("Recordid");
}
}

The Field attribute marks this property as a field. The “Recordid” is the name of the field that this property will map to in the table. AllowDBNull=false means that the Recordid field in the Employee table does not allow nulls. Identifier = true means this field is a unique Id field (primary key field). AutoNumber determines if the field will be automatically populated with a new incremental or unique value by OPF3. You may have noticed that in our fields section we had the following:

private Guid _recordid = Guid.NewGuid();

When AutoNumber is set to true, OPF3 will create a unique key by placing the above as the field value for the recorded. According to the OPF3 documentation, AutoNumber is only supported in Access and MsSql storages. This is important to keep in mind if you plan to allow your applications to be ported to Oracle or any other supported database platforms. This is not a hard fast limit. The source code for all current supported storages comes with a purchased copy of OPF3. Therefore It is possible to modify the other storages to support AutoNumber in a different manner but the AutoNumber parameter may still be ignored.


The “set” or setter method for the property is set as private. This ensures that developers will not be able to accidentally overwrite the existing record’s value and maintaining data integrity.

The final part of the property is this line:

OnRowChanged( "Recordid" );

Notice that in the class’s declaration, it implements INotifyPropertyChanged interface. This adds the ability for the Persistent object to notify any databound controls that have been bounded to this object that it’s value has been changed. The INotifyPropertyChanged blue prints OnRowChanged event which is implemented in the OPF3 persistent object class as follows:

private void OnRowChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

This pretty much covers the persistent objects and the structure. If you are using the wizard to generate the persistent object’s class files all of the code for each object will be automatically generated for you! Yes even the Interface implementation code. It is a major time saver and it comes free when you buy a registered copy.

Part 2 in this series will show you how crate a ObjectContext to your storage, perform CRUD operations on objects (records) and the various ways to do so. I will also show filtering using SqlQuery and alternatively (better) the ObjectSearcher.

Part 3 - will tie up all of what we learned in Parts 1 and 2 using the ASP.Net EmployeeDirectory demo.

Monday, January 29, 2007

OPF3 fundamentals

Hello lucky .Net Developers!!

Last weekend I did a talk on ObjectDataBinding, Application Layering and Object Persistent Framework - OPF3. The biggest interest was on OPF3 and the Asp.Net ObjectDataBinding.

To help inform and educate the masses, I am going to begin writing a series of blogs on these topics:
  1. A complete series on OPF3. OPF3 is a ORM tool that is absolutely the fastest and easiest to use ORM available today. It is fully .Net 1.1 and 2.0 compliant.
  2. A new series on Layreing and Object Persistence across the nTiers.
  3. Asp.Net data binding to OPF3 objects

Throughout this series I will build an Employee Directory Asp.Net and Winforms Example. They will highlight each of the session’s key points. I will also explore the issues of object data binding in Asp.Net applications and how to work around them.

Please subscribe to this blog and wonderful things will come to you in the near future.