This step explains how OpenAccess ORM supports persistence by reachability which, means that all objects that are reachable from a persistent class are automatically stored in the database (Refer to The OpenAccess ORM Object Lifecycle for more information). Aggregation of objects is integrated seamlessly into the C# and VB .NET programming language. This step also explains the load behavior for references to objects of persistence capable user classes.
The topic is based on an object-oriented data model. There is a Customer class as well as an Order class, which has a reference to one Customer and a Generic Collection of OrderDetail instances. Each OrderDetail references exactly one Product. The data model used has been explained diagrammatically below:
Data Model
Creating the persistent classes needed for Step 3
Order class where the usage of generic collections is demonstrated:
C# |
Copy Code |
[OpenAccess.Persistent] public class Order { // persistent reference to customer // The customer field is aggregated in the Order class. // Objects of Customer have their own identity // and can be referenced by other objects as well.
private Customer customer; private int orderNo; private DateTime orderDate; private DateTime shippedDate;
// Defining a Generic Collection of OrderDetail instances private IList <OrderDetail> details;
public IList<OrderDetail> details { get { if( details == null ) { details = new List<OrderDetail>(); } return details; } } } |
VB .NET |
Copy Code |
<OpenAccess.Persistent> _ Public Class Order
Private customer As Customer Private orderNo As Integer Private orderDate As DateTime Private shippedDate As DateTime
Private details As IList(Of OrderDetail) Public ReadOnly Property details() As IList(Of OrderDetail) Get If details = Nothing Then details = New List(Of OrderDetail)() End If Return details End Get End Property End Class |
Customer class where the usage of Nullables is demonstrated, for the discountRate field as shown below:
C# |
Copy Code |
[OpenAccess.Persistent] public class Customer { //the following members are private, but any access rights are permitted private string name; private int customerNo; //Use of Nullable datatypes private int? discountRate; } |
VB .NET |
Copy Code |
<OpenAccess.Persistent> _ Public Class Customer Private name As String Private customerNo As Integer Private discountRate As System.Nullable(Of Integer) End Class |
OrderDetail class
C# |
Copy Code |
[OpenAccess.Persistent] public class OrderDetail { private float unitPrice; private int quantity; // persistent reference to Product private Product product; } |
VB .NET |
Copy Code |
<OpenAccess.Persistent> _ Public Class OrderDetail Private unitPrice As Single Private quantity As Integer Private product As Product End Class |
Product class
C# |
Copy Code |
[OpenAccess.Persistent] public class Product : IInstanceCallbacks { private int productNo; private string name; private float unitPrice; } |
VB .NET |
Copy Code |
<OpenAccess.Persistent> _ Public Class Product Implements IInstanceCallbacks Private productNo As Integer Private name As String Private unitPrice As Single End Class |
The class Product implements the methods of the OpenAccess.IInstanceCallbacks interface in order to obtain life cycle events. In this example, the PostLoad() method is used to print a message when the object data is retrieved, i.e., "loaded", from the database. A more practical example of using instance callbacks is for classes, which include non-persistent fields whose values depend on the values of persistent fields. The PostLoad() callback can be used to populate the values in these transient fields.
The PostLoad() is called after the values are loaded from the data store into this instance. Transient fields should be initialized in this method:
C# |
Copy Code |
public void PostLoad(); |
VB .NET |
Copy Code |
Public Sub PostLoad() |
Main Function
The main purpose of this task is to explain how to use the powerful concepts of persistence by reachability, aggregation of objects and delayed loading of complex object graphs.
C# |
Copy Code |
static void Main(string[] args) { IObjectScope scope = Database.Get(connectionId).GetObjectScope();
Order order = CreateOrder(scope);
DemonstrateDelayedLoading(scope, order);
Console.WriteLine("\n\nPress <Return> to continue ..."); Console.ReadLine();
scope.Dispose(); } |
VB .NET |
Copy Code |
Shared Sub Main(args As String()) Dim scope As IObjectScope = Database.Get(connectionId).GetObjectScope() Dim order As Order = CreateOrder(scope) DemonstrateDelayedLoading(scope, order) Console.WriteLine(vbLf & vbLf & "Press <Return> to continue ...") Console.ReadLine() scope.Dispose() End Sub |
Create a complex persistent Order object:
C# |
Copy Code |
Order order = CreateOrder(scope); |
VB .NET |
Copy Code |
Dim order As Order = CreateOrder(scope) |
The following method demonstrates the delayed loading of objects, i.e., how object references are loaded on demand:
C# |
Copy Code |
DemonstrateDelayedLoading(scope, order); |
VB .NET |
Copy Code |
DemonstrateDelayedLoading(scope, order) |
Persistence by reachability
Given below is the definition of the CreateOrder class:
C# |
Copy Code |
private static Order CreateOrder(IObjectScope scope) { scope.Transaction.Begin();
Product product1 = new Product(1001, "Hamburger", 1.20f); Product product2 = new Product(1002, "Cheeseburger", 1.30f);
Customer customer = new Customer("4567", "Smith");
Order order = new Order( customer ); order.OrderNo = 100546; order.OrderDate = new DateTime(2004, 02, 01); order.ShippedDate = new DateTime(2004, 02, 01); order.Details.Add(new OrderDetail(1.20f, 2, product1)); order.Details.Add(new OrderDetail(1.30f, 3, product2));
scope.Add(order);
scope.Transaction.Commit();
return order; } |
VB .NET |
Copy Code |
Private Shared Function CreateOrder(scope As IObjectScope) As Order scope.Transaction.Begin() Dim product1 As New Product(1001, "Hamburger", 1.2F) Dim product2 As New Product(1002, "Cheeseburger", 1.3F) Dim customer As New Customer("4567", "Smith") Dim order As New Order(customer) order.OrderNo = 100546 order.OrderDate = New DateTime(2004, 2, 1) order.ShippedDate = New DateTime(2004, 2, 1) order.Details.Add(New OrderDetail(1.2F, 2, product1)) order.Details.Add(New OrderDetail(1.3F, 3, product2)) scope.Add(order) scope.Transaction.Commit() Return order End Function |
The following method calls the Product class constructor to set the fields productNo, name, and unitPrice:
C# |
Copy Code |
Product product1 = new Product(1001, "Hamburger", 1.20f); |
VB .NET |
Copy Code |
Dim product1 As New Product(1001, "Hamburger", 1.2F) |
Creating an instance of Order with the customer as parameter:
C# |
Copy Code |
Order order = new Order( customer ); |
VB .NET |
Copy Code |
Dim order As New Order(customer) |
Adding an OrderDetail instance to the Details ArrayList/Generic Collection field of the Order object:
C# |
Copy Code |
order.Details.Add(new OrderDetail(1.20f, 2, product1)); |
VB .NET |
Copy Code |
order.Details.Add(New OrderDetail(1.2F, 2, product1)) |
Adding the order object to the scope implicitly adds all the objects referenced by order to the scope as well. This powerful concept is called persistence by reachability. The entire object graph will be stored in the database during the following Commit() call:
C# |
Copy Code |
scope.Add(order); |
VB .NET |
Copy Code |
scope.Add(order) |
Delayed loading of objects
C# |
Copy Code |
private static void DemonstrateDelayedLoading(IObjectScope scope, Order order) { scope.Transaction.Begin();
Console.WriteLine("The orders number is: {0}", order.OrderNo);
Console.WriteLine("Name of first ordered product: {0}", order.Details[0].Product.Name);
scope.Transaction.Commit(); } |
VB .NET |
Copy Code |
Private Shared Sub DemonstrateDelayedLoading(scope As IObjectScope, order As Order) scope.Transaction.Begin() Console.WriteLine("The orders number is: {0}", order.OrderNo) Console.WriteLine("Name of first ordered product: {0}", order.Details(0).Product.Name) scope.Transaction.Commit() End Sub |
OpenAccess ORM automatically ensures, that if the Order object is accessed in a new transaction, then its values are loaded from the database again upon the first access to the object. The Visual Studio debugger also loads the objects into memory when the object values need to be displayed. For example, expanding all object references from order triggers the loading of objects from the database.
The following code fragment begins a transaction:
C# |
Copy Code |
scope.Transaction.Begin(); |
VB .NET |
Copy Code |
scope.Transaction.Begin() |
The following code fragment loads the Order object:
C# |
Copy Code |
Console.WriteLine("The orders number is: {0}", order.OrderNo); |
VB .NET |
Copy Code |
Console.WriteLine("The orders number is: {0}", order.OrderNo) |
The access to the field order.orderNo (via property OrderNo ) triggers the loading of the default fetch group for the Order object from the database. By default, the default fetch group contains all value type fields (e.g. Int32, Datetime, and user-defined structs) and string fields. The default FetchGroup also contains only the id of the reference fields, the referenced object itself will only be fetched when it is actually accessed. You can modify the default fetch group for a class using the DefaultFetchGroupAttribute field attribute. Fields, which are not present in the default fetch group are loaded on demand, when they are accessed for the first time. This includes references to persistent classes, such as the customer field, and collections, such as the details field. For example, calling the customer.Name property requires the Customer reference in the Order to be resolved. Refer to The OpenAccess ORM Object Lifecycle for more information about object resolution and delayed loading.
The following code fragment loads the product objects:
C# |
Copy Code |
order.Details[0].Product.Name; |
VB .NET |
Copy Code |
order.Details(0).Product.Name |
Here, the name of the product of the first OrderDetail object is accessed. Access to the order.Details property triggers loading of the details field, and access to the Product.Name property triggers the loading of one Product object including its default fetch group fields. This is demonstrated by printing a message to the console in the PostLoad() method of the Product class:
C# |
Copy Code |
[OpenAccess.Persistent] public class Product : IInstanceCallbacks { public void PostLoad() { Console.WriteLine("A Product instance has been loaded from the database."); } } |
VB .NET |
Copy Code |
<OpenAccess.Persistent> _ Public Class Product Implements IInstanceCallbacks Public Sub PostLoad() Console.WriteLine("A Product instance has been loaded from the database.") End Sub End Class |
PostLoad() is called after the default fetch group values are loaded in the persistent instance. This is not done inside the query execution. The fields are loaded into the instance, either when you access a default fetch group field or when you call retrieve(). Inside the PostLoad call, only fields of the default fetch group are allowed to be accessed; other fields may not be set. It is only allowed to change transient fields from within the PostLoad, for e.g., Overriding PostLoad() is useful for initializing transient fields.
For more information about the IInstanceCallbacks interface and life cycle events, refer to The OpenAccess ORM Object Lifecycle.