It is an example of a many-to-many relationship and contrasts to the to-many relationship between orders and customers an order belongs to a single customer, but acustomer can create mul
Trang 1Data Access Layer
Silverlight and some WPF applications do not use offline storage for persisting data Instead, they use a relational database to store the object data in a structured format Rather than employing serialization, applications that use a relational database for storage will have a dedicated Data Access Layer (DAL) that serves to insulate the model from additional responsibility while allowing clients to load and save the
model state
There are four typical operations that the DAL should provide for each object in the model: create, read, update, and delete (often given the unfortunate acronym CRUD) These operations correspond to inserting new records in the database, retrieving records from the database, editing existing records, and removing records entirely from the database, respectively Clients can combine these simple operations
to fulfill all of the requirements necessary to interact with application data
DALs can be implemented in many different ways, from simplistic wrappers around SQL statements
to complex modules that require maintenance in their own right Explaining how to fit the latter into an MVVM application is the focus of this chapter
Object-Relational Dichotomy
At a coarse-grained level, the NET Framework uses classes that interact with each other in order to solve
a particular problem These classes are a mixture of data, which comprise the state of an object, and
method implementations, which transform the data in some meaningful way Relational database
management systems (RDBMS), on the other hand, consist solely of data that has no associated
behavior—absent stored procedures, of course An RDBMS solves a very specific problem that does not concern objects That is, it does not concern them until one tries to convert from one to the other (from objects to database tables or vice versa)
Trang 2DATABASE KEYS
Database tables should always have some sort of key that uniquely identifies each record and
distinguishes it from other records in the same table There are also different kinds of keys Natural keys
are composed of data that is a real-world property of the entity being mapped The natural key for a person could be his name, but this precludes multiple people being stored who share the same name Natural
keys can be hard to determine without resorting to a composite key, which is a key made up of more than one property that, when combined, uniquely identifies an entity For a person, his name and date of birth
could be combined to form a natural composite key, but even this is dissatisfying because it prevents
the—rare but plausible—case of two people sharing both name and birth date For this reason, rather
than struggling to force a natural key on an entity, a surrogate key is often used This is a value that does
not relate to the entity directly and is contrived to satisfy the requirement for uniqueness Typically, the
surrogate key is an integer that auto-increments with each additional record added to the table, or a
Universally Unique Identifier (UUID), which is a 32-byte (128–bit) value that is virtually guaranteed to be
unique (there are roughly 3.4 1038 possible values) Object-Relational Mapping (ORM) libraries, as
discussed in this chapter, typically recommend surrogate keys for simplicity
In an RDBMS, data is stored in tables, which are two-dimensional data structures where each
column applies to an atomic datum and each row is a full record of a single entry in the table Table 8–1 shows a trivial database design that contains products, customers, and orders aggregated into one
monolithic table
Table 8–1 A Monolithic—Unnormalized—Database Table
Product Name Product Price Customer Name Customer Address Order Number Order Date
XBox 360 100.00 Gary Hall Sommershire, England XX217P11D 08/02/2010
HP Probook 500.00 Gary Hall Sommershire, England XX217P11D 08/02/2010
40.00 A Reader Theirton, Utherplace IJ876Q98X 10/02/2010
This table poses a number of problems that render it so difficult and potentially problematic to use that it is almost useless The most egregious problem is that there is so much repeated data If the name
of this book or its price were to change, the database would require changes in two different places
Similarly, if I changed my name or address, the database would have to be changed wherever this data occurred The repetition is unnecessary and causes a maintenance headache, at best Also, how is the correct “A Reader” found without using both their name and address? As it stands, there is no single distinguishing column that allows differentiation between the two customers, so their whole data record must be used to ensure uniqueness
Normalization is the process of refactoring a database so that it conforms to a structure more
suitable for querying It frees the database of anomalies that negatively affect the integrity of the data
Trang 3There are many different levels of normalization, called normal forms (or NF), which are assigned an
ascending index to indicate the rigor of the normalization Although normalization can progress to 6th
Normal Form (6NF), 3rd Normal Form is usually considered sufficient because, by this point, the tables are quite likely to be free of anomalies When the database table from Table 8–1 is normalized, the result
is what is shown in Table 8–2
Table 8–2 Table 8–1 Normalized to 3NF
WPFMVVM Pro WPF and Silverlight MVVM 40.00
XX217P11D 08/02/2010 1
IJ094A73N 11/10/2010 2
IJ876Q98X 10/02/2010 3
There are now three tables: Products, Customers, and Orders The products and customers have
been assigned identity columns that uniquely differentiate each record as being distinct However, there
is a table missing that links each order to the products that the customer purchased For this, an
OrderLine table is required, as shown in Table 8–3
Table 8–3 The OrderLine table
XX217P11D HPP
Trang 4This table allows an order to contain multiple products while simultaneously allowing each product
to be used in different orders It is an example of a many-to-many relationship and contrasts to the to-many relationship between orders and customers (an order belongs to a single customer, but acustomer can create multiple orders)
one-Now, imagine that there are corresponding Product, Order, and Customer classes, each of whichcontains the same data as is here in the database tables However, there are also operations on theclasses that allow collaborations between the object instances Listing 8–1 is an example implementation
of such classes
Listing 8–1 A Sample Implementation of the Product, Order, and Customer Classes
public class Product
Trang 5Notice that there is no OrderLine class; it is simply implied in the relationship between Order and
Product This is a key signifier for how different the two paradigms are and the difficulty that is inherent
in reconciling the object-relational dichotomy
Trang 6Mapping Object Relationships
There are many different relationship types that can exist between objects Objects can be compositions
of other objects, whereby the container and the contents are both intrinsically linked If the containing class is destroyed, then all of the contained objects are also destroyed The UML diagram in Figure 8–1 shows the Customer class is composed of Orders
Figure 8–1 The Customer class has a composition relationship with the Order class
If a specific Customer instance is destroyed, all of the Order instances that it contains should also be destroyed The relationship between Order and Product is one of aggregation, which does not link the lifetimes of the two objects so strictly Instead, the two can be created and destroyed independently, as shown in Figure 8–2
Figure 8–2 The Order class has an aggregation relationship with the Product class
This makes sense because each Product is not wedded to a specific Order If the relationship was composition, deleting an Order would consequently delete all of the Products in that Order, so no-one could subsequently purchase any more of that Product!
In the RDBMS, both of these relationships would be one-to-many, with the primary key of the Customer table referenced as a foreign key within the Order table The difference would be that the composition relationship would require the ON CASCADE DELETE option when creating the Order table The contained objects in composition and aggregation need not be collections Suppose the Customer class was refactored so that the customer’s address was placed into its own Address class The Address would only ever be referenced by the one Customer object, and the Customer object would only have one address This, then, is a one-to-one composition relationship, as shown in Figure 8–3
Figure 8–3 The Customer class has a composition relationship with the Address class, but it is one-to-one
There are two ways to handle this relationship in the RDBMS The first option is to ignore that it is one-to-one and decide which side seems most reasonable for multiplicity In this instance, it makes more sense for customers to be allowed to store multiple addresses—in case the shipping address differs from the billing address, for example—rather than pander to the small demographic of customers who
Trang 7live at the same address From this point, the tables can have a foreign key relationship to satisfy the new multiplicity: Address would be handed the Customer ID field as a foreign key The other option would be
to flatten the relationship out on the RDBMS side because it serves no purpose, leaving all of the address data in the Customer table The objects may be retrieved with a composition relationship, but the
database itself ignores that and stores the data in a single table
The final object relationship occurs when two objects have a many-to-many relationship, as shown
in Figure 8–4
Figure 8–4 The Product and Order classes have a many-to-many relationship
Orders can contain many Products, and Products can belong to many Orders In fact, the multiplicity
1 * indicates that an Order must have at least one Product, whereas 0 * implies that Products can exist wholly independent of Orders In this scenario, an association table is required, which links the Order
and Product tables together This is necessary, because a many-to-many relationship cannot exist in an RDBMS and must be factored out into two one-to-many relationships instead The OrderLine table from Table 8–3 is exactly the association table that is required in this case
Mapping Class Hierarchies
Although it is advisable to look first at the possibility of composing objects through a “has-a”
relationship, object-oriented programming provides the facility for deriving one class from another to form an “is-a” relationship With a sufficiently complex domain model, class hierarchies can form that succinctly solve the problem at hand Mapping this to a relational model is certainly achievable, but
there are three options to choose from that each present themselves as appropriate under varying
circumstances
To make these examples clearer, I am going to alter the design of the e-commerce system to include
a class hierarchy Customer seems like a good option with two subclasses inheriting from a base class (see Figure 8–5)
Figure 8–5 The Customer class refactored to distinguish between different types of User
Trang 8The common data (the Customer’s Name, EmailAddress, and Password) have been extracted and
placed into a superclass called User, which is marked as abstract and cannot be instantiated in its own right The Customer class now only contains the Orders list and inherits the other data from the User A second subclass has been created to represent administrators for the application Administrators also have a Name, EmailAddress, and Password, but they have a PermissionsToken, which would determine
what level of administrative privileges the administrator possesses, and a list of AssignedDefects, which are bugs that have been handed to them for investigation and fixing Now that the inheritance hierarchy
is in place, the mapping options can be examined
Table-Per-Class-Hierarchy
The easiest option, from an implementation standpoint, is to flatten the whole hierarchy down and
create a single table that incorporates all of the data (see Table 8–4) While the User data will
undoubtedly be shared and not repeated, each record will contain redundant data depending on
whether it applies to a Customer or an Administrator This can affect data integrity, too, because the
AssignedDefects and Orders fields are collections that hold a one-to-many relationship with their
respective object types The tables that contain the data for these fields are not prevented from
referencing a row in the User table of either Customer or Administrator type This could leave Customers with AssignedDefects and Administrators with Orders, which is clearly erroneous
Table 8–4 The User Class Hierarchy Implemented as a Single Table
1 Gary Hall gary.hall@apress.com 112ADP33X NULL 76
2 Joe Bloggs Joe.Bloggs@hotmail.co.uk 938BCX82L 45 NULL
3 John Doe John_Doe@hotmail.com 364PZI32H 33 NULL
Table-Per-Concrete-Class
Each concrete class—that is, each non-abstract class—has its own corresponding table The database does not recognize that there is shared data between the different subclasses In Table 8–5, the User
abstract class is ignored, and the two tables replicate the Name, EmailAddress, and Password fields, which
is somewhat redundant However, the Order and Defect tables will correctly reference the separated Customer and Administrator, respectively, with no possibility of a mix-up As long as a user cannot be both a Customer and an Administrator, the redundancy is not too much of a problem
Trang 9Table 8–5 The User Class Hierarchy Implemented with a Table for Each Subclass
1 Joe Bloggs Joe.Bloggs@hotmail.co.uk 938BCX82L 45
2 John Doe John_Doe@hotmail.com 364PZI32H 33
1 Gary Hall gary.hall@apress.com 112ADP33X 76
Table-Per-Class
The final alternative is to include the abstract base class and allow it a table of its own The subclass
tables then hold a foreign key reference to the appropriate User, so the shared data is given one single
schema in the database (see Table 8–6) While the implementation of this method is a little more
complicated, the database is properly normalized The application could be extended to allow a User to
be simultaneously a Customer and an Administrator without any alterations to the database structure
Table 8–6 The User Class Hierarchy Implemented with a Table for Each Concrete Class
1 Gary Hall gary.hall@apress.com 112ADP33X
2 Joe Bloggs Joe.Bloggs@hotmail.co.uk 938BCX82L
AdministratorID UserID PermissionsToken
Trang 10DAL Implementations
So far, there is a domain model of a simple e-commerce system and a corresponding database structure What is left is to bridge the gap with the code that will perform the CRUD operations required by the clients of the model—most likely to be the ViewModel The objective of the DAL code is to protect the object schema from changes to the database schema This requires a type of mediator that will isolate the object model from the domain model while allowing clients to transfer data to and from each schema
The DataMapper pattern [PoEAA ,Fowler] is employed throughout the DAL to keep model classes and the database independent of each other and, more important, independent of the DataMapper itself (see Figure 8–6) Only the clients of the DataMapper are aware of its existence and use it to interact with the RDBMS
Figure 8–6 The DataMapper pattern: A UserMapper acts as the mediator between the RDBMS and User
class
A big decision must be made whether to implement the DAL manually, which allows a greater degree of control over the code, or whether to integrate a third-party library, which will generate the mapping layer automatically Although the latter will undoubtedly be quicker to implement, assimilating
a library into a project remains a non-trivial task that requires significant developer resource There are enough Object Relational Mapping (ORM) libraries available—at no cost—that using an existing
solution should be the default position However, guidance for manually implementing the DAL is provided to give background on the internal workings of an ORM Then, the current most popular third-party ORM libraries are briefly compared and contrasted
Manual Implementation
Before undertaking the monumental task of manually implementing a mapping layer, consider whether the resources required are worth the gains in control Reinvention of the wheel should be avoided if possible, especially when there are so many compelling ORM libraries available The DAL fits into the category of application infrastructure—any effort expended building infrastructure is effort diverted from adding true value to a project Caveats aside, it is still beneficial to understand what goes into a mapping layer because there is a lot of noise generated by third-party components that must remain domain agnostic so that it remains all things to all people, which is not an easy task
Listing 8–2 shows the interface of the UserMapper class that will be used by the ViewModel to interact with the underlying data storage mechanism The implementation will follow: method-by-method For now, notice that there are four methods for finding Users, two of which return a unique User when given their ID and EmailAddress fields, respectively, two of which return lists of Users The FindByName method accepts a partial name and, because User Names are not unique, multiple User instances may be returned The FindAll method, on the other hand, returns every user in the database without discrimination
Trang 11There is also a single method each for the remaining create, update, and delete aspects of the CRUD
acronym, each accepting an existing User instance to act upon
Listing 8–2 The Interface for the UserMapper Class
public class UserMapper
{
public User FindByID(int id)
public User FindByEmailAddress(string emailAddress)
public IEnumerable<User> FindByName(string name)
public IEnumerable<User> FindAll()
public void Update(User user)
public int Insert(User user)
public void Delete(User user)
}
To implement this class succinctly, some helper methods will be required to avoid needless
repetition of database access code (see Listing 8–3) The NET Framework provides the ADO.NET
services for accessing databases without tying code to a specific vendor The class will require a database Connection, which (for now) will be opened and closed inside each method, making each action atomic
A Command will be executed on the Connection, instructing the data store to return the requested data,
insert new records, and update or remove existing records The DataReader class will be examined to
retrieve the correct data into newly instantiated User objects in each of the Find* methods
Listing 8–3 Database Interaction Helper Methods
public UserMapper(string connectionString)
{
_connectionString = connectionString;
}
private string _connectionString;
private IDbConnection _databaseConnection;
private bool ConnectToDatabase(string connectionString)
{
bool success = false;
_databaseConnection = new SqlConnection(connectionString);