综合

img Aaron_Lau

Developing Database Applications with an O-R Mapping Framework Part 1

发表于2004/9/25 21:31:00  887人阅读

Developing Database Applications with an O-R Mapping Framework Part 1

Download this article in Word format

Aaron Lau
September 2004

Applied to:
Microsoft .NET Framework 1.1
Microsoft SQL Server 2000


Summary: this is the part one of a two-part article which introduces how to develop database applications with an O-R (Object-Relational) mapping framework. The first part introduces an O-R mapping framework called DAP (Data Access Process) Framework; and the second part introduces an O-R mapping tool called SuperType which is used to generate from database schemas files of business types and configurations that will be used as the mete-data by the DAP Framework. At the time this article was written, the latest version of the DAP Framework and the SuperType is 2.0.

Content

Introduction

Design of the DAP Framework 2.0

Developing with the DAP Framework 2.0

Deployment and Operations

Conclusion

Introduction

Multi-Layered architecture has for a long time been proved to be an effective way to build reliable, maintainable and flexible enterprise applications. Originally, we had tree layers in the applications we built, they are Presentation (UI) layer, BL (Business Logic) layer and Data layer. As we proceeded, we found that another two layers are needed. One is User Interface Process layer between UI and BL layers, and the three together work as the MVC pattern which is a very good approach that can effectively separate UI and BL layers; another is Data Access layer that exists between BL and Data layers, and that Data Access layer is what the DAP Framework is all about.

The DAP Framework is an O-R mapping framework for the .NET enterprise development. It can assist you with developing flexible and extensible Multi-Layered Windows and Web applications which involve relational databases development. It can be used with SuperType, an O-R mapping tool, together to build intricate enterprise applications in a record time. As an O-R mapping framework, the DAP Framework has the following features, and all of them will be described in detail throughout the rest of this article.

Materialization and dematerialization of a single object

Materialization and dematerialization of a collection of objects

Paging is enabled

Object state management

Cache is enabled

Distributed transaction

Multi-User access and locking strategies

Who Should Read This Article

This guide is targeted at software architects who has already been familiar with architecture design and can use this framework as an alternative way to realize the O-R mapping, or developers who is interested Data Access layer design and can get a first view of what the O-R mapping is primarily about.

In order to fully benefit from this guide, potential readers should have a good understanding of the object-oriented technology and relational databases, such as SQL-Server, basic knowledge of OOD (object-oriented design) and Multi-Layered development methodology. The specific technologies mentioned in this article are C#, the .NET Framework, and XML.

Why O-R mapping?

Because of the prevalence and maturity of the relational databases, they are widely used in the database applications. A number of problems, however, arose due to the mismatch between the record-oriented and the object-oriented representations of data. And that’s the main reason why O-R mapping was introduced as a solution.

What Is an O-R mapping anyway?

Before you go any further, you need to get familiar with the following key ideas, and in the sections to come, each of them is to be described in detail. How they can be achieved and how we should employ them in the real-world enterprise applications will also be introduced.

Mapping: There must be some mapping between a class and its persistence store (for example, a table in a database), and between object attributes and the fields (columns) in a record. That is, there must be a schema mapping between the two schemas.

Materialization and dematerialization: Materialization is the act of transforming a non-object representation of data (for example, a record) from a persistence store into an object. Dematerialization is the opposite activity (also known as passivation).

Caches: Persistence services cache materialized objects for performance.

Transaction state of object: It is useful to know the state of an object in terms of its relationship to the current transaction. For example, it is useful to know whether an object has been modified (is dirty) so that it is possible to determine if it need to be saved back to its persistent store.

Transaction operations: Commit and rollback operations.

Lazy materialization: Not all objects are materialized at once; a particular instance is only materialized on-demand, when needed.

Now, you may be able to figure out what an O-R mapping is.
O-R mapping is a mechanism that can map an object to a record in a relational database table and map that object’s attributes to the fields of that record, and unique values are used to relate that object to that record.

Design of the DAP Framework 2.0

To make the most of the DAP Framework, you need to understand the design of it. The DAP Framework was built with C# and designed to be an extensible and reusable package that can be freely deployed in any project developed with the programming languages that conform to Common Language Specification (CLS). The DAP Framework works in Multi-Layered architecture as an O-R mapping framework through which records in database table can be easily mapped to objects, which also provide functionalities of object caching, object states management, distributed transaction, and etc.

How Does an O-R Mapping Work in the DAP Framework?

In spite of knowing what an O-R mapping is, some of you, especially those who are new in this area, may still be puzzled by how it works. What’s the secret behind an O-R mapping framework? To begin, let’s make a comparison between the following two diagrams in figure 1.


Figure 1. Comparison between a RDB table and a class

On the left side is a UML class diagram. Instances of the Project class are objects we work with in Business Logic layer. There are 4 fields that represent the attributes of the BO (Business Object) Project in the real world. On the right side, it is a RDB table diagram. It also has 4 fields which are used to save the attributes of Project objects, e.g. id, name, number and cost. Instances of this table schema are records that hold many projects, so we can see records in RDB table as the persistent format of BOs. Let’s proceed to see what the C# code of Project class looks like.

public class Project
{
    private Guid id;
    private string name;
    private string number;
    private decimal cost;

    public Project(Guid id, string name, string number, decimal cost)
    {
        this.id = id;
        this.name = name;
        this.number = number;
        this.cost = cost;
    }

    public Guid ID
    {
        get
        {
            return this.id;
        }
        set
        {
            this.id = value;
        }
    }

    public string Name
    {
        get
        {
            return this.name;
        }
        set
        {
            this.name = value;
        }
    }

    public string Number
    {
        get
        {
            return this.number;
        }
        set
        {
            this.number = value;
        }
    }

    public decimal Cost
    {
        get
        {
            return this.cost;
        }
        set
        {
            this.cost = value;
        }
    }
}

In this Project class, there are 4 fields and 4 properties each of which corresponds to one field, so we can now map these 4 properties to those 4 fields in that RDB table in figure 2.1.

Ordinarily, there are three ways to implement an O-R mapping framework.

One way is to write some database access methods, such as Select, Save, etc. in Project class itself. This approach is convenient, for Project object has the necessary information for itself to be saved to or retrieved from database. The logical extension of this approach is that each business class has it own methods to access to database. But this leads to problems in cohesion, coupling, and duplication. For example, the Project class must now contains logic related to database handling. This class is no longer focused on just the pure application logic of “being a project”, for it now has other kinds of responsibilities, which lowers its cohesion. The class must be coupled to the database service component, such as ADO.NET, rather than just being coupled to other objects in the domain layer of software objects, which raises its coupling. And it is likely that similar database logic would be duplicated in many persistent classes.

Another way is that we can use another class, such as ProjectORMapper, as the mapper between Project objects and RDB records. In this way, Project class works as an
Information Expert which means an individual has necessary information to fulfill a task. Database handling was removed to a separate class, called O-R mapper class such as ProjectORMapper, where Project objects are selected from and updated to the database. Thus some OOD principles, such as High Cohesion, Low Coupling, are conserved. But the issue of code duplication remains unresolved. Even though we move database handling code into another class, there are still possibilities that duplicated database logic would exist through out many classes, since every business class has its own O-R mapper and each O-R mapper has it own code to access a database.

So we come to the third way. We can use only one O-R mapper and an XML file recording the mete-data of different classes, then this mapper can read the mete-data to map different kinds of objects to records in RDB. In this way, those three OOD issues, cohesion, coupling, and code duplication are resolved. An O-R mapping framework is responsible of mapping different objects to RDB records. The mete-data file is essential to the framework, for it provides the knowledge of how a specific object can be mapped to a RDB record. Actually, with an O-R mapping framework, we don’t have to care about SQL statements and ADO.NET, because all these things are handled by the framework. SQL statements are generated from the mete-data automatically. This third way is what the DAP Framework 2.0 use to implementan O-R mapping framework.

Architecture Overview

Figure 2. illustrates the overall architecture of the DAP Framework.


Figure 2. The DAP Framework 2.0 architecture

There are two parts in that figure. Panes in olivedrab represent the components in the DAP Framework. They are the essential components that work together to realize the mapping between objects and RDB records. What are in turquoise color indicate a configuration file and code that will access this framework. The configuration file is extremely important to the DAP Framework, because it contains the necessary mete-data that the DAP framework will use to map objects to RDB records; and code in BL layer will use this framework to select objects from or update objects to Data layer.

Persistence Facade

Primarily because the DAP Framework is an independent layer in Multi-Layered system architecture, it needs to provide a facade, a common pattern to provide a unified interface to a subsystem, that upper layers can use consistently to access the Data layer underneath.

Figure 3 is the UML diagram of the PersistenceFacade class which works as the facade of the DAP Framework. Its primary responsibilities are retrieving objects from and updating objects to the Data layer. This class is a
Singleton class, which means there is only one instance of this class exists throughout the application domain. You might be confused by the methods in the class diagram, for there are definitely no methods called Insert and Update, how objects can be inserted or updated to the database. Actually there are indeed some methods called Insert and Update, your can not seeing them is just because they are marked as internal. You can not invoke them outside the framework assembly. Every class who wants their instances to be mapped to RDB records must inherits from the PersistentObject class which provides some methods, such as Save or Delete, that will internally invoke the corresponding methods in this PersistenceFacade class. This is the internal logic of the framework, you don't have to care about it while you are using it.


Figure 3. UML diagram of the PersistenceFacade class

Notice that when you use Select method to retrieve object from the data layer, only one object will be returned, even if there are many records in the database are true of the passed conditions, and that one will be the first in the group. By contrast, when you use SelectCollection method, a collection will be retrieved, even though only one record in the database is true of the conditions passed, and it will be the only one element in the returned collection.

O-R Mapper

This component is the exact place where mapping between objects and records is realized. With the helps from other two components, Sql Factory and SqlParameter Factory, it knows how objects can be retrieved from databases and how those objects can be subsequently updated into databases. Reflection is used in this component to dematerialize objects to databases. An application block from Microsoft, called Data Access Block or SqlHelper, is used in this component. It wraps all ADO.NET database commands.

Sql Factory

This component works as the factory that's used to generate SQL statements. For the performance reason, SQL statements generated from this factory will be cached, so no need to generate a new SQL statement if it has already been requested. This component internally calls other factories for getting the mete-data from the configuration file.

SqlParameter Factory

This components is used to create SqlParameter objects which will be passed as parameters to ADO.NET commands. This component will internally call other factories for getting the mete-data from the configuration file.

Config

This component is used to read the mete-data from the configuration file. And at the very beginning, the format of the configuration will be validated against a predefined schema validation file. This is to make sure the format of the configuration is correct, not incomplete. If the validation fails, a meaningful exception will be thrown to inform developers what the problem is. Thus, less codes are needed to prevent the system built on the framework from being cracked down.

Configuration File

There is a heavy use of configuration file in the DAP Framework. The configuration file contains the mete-data that will be used dynamically at run time. Following is a sample configuration file.

<?xml version="1.0" encoding="utf-8"?>
<dapConfiguration>
  <cachePolicies>
    <cachePolicy name="Absolute" cacheExpirationMode="Absolute" cacheExpirationInterval="23:59:59" />
  </cachePolicies>
  <databases>
    <database name="pubs" conString="Initial Catalog=pubs;Data Source=localhost;User ID=sa;Password=pwd"/>
  </databases>
  <types>
    <type name="Author" database="pubs" table="authors" cachePolicy="Absolute">
      <properties>
        <property name="ID" field="id" dbField="au_id" type="String" isPrimary="true" />
        <property name="LastName" field="lastName" dbField="au_lname" type="String" />
        <property name="FirstName" field="firstName" dbField="au_fname" type="String" />
        <property name="Phone" field="phone" dbField="phone" type="String" />
        <property name="Address" field="address" dbField="address" type="String" />
        <property name="City" field="city" dbField="city" type="String" />
        <property name="State" field="state" dbField="state" type="String" />
        <property name="Zip" field="zip" dbField="zip" type="String" />
        <property name="Contract" field="contract" dbField="contract" type="Boolean" />
      </properties>
    </type>
  </types>
</dapConfiguration>

<cachePolicies> that contains one or more child tags, each of which represents a cache policy that will be used to cache objects retrieved from databases. Each <cachePolicy> tag has three attributes. name is used to uniquely represent a cache policy, and a type can be related to a cache policy through this attribute. cacheExpirationMode will be used with cacheExpirationInterval together to indicate how long objects should be cached. There are following examples:

  <cachePolicy name="Absolute" cacheExpirationMode="Absolute" cacheExpirationInterval="23:59:59" />
Cached objects with this cache policy will expire at 23:59:59 every day.
  <cachePolicy name="Sliding" cacheExpirationMode="Sliding" cacheExpirationInterval="20" />
Cached objects with this cache policy will expire in 20 minutes.
  <cachePolicy name="None" cacheExpirationMode="None" />
Cached objects with this cache policy will never expire.

NOTE that if Absolute is used as the cache expiration mode, a time-format value, e.g. 23:59:59, should be assigned to the cache expiration interval attribute. But if Sliding is used as the cache expiration mode, an integer value should be assigned to the cache expiration interval attribute. No need to assign the cache expiration interval attribute if None is used as the cache expiration mode.

<databases> that contains one or more child tags, each of which represents a relational database that will be used. Each <database> tag has two attributes. name is used to uniquely represent a database, and a type can be related to a database through this attribute. conString is used to save the connection string of the database.

<types> that contains one or more child tags, each of which represents a business type or business class. Each <type> tag has four attributes and one child tag. name is used to indicate the name of the type, it is actually the class name. database is used to relate this type to a database specified in the <database> tag. table is used to indicate which table in the specified database is mapped to this type. cachePolicy is used to relate this type to a cache policy specified in the <cachePolicy> tag. This <type> tag has a child tag <properties> which also contains many child tags, each of which represents a property of this type. Each <property> tag can have at most five attributes. name is used to indicate the property name. field is used to indicate the name of the class field that this property will internally access. dbField is used to indicate the name of the field in the specified database table, and this database field is mapped to the class field just mentioned. type is used to indicate the property type, and it is also the type of the class field. This type will be converted to its counterpart type in the database at run time. The last attribute is isPrimary. If the database field just mentioned is the primary key in its table, this attribute will be set to true, otherwise, false.

NOTE that the sequence of the properties MUST be the same as that of the parameters in a constructor of the type.

That is all about the configuration file. Actually, you don't have to code this file manually, SuperType, an O-R mapping tool, can be used to generate this file from database automatically.

PersistenObject

In the DAP Framework, each business class who wants its instances to be mapped to RDB records must inherit from the PersistentObject class which provides many implementations that derived class can use. Figure 4 is the UML diagram of the PersistentObject class.


Figure 4. UML diagram of the PersistentObject class

States Management

The DAP Framework adopts the State pattern and each persistent object has its own state. There are in all five states:

New State which means a persistent object is new, not retrieved from the database.

OldClean State which means a persistent object was retrieved from the database, but without any operation to it.

OldDirty State which means a persistent object was retrieved from the database and has been modified.

OldDelete State which means a persistent object was retrieved from the database and will be deleted.

Deleted State which means a persistent object has already been deleted from the database.

Figure 5 shows the state chart for a persistent object. From the state chart, you may know that the response to an operation depends on the state of the persistent object. If a persistent object is selected from a database, its state is OldClean, and after it’s saved, its state transforms to OldDirty, then it’s committed, at this time, it’s updated to the database, and its state goes back to OldClean. If this persistent object is totally new, nothing will happen if it’s saved, and its state remains the same, then it's committed, at this time, it is inserted into the database, and its state is changed to OldClean.


Figure 5. State chart of a persistent object

In the DAP Framework, a persistent object created explicitly by its constructor has the default state New; and a persistent object retrieved from the database has the default state OldClean.

Overall

The DAP Framework provides developers the ability to clearly separate Data layer and Business Logic layer. Developers have a reasonable and convenient way to retrieve objects from and update objects to the Data layer through the facade of the framework. The framework can work with SuperType, an O-R mapping tool, to develop intricate enterprise application in a record time.

Developing with the DAP Framework 2.0

Feature List of the DAP Framework 2.0

The DAP Framework 2.0 has the following major features.

Materialization and dematerialization of a single object

Materialization and dematerialization of a collection of objects

Paging is enabled

Object state management

Cache is enabled

Distributed transaction

Multi-User access and locking strategies

A Sample Class and a Sample Configuration File

In order to demonstrate the features of the DAP Framework, we need to at first have a sample class and a sample configuration file.

Following is a class Author, and the database table this class mapping to is a sample table called authors in the public database called pubs in the SQL-Server 2000.

using System;
using TypeDev.DAProcess;

public sealed class Author : PersistentObject
{
    private string id;
    private string lastName;
    private string firstName;
    private string phone;
    private string address;
    private string city;
    private string state;
    private string zip;
    private bool contract;

    public Author()
    {
    }

    public Author(string id, string lastName, string firstName, string phone, string address, string city, string state, string zip, bool contract)
    {
        this.id = id;
        this.lastName = lastName;
        this.firstName = firstName;
        this.phone = phone;
        this.address = address;
        this.city = city;
        this.state = state;
        this.zip = zip;
        this.contract = contract;
    }

    public string ID
    {
        get
        {
            return this.id;
        }
    }

    public string LastName
    {
        get
        {
            return this.lastName;
        }
        set
        {
            this.lastName = value;
        }
    }

    public string FirstName
    {
        get
        {
            return this.firstName;
        }
        set
        {
            this.firstName = value;
        }
    }
    
    public string Phone
    {
        get
        {
            return this.phone;
        }
        set
        {
            this.phone = value;
        }
    }

    public string Address
    {
        get
        {
            return this.address;
        }
        set
        {
            this.address = value;
        }
    }

    public string City
    {
        get
        {
            return this.city;
        }
        set
        {
            this.city = value;
        }
    }

    public string State
    {
        get
        {
            return this.state;
        }
        set
        {
            this.state = value;
        }
    }

    public string Zip
    {
        get
        {
            return this.zip;
        }
        set
        {
            this.zip = value;
        }
    }

    public bool Contract
    {
        get
        {
            return this.contract;
        }
        set
        {
            this.contract = value;
        }
    }
}

Following is a configuration file which has already been introduced.

<?xml version="1.0" encoding="utf-8"?>
<dapConfiguration>
  <cachePolicies>
    <cachePolicy name="Absolute" cacheExpirationMode="Absolute" cacheExpirationInterval="23:59:59" />
  </cachePolicies>
  <databases>
    <database name="pubs" conString="Initial Catalog=pubs;Data Source=localhost;User ID=sa;Password=pwd"/>
  </databases>
  <types>
    <type name="Author" database="pubs" table="authors" cachePolicy="Absolute">
      <properties>
        <property name="ID" field="id" dbField="au_id" type="String" isPrimary="true" />
        <property name="LastName" field="lastName" dbField="au_lname" type="String" />
        <property name="FirstName" field="firstName" dbField="au_fname" type="String" />
        <property name="Phone" field="phone" dbField="phone" type="String" />
        <property name="Address" field="address" dbField="address" type="String" />
        <property name="City" field="city" dbField="city" type="String" />
        <property name="State" field="state" dbField="state" type="String" />
        <property name="Zip" field="zip" dbField="zip" type="String" />
        <property name="Contract" field="contract" dbField="contract" type="Boolean" />
      </properties>
    </type>
  </types>
</dapConfiguration>

NOTE that if the application you are building is a Windows application, this configuration file should be placed into the folder where the EXE file is; if you are building an ASP.NET Web application, this configuration file should be placed into the root folder of the Web application, and its extension should changed to config to prevent it from being access through client browsers. Also, the name of this configuration file should be configured in the application configuration file, app.config or web.config, as the following way:

<configuration>
  ...
  <appSettings>
    ...
    <add key="ConfigFileName" value="configFileName.config" />
    ...
  </appSettings>
  ...
</configuration>

Materialization and Dematerialization of a Single Object

The following code snippet first materializes an Author instance based on the property ID and its value passed to the database, then the first name of this author is changed to “Aaron”, and at last, this author is dematerialized to the database.

TypeParameter param = new TypeParameter(typeof(Author), "ID", "172-32-1176");
Author author = PersistenceFacade.Instance.Select(param) as Author;
if(author != null)
{
     author.FirstName = "Aaron";
     author.Save();
     author.Commit();
}

NOTE that if there are more than one records in the database are true of the passed condition, only the first one will be converted into an object and returned.

Materialization and Dematerialization of a Collection of Objects

The following code is used to materialize a collection of Author instances. All authors whose first name contains the letter “a” will be returned. Subsequently their phone numbers are changed, and then they are dematerialized to the database.

TypeParameter param = new TypeParameter(typeof(Author), "FirstName", "%a%", Connector.Like);
ArrayList authors = PersistenceFacade.Instance.SelectCollection(param);
foreach(Author author in authors)
{
     author.Phone = "13366676543";
     author.Save();
     author.Commit();
}

NOTE that if there is no record in the database is true of the passed condition, the returned ArrayList will contain no author, but the list itself has already been instantiated.

The folowing code demonstrates how to retrieve those authors whose first name contains the letter "a", and also they must live in the state CA.

TypeParameter param = new TypeParameter(typeof(Author));
param.PropertyParams.Add(new PropertyParameter("FirstName", "%a%", Connector.Like));
param.PropertyParams.Add(new PropertyParameter("State", "CA"));
ArrayList authors = PersistenceFacade.Instance.SelectCollection(param);
foreach(Author author in authors)
{
     //Code here to operate every author.
}

The folowing code demonstrates how to retrieve those authors whose first name or last name contains letter "a".

TypeParameter param = new TypeParameter(typeof(Author));
param.PropertyParams.Add(new PropertyParameter("FirstName", "%a%", Connector.Like));
param.PropertyParams.Add(new PropertyParameter("LastName", "%a%", Connector.Like));
param.IsOr = true;
ArrayList authors = PersistenceFacade.Instance.SelectCollection(param);
foreach(Author author in authors)
{
     //Code here to operate every author.
}

Paging Is Enabled

Paging is enabled in the DAP Framework 2.0. The following code demonstrate how to separately retrieve the first ten authors and the second ten authors.

TypeParameter param = new TypeParameter(typeof(Author));
param.PropertyParams.Add(new PropertyParameter("FirstName", "%a%", Connector.Like));
param.PropertyParams.Add(new PropertyParameter("LastName", "%a%", Connector.Like));
param.IsOr = true;
param.PageEnabled = true;
param.CountInPage = 10;

//The first ten authors are retrieved. 
param.PageIndex = 0;
ArrayList authors = PersistenceFacade.Instance.SelectCollection(param);
foreach(Author author in authors)
{
     //Code here to operate every author.
}

//The second ten authors are retrieved. 
param.PageIndex = 1;
authors = PersistenceFacade.Instance.SelectCollection(param);
foreach(Author author in authors)
{
     //Code here to operate every author.
}

Object State Management

In the following code, there are two authors, author1 and author2. One is constructed explicitly using the constructor, and the other is retrieved from the database. When both methods, Save and Commit, are invoked on these two object, the first one, author1, is inserted to the database, the second one, author2 however, is updated into the database. How could that happened? I mean how the framework is able to know which object should be inserted into the database and which object should be updated to the database. That's because these two Author objects have their states respectively. The Author object instantiated using the constructor has the default state New, and the Author object retrieved from the database has the default state OldClean, so the framework knows how to separately handle these two Author objects.

Author author1 = new Author("888-88-8888", "Aaron", "Lau", "13366", "Beijing", "Beijing", "BJ", "10000", true);
author1.Save();
author1.Commit();

TypeParameter param = new TypeParameter(typeof(Author), "ID", "172-32-1176");
Author author2 = PersistenceFacade.Instance.Select(param) as Author;
if(author != null)
{
     author2.FirstName = "Aaron";
     author2.Save();
     author2.Commit();
}

The following code shows that an Author object is retrieved from the database, and it is subsequently committed without any operation on that object. In this case, that object is not updated to the database, actually, no database access happens. Because the framework knows that the object's state is OldClean, no operation happened on it. For more information about the state management, please review the figure 5 State chart of a persistent object.

TypeParameter param = new TypeParameter(typeof(Author), "ID", "172-32-1176");
Author author = PersistenceFacade.Instance.Select(param) as Author;
if(author != null)
{
     author.Commit();
}

Cache Is Enabled

Objects retrieved from the database can be cached by the framework automatically. If some kind of objects are requested frequently, they can be cached to promote system performance. In order to cache objects of a specified type, you should configured that type in the configuration file. Take the Author type for example. Looking at the Author tag in the sample configuration file, you can see that an attribute called cachePolicy has the value Absolute, the name of a cache policy, and that cache policy, configured in the <cachePolicy> tag, tells that cached objects with this cache policy will expire at 23:59:59 every day.

Looking at the following code snippet, if objects of Author type are configured to be cached, that equal boolean value will finally be true, whichh means the object author1 has a reference equality with one Author object in the collection that is retrieved at the second time with the same query parameter.

TypeParameter param = new TypeParameter(typeof(Author), "FirstName", "%a%", Connector.Like);
Author author1 = PersistenceFacade.Instance.Select(param) as Author;
ArrayList authors = PersistenceFacade.Instance.SelectCollection(param);
bool equal = false;
foreach(Author author in authors)
{
    if(object.ReferenceEquals(author1, author))
    {
         equal = true;
    }
}

The same result can be illustrated by the following code, which means that if an author is requested several times, the same cached author will always be returned no matter which query parameter is used.

TypeParameter param1 = new TypeParameter(typeof(Author), "FirstName", "%a%", Connector.Like);
Author author1 = PersistenceFacade.Instance.Select(param1) as Author;
TypeParameter param2 = new TypeParameter(typeof(Author), "ID", author1.ID);
Author author2 = PersistenceFacade.Instance.Select(param2) as Author;
bool equal = object.ReferenceEquals(author1, author2);

Distributed Transaction

If transaction were not supported by an O-R mapping framework, developers using it would finally come to a place where something is a mission impossible. Fortunately, distributed transaction is realized in the DAP Framework 2.0 in a reasonable and seemingless way.

Suppose that we had another type called Book. The following code shows how an Author object and a Book object are inserted into the database in a transaction.

Author author = new Author("888-88-8888", "Aaron", "Lau", "13366", "Beijing", "Beijing", "BJ", "10000", true);
Book book = new Book("bookName");
author.Save();
book.Save();
Transaction trans = new Transaction();
trans.AddObject(author);
trans.AddObject(book);
try
{
     trans.Commit();
}
catch
{
     trans.Rollback();
     throw;
}
finally
{
     trans.Dispose();
}

NOTE that the sequence that objects are added into the transaction is the sequence that those objects are going to be committed into databases, so objects MUST be added into the transaction in a reasonable sequence to prevent exceptions from being thrown.

Multi-User Access and Locking Strategies

If the DAP Framework 2.0 is being used in an application that is running on a server, there might be the cases when many users will access this framework simultaneously. This concern was considered the first time this framework was designed. For example, there can be only one transaction being executed at a certain time while the framework is running to prevent databases from being locked; if objects of a specified type are configured to be cached, selecting those objects will be synchronized to prevent duplicated objects from being instantiated.

Pet Shop Built on the DAP Framework 2.0

The .NET Pet Shop was originally built by Microsoft as a sample application for .NET developers, and therefore many developers are so familiar with it, some of them even looked into the code of it. This is the main reason why it's been chosen as the sample application built on the DAP Framework. Actually only two days were used to rebuild this application, partially because that the UI and database remain changed, and partially because it is so straightforward to build database applications on the DAP Framework. In this newly created Pet Shop, new Data Access layer, Business Logic layer and controller were used, and almost all features the framework has have been used in it. I personally think that the new Business Logic layer is more reasonable than that of the original one, for it presents clearer associations among different classes. Figure 6 shows the architecture of this new Pet Shop.


Figure 6. Pet Shop architecture

UI and Data layers remain unchanged. In UI layer, there exist many ASP.NET web forms and some user and custom controls. And Data layer is the SQL-Server 2000 database.

Controller is a class with only static methods which are used to communicate between UI layer and Business Logic layer. Users log in the system through this controller; account, product and order information are saved and retrieved through this controller; session and cache state are maintained through this controller.

Business Logic layer mainly involves business objects and associations among them. It is the place where objects work together to get their job done.

Data Access layer is where the DAP Framework is. It’s used to retrieve objects from or update objects to the Data layer underneath.

Deployment and Operations

Software Requirements

This framework package has been applied in several real-world projects, and it has been fully tested in the following software environment, so you need to make sure that your system meets the following minimum software requirements:

Microsoft Windows 2000 / XP / 2003.

Microsoft .NET Framework version 1.1.

Microsoft SQL-Server 2000.

Microsoft Application Data Block 2.0.

Database Restriction

Database tables should not have fields that are auto-incremental, all fields must be inserted explicitly.

Conclusion

A major advantage of adopting an O-R mapping framework is that it can minimize the complexity of systems and thus help developers build real OO applications. Developers don’t have to care about how to write SQL statements, they can even have no knowledge of what ADO.NET is, they primarily work on how objects can work together to get something done.

1.The Information Expert is a pattern which likes many things in object technology has a real-world analogy. We commonly give responsibility to individuals who have the information necessary to fulfill a task. For example, in a business, who should be responsible for creating a profit-and-loss statement? The person who has access to all the information necessary to create it ─ perhaps the chief financial officer. And just as software objects collaborate because the information is spread around, so it is with people. The company's chief financial officer may ask accountants to generate reports on credits and debits.

2.Practically, there are three principles with OOD. They are High Cohesion, Low Coupling and Protected Variations. More information about these principles can be found from Applying UML and Patterns, an excellent book by Craig Larman.

3.Singleton is one of the GOF patterns, more information can be found from Design Patterns Elements of Reusable Object-Oriented Software.

4.State is one of the GOF patterns, more information can be found from Design Patterns Elements of Reusable Object-Oriented Software.

阅读全文
0 0

相关文章推荐

img
取 消
img