Tải bản đầy đủ - 0 (trang)
Optimistic vs. Pessimistic Locking and the DataSet

Optimistic vs. Pessimistic Locking and the DataSet

Tải bản đầy đủ - 0trang

contention results in deadlock in your application. You have to understand how

much time and resources you can spend reconciling divergent operations, and

how much tolerance for inconsistent or incorrect results your application can

stand, in order to decide under what circumstances you want to avoid deadlock at

all costs, or can deal with the consequences of conflicting operations. [13]


Tim Ewald's book is worth reading to understand this topic. Philip

Bernstein and Eric Newcomer's Principles of Transaction Processing is

another good reference.

You might have to use the DataSet with additional logic to test whether the

records in the DataSet have been changed since the last time they were fetched or

modified. Or you might just decide to use the SqlDataReader and refetch the

data. It all depends.

For example, when making a reservation in our HotelBroker case study you

cannot make an optimistic assumption about the availability of rooms. It is not

acceptable to assume an infinite supply of rooms at a hotel and let the

reservations clerk deal with what happens when more people show up then there

are rooms for. [14] We use the MakeReservation stored procedure to check on

the availability of a room before we make the reservation. [15]


Of course, airlines and hotels overbook. This is a conscious

strategy to deal with passengers or guests not making explicit

cancellations, not a database concurrency strategy.


In fact, the transaction in MakeReservation includes the checking

of the availability of the room as well as the actual making of the

reservation in order to maintain consistency. It also breaks up what

could be one multiple table join into several queries in order to return

better error information.

Sometimes, even without concurrency issues, the DataSet cannot be used to add

new rows in isolation from the database. Sometimes, as in our HotelBroker

application, an arbitrary primary key cannot be used. [16] Many users will be

making reservations at the same time. Reservation ids cannot be assigned locally;

some central logic on the database has to be employed to issue them. [17] The

MakeReservation stored procedure does this as well.


For instance, a GUID. Well, theoretically GUIDs could be used in

our case, but when was the last time you got a reservation number

from a hotel or airline that was composed of 32 identifiers? Many times

a primary key has meaning to an organization—for example, a part

number whose subsections indicate various categories.


Of course, if performance were critical, instances of the HotelBroker

could be preassigned ranges of reservation ids to give out. But this

would have to be done by some central authority as well (the database,

some singleton object?). But then this raises the issue of state

management in the middle tier. This just reinforces my previous point

about the dependency of any solution on the specific requirements of

your program. It also reinforces the maxim that any programming

problem can be solved either by trading memory against time, or

adding another level of indirection.

The degree of disconnected operation that your application can tolerate has to be

understood before you can decide how to use SqlDataReader or the DataSet in

your applications.

Why bother to use the DataSet at all in our HotelBroker application? In fact, the

code for the Customer object does not use the DataSet at all. The HotelBroker

object does—for two reasons. The first is pedagogical. We wanted to show you

how a complete application might use the features of the DataSet, rather than just

isolated sample programs. Second, in the Web version of the application which is

developed in subsequent chapters, it is convenient to cache certain pieces of

information. For example, it is probably reasonable to assume that a user can

work with their own local copy of reservations. On the other hand, the

information about a customer such as their email address can be obtained just

once when they log in. There is no need for an elaborate mechanism to cache

customer information, so the Customer object uses methods on the

SqlCommand object.

Working with DataSets

Figure 9-5 depicts the hierarchy of classes that exist within the DataSet class. It will be

helpful to glance at this diagram over the next few sections that discuss these classes.

Figure 9-5. DataSet class hierarchy.

Multiple Tables in a DataSet

Each DataSet has a collection of one or more DataTable objects. Each DataTable object

represents one table.

With a SelectCommand that contains a join you can place data from multiple database tables

into one DataTable. If you want to update the multiple tables, you will have to specify the

update commands, because all relationship of the data to the original tables is lost. The

SqlDataAdapter for the HotelBroker object has the following SelectCommand property:

string cmd = "select CustomerId, HotelName, City,

ArrivalDate, DepartureDate, ReservationId

from Reservations, Hotels where

Reservations.HotelId = Hotels.HotelId";

adapter.SelectCommand = new SqlCommand(cmd, conn);

dataset = new DataSet();

adapter.Fill(dataset, "Reservations");

The DataSet will only have one DataTable called Reservations. The fact that some of the

data came from the Hotels table is lost.

You can also load more than one table into a dataset. The DataSchema example does just


adapter.SelectCommand = new SqlCommand(

"select * from [Order Details] where ProductId = 1",


adapter.FillSchema(dataset, SchemaType.Source,

"Order Details");

adapter.Fill(dataset, "Order Details");

adapter.SelectCommand = new SqlCommand("select * from

Shippers", conn);

adapter.FillSchema(dataset, SchemaType.Source,


adapter.Fill(dataset, "Shippers");

There will be two tables, OrderDetails and Shippers, in the DataSet. The method

SqlDataAdapter.FillSchema fills the DataSet with the primary key information associated

with the tables. The code can now iterate through the tables and print out both the data and the

primary keys of the tables. The Columns collection on the DataTable enables you to find the

DataColumns for the DataTable.

foreach(DataTable t in dataset.Tables)



DataColumn[] dc = t.PrimaryKey;

for (int i = 0; i < dc.Length; i++)


Console.WriteLine("\tPrimary Key Field {0} = {1}", i,




foreach(DataColumn c in t.Columns)

Console.Write("{0, -20}", c.ColumnName);


foreach(DataRow r in t.Rows)



foreach(DataColumn c in t.Columns)

Console.Write("{0, -20}", r[c].ToString().Trim());




The example output shows the tables, primary keys, columns, and data:

Order Details

Primary Key Field 0 = OrderID

Primary Key Field 1 = ProductID


ProductID UnitPrice Quantity Discount













Primary Key Field 0 = ShipperID





Speedy Express

(503) 555-9831


United Package

(503) 555-3199


Federal Shipping

(503) 555-9931

Table Creation without a Data Source

One can use a DataSet as a memory-resident relational database not based on any database. In

fact, we will explore various features of the DataSet in the DataEditing example by adding

the data and relationships directly to the data set without extracting them from a database.

First we create a new DataSet and turn on constraint checking. We then add four DataTables

to the DataSet: Books, Categories, Authors, and BookCategories. Even though it is set in the

example code for pedagogical reasons, EnforceConstraints by default is true.

DataSet ds = new DataSet();

ds.EnforceConstraints = true;





categories = ds.Tables.Add("Categories");

bookcategories = ds.Tables.Add("BookCategories");

authors = ds.Tables.Add("Authors");

books = ds.Tables.Add("Books");

Each DataTable object has a collection of DataColumn objects. Each object represents one

column of the table. We then add columns to the table definition.

Type stringType = System.Type.GetType("System.String");

Type intType = System.Type.GetType("System.Int32");

DataColumn categoryname =


DataColumn cn = bookcategories.Columns.Add

("CategoryName", stringType);

DataColumn loc =


"LibraryofCongressNumber", stringType);

DataColumn auid = authors.Columns.Add("AuthorId",


authors.Columns.Add("AuthorLastName", stringType);

authors.Columns.Add("AuthorFirstName", stringType);

DataColumn ISBN = books.Columns.Add("ISBN", stringType);

DataColumn booksauid = books.Columns.Add("AuthorId",


books.Columns.Add("Title", stringType);

DataColumn bloc =

books.Columns.Add("LibraryofCongressNumber", stringType);

Constraints and Relations

Each DataTable object has a collection of DataRow objects. Each object represents one row

of the table. When you add a DataRow, it is subject to the constraints on the DataTable

objects (assuming the DataSet's EnforceConstraints property has been set to true).

Primary Keys

There are several constraints on a table. The Primary Key constraint is the unique identifier

for the table. Other unique constraints force the values in various column(s) to which they are

applied to be unique. A Foreign Key constraint forces the values in the column(s) to which it

applies to be a primary key in another table in the DataSet.

The DataTable's primary key is a property:

DataColumn[] bookcategoriesPK = new DataColumn[2];

bookcategoriesPK[0] = cn;

bookcategoriesPK[1] = loc;

bookcategories.PrimaryKey = bookcategoriesPK;

DataColumn[] authorsPK = new DataColumn[1];

authorsPK[0] = auid;

authors.PrimaryKey = authorsPK;

DataColumn[] booksPK = new DataColumn[1];

booksprimarykey[0] = ISBN;

books.PrimaryKey = booksPK;


The other constraints on the Table are represented by the abstract base class Constraint and

its derived classes: UniqueConstraint and ForeignKeyConstraint. The base class enables

the constraints to be placed in the table's constraint collection. Primary Keys also appear in the

table's constraint collection as a unique constraint with a system-generated name. The

UniqueConstraint.IsPrimaryKey property can be used to detect primary keys.

We constrain the Category column in the Categories table to be unique. Since the last

argument to the Add method is false, this is not a primary key of the table. We do not define a

primary key for this table, only a unique constraint. In fact, we do not even have to define any

constraint on the table. Although that would violate the rules of relational integrity, you are

not forced to use the DataSet in a relational manner. If you wish you can add a name to the


categories.Constraints.Add("Unique CategoryName

Constraint", categoryname, false);

Foreign Keys can specify what action should be taken when the primary key on which it is

based is changed. Your choices are the standard database choices: None, Cascade, SetNull.

You can also use SetDefault to set the new value to the DataColumn's DefaultValue

property. These operations can be specified for both update and delete conditions.

In this example, a foreign key constraint is set so that all author ids in the Books table have to

be found in the Authors table. In other words, when a new book row is inserted, it must have

an author. We give this constraint a name: "Authors->Books." If the author id is changed, the

update rule forces the DataSet to change all the author ids in the related rows to the new

author id. If the author id is deleted, the DataSet will set the deleted author ids in the Book

rows to null. If we had set the DeleteRule to Cascade, a cascading delete would be applied to

all those rows in the Books table. The AcceptRejectRule applies to editing of the DataSet,

which we will cover in a future section. This rule dictates what happens when the

AcceptChanges method is invoked on a DataSet, DataRow, or DataTable. In this case all

changes are cascaded. The alternative rule would be to take no action (None).

DataColumn[] bookauthorFK = new DataColumn[1];

bookauthorFK[0] = booksauid;

ForeignKeyConstraint fk = new


authorsPK, bookauthorFK);

fk.AcceptRejectRule = AcceptRejectRule.Cascade;

fk.DeleteRule = Rule.SetNull;

fk.UpdateRule = Rule.Cascade;


Data Relations

Besides constraints you can add a relation to the DataSet's DataRelation collection. A

relation connects two tables so that you can navigate between the parent and the child or the

child and the parent. When you add the relation you can optionally create and add the

equivalent unique and foreign key constraints to the parent and child tables' constraint

collections. [18]


Use the optional boolean createConstraints argument when you add a relation

to indicate whether the associated constraint should be added. If this argument is not

specified, the default is to add the associated constraint.

The Categories table is made the parent of the BookCategories table through the Categories

and CategoryName columns. In a relation both columns have to be of the same type (string).

You can use this relation to navigate by finding all the rows in the child table that have the

same value as in the parent table, or finding the row in the parent table that is the parent of a

row in the child table. Similarly the Library of Congress number associated with a book has to

be found in the Library of Congress field in the BookCategory's Library of Congress field.

ds.Relations.Add("Category->BookCategories Relation",

categoryname, cn);

ds.Relations.Add("Book Category LOC->Book LOC Relation",

loc, bloc);

Examining the Schema Information about a DataTable

You can examine the information about a DataTable. Here is how to examine the constraint

and key information. A previous example has already shown you how to find the

DataColumns for a DataTable. Note the use of the IsPrimaryKey property on the

UniqueConstraint to detect a primary key.

foreach(DataTable t in ds.Tables)



Console.WriteLine("\tPrimary Key:");

for (int i = 0; i < t.PrimaryKey.Length; i++)


DataColumn c = t.PrimaryKey[i];

Console.WriteLine("\t\t{0}", c.ColumnName);



foreach(Constraint c in t.Constraints)


string constraintName;

if (c is ForeignKeyConstraint)

constraintName = "Foreign Key:" + c.ConstraintName;

else if (c is UniqueConstraint)


UniqueConstraint u = (UniqueConstraint)c;

if (u.IsPrimaryKey)

constraintName = "Primary Key";


constraintName = u.ConstraintName;



constraintName = "Unknown Name";

Console.WriteLine("\t\t{0, -40}", constraintName);



This produces the following output. Note how the relations defined as a DataRelation appear

in the table's constraint collection as a ForeignKeyConstraint instance. PrimaryKeys appear

in the constraint collection as a UniqueConstraint instance. Constraints defined as unique

constraints or foreign keys appear as you would expect in the collection.


Primary Key:


Unique CategoryName Constraint


Primary Key:




Primary Key

Foreign Key:Category->BookCategories Relation



Primary Key:



Primary Key


Primary Key:



Primary Key

Foreign Key:Authors->Books

Foreign Key:Book Category LOC->Book LOC Relation

Note the BookCategories constraint with the system-generated name. If you examine the code

carefully, you will see we never added this constraint. Where did it come from? If you were to

look at the columns in that constraint, you would find the Library of Congress field. The

system realized that, since the CategoryName is a foreign key in another table, the Library of

Congress field should be unique.

You can also examine the relations collection on the DataSet. You can examine the parent

table and the columns in the parent table involved in the relationship. You can also examine

the child table in the relationship and its columns.

foreach(DataRelation dr in ds.Relations)


DataTable parentTable = dr.ParentTable;

DataTable childTable = dr.ChildTable;


Relation: {0} ", dr.RelationName);


ParentTable: {0, -10}",



Columns: ");

for(int j = 0; j < dr.ParentColumns.Length; j++)


{0, -10}",




ChildTable: {0, -10}",



Columns: ");

for(int j = 0; j < dr.ChildColumns.Length; j++)


{0, -10}",




Here is the resulting output:

Output Relations between tables in the DataSet...

Relation: Category->BookCategories Relation

ParentTable: Categories



ChildTable: BookCategories



Relation: Book Category LOC->Book LOC Relation

ParentTable: BookCategories



ChildTable: Books



Database Events

Several ADO.NET classes generate events.

The SqlConnection class generates the StateChange and InfoMessage events. The

SqlDataAdapter generates the RowUpdated and RowUpdating events. The DataTable

class generates the ColumnChanging, ColumnChanged, RowChanged, RowChanging,

RowDeleted, and RowDeleting events.

For example, the RowChanged event occurs after an action has been performed on a row.

Continuing with our DataEditing example, it defines a handler for the RowChanged event in

the Books table. Every time a row changes in the Books table, the event handler will run.



private static void Row_Changed(object sender,

System.Data.DataRowChangeEventArgs e)


DataTable table = (DataTable)sender;

DataColumn[] primaryKey = table.PrimaryKey;

string keyName = primaryKey[0].ColumnName;

Console.WriteLine("Table " + table.TableName + " " +

e.Action.ToString() + "Row with Primary Key " +




So when the code adds some rows, including some to the Books table:

DataRow row

row = categories.NewRow();

row["Category"] = "UnitedStates:PoliticalHistory";



row = authors.NewRow();

row["AuthorId"] = 1;

row["AuthorLastName"] = "Burns";

row["AuthorFirstName"] = "James M.";



row = books.NewRow();

row["ISBN"] = "0-201-62000-0";

row["Title"] = "The Deadlock of Democracy";

row["AuthorId"] = 1;

row["LibraryofCongressNumber"] = "E183.1";


row = books.NewRow();

row["ISBN"] = "0-201-62000-3";

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

Optimistic vs. Pessimistic Locking and the DataSet

Tải bản đầy đủ ngay(0 tr)