Tải bản đầy đủ (.pdf) (22 trang)

Data Access Layer

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (644.92 KB, 22 trang )

C H A P T E R 8

■ ■ ■
163
Data 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).
CHAPTER 8 ■ DATA ACCESS LAYER
164
DATABASE 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

10
38
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
Pro WPF and
Silverlight
MVVM
40.00
A. Reader Yorton, Sumplace IJ094A73N 11/10/2010
Pro WPF and

Silverlight
MVVM
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.
CHAPTER 8 ■ DATA ACCESS LAYER
165
There 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 6
th

Normal Form (6NF), 3
rd
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
Product Code Product Name Product Price
X360 XBox 360 100.00
HPP HP Probook 500.00
WPFMVVM Pro WPF and Silverlight MVVM 40.00


Customer ID Customer Name Customer Address
1 Gary Hall Sommershire, England
2 A. Reader Yorton, Sumplace
3
A. Reader Theirton, Utherplace

Order Number Order Date Customer ID
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
Order Number Product Code
XX217P11D X360
XX217P11D HPP
IJ094A73N WPFMVVM
IJ876Q98X WPFMVVM
CHAPTER 8

DATA ACCESS LAYER
166
This 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 one-
to-many relationship between orders and customers (an order belongs to a single customer, but a
customer can create multiple orders).
Now, imagine that there are corresponding

Product
,
Order
, and
Customer
classes, each of which
contains the same data as is here in the database tables. However, there are also operations on the
classes 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
{
public Product(string name, decimal price)
{
Name = name;
Price = price;
}
public string Name
{
get;
private set;
}
public decimal Price
{
get;
private set;
}
}
public class Customer

{
public Customer(int id, string name)
{
ID = id;
Name = name;
_orders = new List<Order>();
}
public int ID
{
get;
private set;
}
public string Name
{
get;
private set;
}
public IEnumerable<Order> Orders
CHAPTER 8 ■ DATA ACCESS LAYER
167
{
get { return _orders; }
}

public void AddOrder(Order order)
{
_orders.Add(order);
}

private ICollection<Order> _orders;

}

public class Order
{
public Order(string code, Customer customer)
{
Code = code;
Customer = customer;
OrderLines = new List<Product>();
}

public string Code
{
get;
private set;
}

public Customer Customer
{
get;
private set;
}

public IEnumerable<Product> OrderLines
{
get;
private set;
}

public void AddOrderLine(Product product)

{
_orderLines.Add(product);
}

private ICollection<Product> _orderLines;
}
Notice 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.
CHAPTER 8 ■ DATA ACCESS LAYER
168
Mapping 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
CHAPTER 8 ■ DATA ACCESS LAYER
169
live 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
CHAPTER 8 ■ DATA ACCESS LAYER
170
The 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
UserID Name EmailAddress Password BillingAddressID PermissionsToken

1 Gary Hall 112ADP33X NULL 76
2 Joe Bloggs 938BCX82L 45 NULL
3
John Doe 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.

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×