The Golf Reservation System Server Application

The Golf Reservation System server consists of the following: an SQL-compliant RDBMS, data adapter classes, .NET data sets, C# Object classes, and the GolfCourseService XML Web service and associated methods. When assembled, the C# Business Object classes, data set, data view, and data adapter classes, and the SQL RDBMS, will comprise the server application's business logic. The XML Web services layer will rest on top of this logic and provide a clean, easily accessible interface for client applications.

The Database

As mentioned earlier, the application will use a small Access database to store its information. The .mdb file can be transported easily and viewed on workstations as well as servers. The following sections represent a brief overview of the tables that appear in our database schema.

The Course Table

The course table will contain all the course-related information in the server application. Records can be inserted into the course table by issuing the following SQL statement:

 INSERT INTO course(city, country, description, name, postalCode, price, 
state, streetAddress, telephone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)

Table 9-2 contains the fields inside the course table.

Table 9-2 Course Fields

Column Description

Id

Contains the course's ID. It is autoincremented by the database.

Name

Contains the course's name.

StreetAddress

Contains the course's street address.

City

Contains the course's city.

State

Contains the course's state.

PostalCode

Contains the course's postal code or ZIP code.

Country

Contains the course's country.

Telephone

Contains the course's telephone number.

Price

Contains the price for a round of golf.

Description

Contains a longer description of the golf course.

The Tee Table

The tee table will represent the different tees on the golf course (for example, red, white, and blue). Each tee will have a unique yardage and slope. Tee records can be created with the following SQL command:

 INSERT INTO tee(courseId, description, distance, slope) VALUES (?, ?, ?, ?) 

Table 9-3 contains the fields in the tee table.

Table 9-3 Tee Fields

Column Description

Id

Contains the tee's ID. It is autoincremented by the database.

Slope

Contains a measurement of the difficulty of the tee.

Distance

Contains an aggregate measurement of all of the holes' distances.

Description

Contains a description of the tee (for example, red, white, and blue).

CourseId

Contains a primary key reference to the associated course.

The Hole Table

Records in the hole table represent a hole for a given tee. The hole's yardage, handicap, and par might vary with the tee. The following SQL command can be used to create new hole records:

 INSERT INTO Hole(handicap, length, teeId, hole, par) VALUES (?, ?, ?, ?, ?) 

Table 9-4 contains the fields in the hole table.

Table 9-4 Hole Fields

Column Description

Id

Contains the hole's ID. It is autoincremented by the database.

TeeId

Contains a primary key reference to the associated tee.

Handicap

Contains the hole's handicap (1-18).

Length

Contains the hole's yardage.

Hole

Contains the hole number (1-18).

Par

Contains the hole's par.

The Golfer Table

The golfer table will contain records for each registered golfer in the system, regardless of whether or not they have booked a tee time. Golfer records can be created by issuing the following SQL command:

 INSERT INTO golfers(city, country, email, firstName, lastName, phone, postalCode, state, streetAddress, [password], username) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 

Table 9-5 contains the fields in the golfer table.

Table 9-5 Golfer Fields

Column Description

Id

Contains the golfer's ID. It is autoincremented by the database.

FirstName

Contains the golfer's first name.

LastName

Contains the golfer's last name.

StreetAddress

Contains the golfer's street address.

City

Contains the golfer's city.

State

Contains the golfer's state.

PostalCode

Contains the golfer's postal code or Zip Code.

Country

Contains the golfer's country.

Email

Contains the golfer's e-mail address.

Phone

Contains the golfer's telephone number.

Username

Contains the golfer's username, required to log in and request a tee time

Password

Contains the golfer's password, required to log in and request a tee time.

The Bookings Table

The bookings table will house all the requested course tee times. The table will contain only those tee times for a course that have been filled. Available tee times will be inferred by the "gaps" in this table. New bookings records can be created with the following SQL insert statement:

 INSERT INTO bookings(courseId, golfer, teeDate, teeTime) VALUES (?, ?, ?, ?) 

Table 9-6 contains the fields in the bookings table.

Table 9-6 Bookings Fields

Column Description

Id

Contains the booking's ID. It is autoincremented by the database.

CourseId

Contains a primary key reference to the associated course.

Golfer

Contains a primary key reference to the associated golfer.

TeeDate

Contains the date of the tee time.

TeeTime

Contains the time of the tee time.

Data Sets and Data Adapters

Microsoft Visual Studio .NET aids your enterprise development efforts by providing data sets and data adapters to act as two layers of abstraction above the database. All data interaction to and from the business logic layer happens through data sets. Data sets act like large, multidimensional arrays for staging data that could come from databases, files, the Internet, or all of the above. They pull together the data that the application's business logic will need; and provide a clean, consolidated interface. Data sets are serializable, which means they can be saved or sent over the network to another running application.

Data adapters are the helper classes that populate the data sets and perform necessary database updates. They keep the data set classes simple by handling the dirty work of reading and writing data. Data adapter classes are tuned to a specific datasource such as a specific table in Access. They contain all the SQL Insert, Update, Select, and Delete statements needed to access that table. Multiple data adapters can be bound to a single data set. This is the case with the Golf Reservation System server application. Each data adapter is responsible for loading and storing a subset of the data set.

Data Adapters

The best way to create your data adapter classes is to use the .NET Studio Visual Designer. Use the Server Explorer pane to add a new data connection to your project. Upon adding this connection, the Server Explorer will update itself to show the connection and the data tables that can be managed by the connection.

Drag each of the tables you want to use onto the designer. The .NET Studio will generate a default data adapter for the table and display its properties in the Properties window. At this point you can conFigure the data adapter's Insert, Select, Update, and Delete SQL commands and other connection-related properties. We created data adapters this way for the course, hole, tee, golfer, and bookings tables in the application.

The .NET Studio will autogenerate the adapter code and place it into the .asmx.cs file of your project. You might need to tweak this code from time to time, so you should know what it does. (We needed to alter the SQL Insert command that some of the adapters were using to accommodate system-generated keys.) Let's take a look at how the GolferDataAdapter class works. The code fragments shown here come from the GolfCourseService.asmx.cs file.

First we need to create an instance of the data adapter class and its associated OLE-DB command classes.

 this.oleDbUpdateCommand5 = new System.Data.OleDb.OleDbCommand(); this.oleDbSelectCommand5 = new System.Data.OleDb.OleDbCommand(); this.oleDbInsertCommand5 = new System.Data.OleDb.OleDbCommand(); this.oleDbDeleteCommand5 = new System.Data.OleDb.OleDbCommand(); this.golferDataAdapter = new System.Data.OleDb.OleDbDataAdapter(); 

Next we need to conFigure each of the OLE-DB command classes. This is done by specifying the SQL statement and filling in its required parameters. Again, the .NET Studio Visual Designer will create this code for you, although you may need to customize it in order for it to work with your database. The following is the configuration code for the Insert command. This code will create a new golfer record in the golfers table.

 this.oleDbInsertCommand5.CommandText = "INSERT INTO golfers(city, 
country, email, firstName, lastName, phone, postalCode," + " state, streetAddress, [password], username) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?)"; this.oleDbInsertCommand5.Connection = this.oleDbConnection1; this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("city", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "city", System.Data.DataRowVersion.Current, null)); this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("country", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "country", System.Data.DataRowVersion.Current, null)); this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("email", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "email", System.Data.DataRowVersion.Current, null)); this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("firstName", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "firstName", System.Data.DataRowVersion.Current, null)); this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("lastName", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "lastName", System.Data.DataRowVersion.Current, null)); this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("phone", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "phone", System.Data.DataRowVersion.Current, null)); this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("postalCode", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "postalCode", System.Data.DataRowVersion.Current, null)); this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("state", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "state", System.Data.DataRowVersion.Current, null)); this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("streetAddress", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "streetAddress", System.Data.DataRowVersion.
Current, null)); this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("password", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "password", System.Data.DataRowVersion.Current, null)); this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("username", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "username", System.Data.DataRowVersion.Current, null));

The Select, Update, and Delete commands for the golfers table (and all other tables in the application) work in much the same way. In all cases, you must specify a parameterized SQL CommandText string to execute against the database. You must also add a new object to the Parameters array for each runtime value you intend to use.

Next we bind the newly created OLE-DB command objects to the data adapter. This will force the adapter to use the SQL we just wrote when inserting, updating, deleting, or selecting data to and from the table.

 this.golferDataAdapter.DeleteCommand = this.oleDbDeleteCommand5; this.golferDataAdapter.InsertCommand = this.oleDbInsertCommand5; this.golferDataAdapter.SelectCommand = this.oleDbSelectCommand5; this.golferDataAdapter.UpdateCommand = this.oleDbUpdateCommand5; 

Finally we need to specify the table mappings that map columns in the golfers table to columns in the associated data set. We will take a closer look at the data set momentarily.

 this.golferDataAdapter.TableMappings.AddRange(new System.Data.
Common.DataTableMapping[] { new System.Data.Common.DataTableMapping("Table", "golfers", new
System.Data.Common.DataColumnMapping[] { new System.Data.Common.DataColumnMapping("city", "city"), new System.Data.Common.DataColumnMapping("country", "country"), new System.Data.Common.DataColumnMapping("email", "email"), new System.Data.Common.DataColumnMapping("firstName", "firstName"), new System.Data.Common.DataColumnMapping("id", "id"), new System.Data.Common.DataColumnMapping("lastName", "lastName"), new System.Data.Common.DataColumnMapping("phone", "phone"), new System.Data.Common.DataColumnMapping("postalCode",
"postalCode"), new System.Data.Common.DataColumnMapping("state", "state"), new System.Data.Common.DataColumnMapping("streetAddress", "streetAddress"), new System.Data.Common.DataColumnMapping("password", "password"), new System.Data.Common.DataColumnMapping("username", "username") }) });

That's it for the golfer data adapter! As you can see, creating the adapter objects for your application is a tedious and error-prone process. The .NET Studio will save a lot of time and debugging effort if you let it create these classes for you.

Data Sets

You will use data sets for practically all the database manipulation you need to do in your application. Data sets are in-memory databases that provide their own insert, update, delete, select, transaction, and filter semantics. By coding to the data set interface you make it much easier to port your application to another database later.

Typed vs. Untyped Data Sets

.NET data sets come in two flavors: typed and untyped. Typed data sets have built-in schemas associated with particular databases and columns. .NET uses this schema to extend the base DataSet class and create a new class that contains column references as typed properties and accessors.

Untyped data sets, on the other hand, do not contain extra information provided by the schema. You must manually extract and cast data values from the data set's array. The difference between typed and untyped data sets can be illustrated best by an example.

To retrieve a golfer's last name from an untyped data set you would have to execute the following lines of code:

 string lastName = (string)courseDataSet1.Tables
["golfers"].Rows[0]["lastName"];

Compare that to using a typed version of the same data set:

 string lastName = courseDataSet1.golfers[0].lastName; 

The typed data set is much easier to use (especially when you have command completion turned on) and the .NET Studio will generate all the code for you.

Generating the Data Set

Once you've prepared your data adapter classes, you're ready to generate a typed data set from them. Right-click on the visual designer and select Generate Dataset. Alternatively, you can select the Generate Dataset option in the Tools menu. The Generate Dataset Wizard will walk you through the steps necessary to create a new DataSet class. The only detail you definitely need take care of is to select which data tables and data adapters the data set should use. After you have checked these, the .NET Studio will create a new typed data set schema and will use this schema to create the typed data set class definition.

The code behind the new data set is incredibly long (over two thousand lines for the server application), so we won't get into the guts here. Basically, the typed data set class provides simple marshaling utilites that either put information into the data set array or pull information out of it. Figure 9-3 shows how the designer looks with all the data adapters and data sets already configured.

Figure 9-3 A snapshot of the .NET Studio Designer showing the completed data acess objects.

Data Views

You will often need to filter the underlying database in your enterprise application. Whether you're showing information about one record or 20, you need some mechanism to extract and sort a well-defined subset of data. This was traditionally done via SQL SELECT statements that pull the filtered data view directly from the database. This approach shouldn't be used with data sets. Instead, .NET includes a DataView class to provide this functionality.

DataView objects are bound to a specific table in a data set. The table can be passed into the DataView object through its constructor or set manually via its Table public property. Once the data view is bound to a table, you can change its RowFilter and Sort properties at runtime to manipulate the data shown in the view. The Count property tells you how many records the view currently holds.

The RowFilter data view property uses the .NET Expression syntax to query the data set. .NET Expressions is a powerful query language with rich operator support. For example, the following expressions can be used to filter the course table:

  • "Name LIKE `Pinehurst*'"
  • "Price < 100.00"
  • "State = `North Carolina'"
  • "Price < 100.00 AND State = `North Carolina' AND (City = `Durham' OR City = `Chapel Hill' OR City =`Raleigh'"

DataView objects and their associated DataRowView objects are not typed, however. Therefore, you must use them as you would an untyped data set.

Business Objects

The Golf Reservation System server will contain class descriptions that model the database entities shown previously in an object format. Class definitions will exist for GolfCourse, Golfer, Tee, Hole, and TeeTime. A generic Address class will also be provided as a uniform means to represent golf course and golfer addresses. Instances or collections of instances of these objects will be returned by Web methods.

Golf Reservation System clients will need to download stub representations of these classes to make sense of the return results. Visual Studio .NET takes care of this by adding Web references. Other clients will need to query the WSDL document for the GolfCourseService XML Web service.

GolfCourse

The GolfCourse object provides an objectified view of objects in the course database table. The class definition for this object resides in GolfCourse.cs. The GolfCourse object is used primarily as a means to transport information back to an XML Web services client. The class uses public properties so that they can be included as a return result from an XML Web service call.

 using System; using System.Data; namespace TeeTimes { /// <summary> /// A GolfCourse /// </summary> public class GolfCourse { public int id; public String name; public String description;  public String price; public String telephone; public Address address; public Tee[] tees; 

The GolfCourse object has four different constructors. The first is the default constructor, which initializes all its properties to null.

 public GolfCourse() { id = 0; name = ""; description = ""; price = ""; telephone = ""; address = new Address(); tees = null; } 

The second constructor takes a row in a typed data set as an argument. The course's properties are initialized by the properties contained in the data row.

 public GolfCourse(courseDataSet.courseRow c) { name = ""; description = ""; price = ""; address = new Address(); tees = null; id = c.id; if (!c.IsnameNull()) name = c.name; if (!c.IsdescriptionNull()) description = c.description; if (!c.IspriceNull()) price = c.price; if (!c.IstelephoneNull()) telephone = c.telephone; if (!c.IsstreetAddressNull()) address.street = c.streetAddress; if (!c.IscityNull()) address.city = c.city; if (!c.IsstateNull()) address.state = c.state; if (!c.IspostalCodeNull()) address.postalCode = c.postalCode; if (!c.IscountryNull()) address.country = c.country; } 

The third constructor accepts a DataRowView object that was obtained via a DataView filter. The DataRowView is untyped, so the constructor must use array access to load its properties.

 public GolfCourse(DataRowView c) { id = 0; name = ""; description = ""; price = ""; address = new Address(); tees = null; id = Int32.Parse(c["id"].ToString()); name = c["name"].ToString(); description = c["description"].ToString(); price = c["price"].ToString(); telephone = c["telephone"].ToString(); address.street = c["streetAddress"].ToString(); address.city = c["city"].ToString(); address.state = c["state"].ToString(); address.postalCode = c["postalCode"].ToString(); address.country = c["country"].ToString(); } 

The fourth and final GolfCourse constructor accepts a reference to a typed DataSet object and a primary key value. The constructor calls the DataSet object's FindById( ) method to obtain the correct row. If the row exists the constructor uses the data contained therein to initialize the GolfCourse object.

 public GolfCourse(courseDataSet ds, int courseId) { id = 0; name = ""; description = ""; price = ""; address = new Address(); courseDataSet.courseRow c = ds.course.FindByid(courseId); if (c != null) { id = courseId; name = ""; description = ""; price = ""; address = new Address(); if (!c.IsnameNull()) name = c.name; if (!c.IsdescriptionNull()) description = c.description; if (!c.IspriceNull()) price = c.price; if (!c.IstelephoneNull()) telephone = c.telephone; if (!c.IsstreetAddressNull()) address.street = c.streetAddress; if (!c.IscityNull()) address.city = c.city; if (!c.IsstateNull()) address.state = c.state; if (!c.IspostalCodeNull()) address.postalCode = c.postalCode; if (!c.IscountryNull()) address.country = c.country; 

The GolfCourse contructor locates all the course's Tee objects, instantiates them, and initializes the tees array property in the following:

 DataView teeView = new DataView(ds.tee); int teeCnt = ds.tee.Count; teeView.RowFilter = "courseId = "+courseId; int cnt = 0; tees = new Tee[teeView.Count]; foreach (DataRowView r in teeView) { int tee_id = Int32.Parse(r["id"].ToString()); Tee tee = new Tee(ds,tee_id); tees[cnt] = tee; cnt++; } }  } 

Public getters and setters are used to manipulate the GolfCourse's properties.

 public int getId() { return id; } public String getName() { return name; } public String getDescription() { return description; } public Address getAddress() { return address; } public String getPrice() { return price; } public String getTelephone() { return telephone; } public void setId(int _id) { id = _id; } public void setName(String _name) { name = _name; } public void setDescription(String _description) { description = _description;} public void setPrice(String _price) { price = _price; } public void setTelephone(String _telephone) { telephone = _telephone; } 

The class provides a setAddress( ) method to update the Address-dependent object.

 public void setAddress(String street, String city, String state, 
String postalCode, String country) { address.setStreet(street); address.setCity(city); address.setState(state); address.setPostalCode(postalCode); address.setCountry(country); } } }

Golfer

The Golfer object maintains all golfer and user states. Golfer object references can also be included within Web method result documents. The class definition for this object is shown in Golfer.cs.

 using System; namespace TeeTimes { /// <summary> /// Summary description for Golfer. /// </summary> public class Golfer { public int id; public String firstName; public String lastName; public String email; public String phone; public String username; public String password; public Address address; 

The Golfer object also has several constructors.

 public Golfer() { id = 0; firstName = ""; lastName = ""; email = ""; phone = ""; username = ""; password = ""; address = new Address(); } 

This constructor accepts a list of all Golfer properties to be initialized.

 public Golfer(String _firstName, String _lastName, String _email, 
String _phone, String _streetAddress, String _city, String _state,
String _postalCode, String _country, String _username, String _password) { firstName = _firstName; lastName = _lastName; email = _email; phone = _phone; username = _username; password = _password; address = new Address(); address.setStreet(_streetAddress); address.setCity(_city); address.setState(_state); address.setPostalCode(_postalCode); address.setCountry(_country); }

This constructor accepts the typed courseDataSet object and a Golfer id value. The constructor looks up the Golfer in the data set and sets its properties accordingly.

 public Golfer(courseDataSet ds, int golferId) { id = 0; firstName = ""; lastName = ""; email = ""; phone = ""; username = ""; password = ""; address = new Address(); courseDataSet.golfersRow g = ds.golfers.FindByid(golferId); if (g != null) { id = golferId; if (!g.IsfirstNameNull()) firstName = g.firstName; if (!g.IslastNameNull()) lastName = g.lastName; if (!g.IsemailNull()) email = g.email; if (!g.IsphoneNull()) phone = g.phone; if (!g.IsusernameNull()) username = g.username; if (!g.IspasswordNull()) password = g.password; if (!g.IsstreetAddressNull()) address.setStreet(g.streetAddress); if (!g.IscityNull()) address.setCity(g.city); if (!g.IsstateNull()) address.setState(g.state); if (!g.IspostalCodeNull()) address.setPostalCode(g.postalCode); if (!g.IscountryNull()) address.setCountry(g.country); } } 

The following are the Golfer accessor and mutator methods:

 public int getId() { return id; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public String getEmail() { return email; } public String getPhone() { return phone; } public String getUserName() { return username; } public String getPassword() { return password; } public Address getAddress() { return address; } public void setId(int _id) { id = _id; } public void setFirstName(String _firstName) { firstName = _firstName; } public void setLastName(String _lastName) { lastName = _lastName; } public void setEmail(String _email) { email = _email; } public void setPhone(String _phone) { phone = _phone; } public void setUserName(String _username) { username = _username; } public void setPassword(String _password) { password = _password; } public void setAddress(String streetAddress, String city, String state, 
String postalCode, String country) { address.setStreet(streetAddress); address.setCity(city); address.setState(state); address.setPostalCode(postalCode); address.setCountry(country); } } }

Tee

The Tee class models the different types of tees that a golf course might have, such as red, white, and blue. Each Tee has its own set of yardage, slope, and distance properties as well as a collection of 18 unique Hole objects. Tee.cs contains the class definition.

 using System; using System.Data; namespace TeeTimes { /// <summary> /// Summary description for Tee. /// </summary> public class Tee { public int id; public double slope; public int distance; public String description; public int courseId; public Hole[] holes; public Tee() { } public Tee(int _id, int _courseId, String _description, float _slope) { id = _id; courseId = _courseId; description = _description; slope = _slope; holes = null; } 

Again, we see the Data Set/ID constructor design pattern. You have the option to decide how "data-aware" your business objects should be. We overloaded our business objects' constructors to perform the most data-set access. Doing so simplifies the XML Web service logic.

 public Tee(courseDataSet ds, int teeId) { courseDataSet.teeRow t = ds.tee.FindByid(teeId); if (t != null) { id = teeId; courseId = t.courseId; description = t.description; slope = t.slope; 

Here the Tee constructor builds an array of dependent Hole objects. We use a DataView object to filter the courseDataSet and generate a list of keys. We iterate over these keys, construct the objects, and create our holes array.

 DataView holeView = new DataView(ds.Hole); holeView.RowFilter = "teeId = "+teeId; holeView.Sort = "hole"; int cnt = 0; holes = new Hole[holeView.Count]; foreach (DataRowView r in holeView) { Hole hole = new Hole(); hole.setTeeId(teeId); int hole_handicap = Int32.Parse(r["handicap"].ToString()); int hole_length = Int32.Parse(r["length"].ToString()); int hole_hole = Int32.Parse(r["hole"].ToString()); int hole_par = Int32.Parse(r["par"].ToString()); hole.setHandicap(hole_handicap); hole.setLength(hole_length); hole.setHole(hole_hole); hole.setPar(hole_par); holes[cnt] = hole; cnt++; } }  } 

These are the Tee accessors and mutators.

 public int getId() { return id; } public double getSlope() { return slope; } public int getDistance() { return distance; } public int getCourseId() { return courseId; } public String getDescription() { return description; } public Hole[] getHoles() { return holes; } public void setId(int _id) { id = _id; } public void setSlope(double _slope) { slope = _slope; } public void setDistance(int _distance) { distance = _distance; } public void setCourseId(int _courseId) { courseId = _courseId; } public void setDescription(String _description) { description = _description; } public void setHoles(Hole[] _holes) { holes = _holes; } } } 

Hole

Contrary to common sense, a single golf course might have 18, 36, 54, or more holes because each hole is different with respect to the tee from which the golfer is playing on a given day. A hole that plays 320 yards from the white tees might play 380 yards from the blue tees. To accommodate this requirement we decided to give each tee its own collection of 18 hole objects. The Hole class definition is found in Hole.cs.

 using System; namespace TeeTimes { /// <summary> /// Summary description for Hole Class /// </summary> public class Hole { public int id; public int teeId; public int handicap; public int length; public int hole; public int par; public Hole() { id = 0; teeId = 0; handicap = 0; length = 0; hole = 0; par = 0; } 

Hole constructors are always fed properties by value in the server project. This class might be extended later to provide the DataSet/ID constructor interface.

 public Hole(int _id, int _teeId, int _handicap, int _length, int _hole, int _par) { id = _id; teeId = _teeId; handicap = _handicap; length = _length; hole = _hole; par = _par; } public int getId() { return id; } public int getTeeId() { return teeId; } public int getHandicap() { return handicap; } public int getLength() { return length; } public int getHole() { return hole; } public int getPar() { return par; } public void setId(int _id) { id = _id; } public void setTeeId(int _teeId) { teeId = _teeId; } public void setHandicap(int _handicap) { handicap = _handicap; } public void setLength (int _length) { length = _length; } public void setHole (int _hole) { hole = _hole; } public void setPar (int _par) { par = _par; } } } 

TeeTime

The TeeTime class represents a tee-time booking on a specific date and time. TeeTime objects hold references to a golfer and a golf course. TeeTime objects can be obtained by XML Web services clients for a specific golf course to see what times are still available for play. TeeTime.cs shows the source code behind the TeeTime class.

 using System; namespace TeeTimes { /// <summary> /// Summary description for TeeTime. /// </summary> public class TeeTime { public int id; public DateTime date; public DateTime time; public int courseId; public int golfer; public TeeTime() { id = 0; date = System.DateTime.Now; time = System.DateTime.Now; courseId = 0; golfer = 0; } public TeeTime(int _id, DateTime _date, DateTime _time, int _courseId, int _golfer) { id = _id; date = _date; time = _time; courseId = _courseId; golfer = _golfer; } 

Again, we see the Data Set/ID constructor pattern.

 public TeeTime(courseDataSet ds, int ttId) { id = 0; date = System.DateTime.Now; time = System.DateTime.Now; courseId = 0; golfer = 0; courseDataSet.bookingsRow tt = ds.bookings.FindByid(ttId); if (tt != null) { id = ttId; if (!tt.IscourseIdNull()) courseId = tt.courseId; if (!tt.IsgolferNull()) golfer = tt.golfer; if (!tt.IsteeDateNull()) date = DateTime.Parse(tt.teeDate); if (!tt.IsteeTimeNull()) time = DateTime.Parse(tt.teeTime); } } public int getId() { return id; } public DateTime getDate() { return date; } public DateTime getTime() { return time; } public int getCourseId() { return courseId; } public int getGolfer() { return golfer; } public void setId(int _id) { id = _id; } public void setDate(DateTime _date) { date = _date; } public void setTime(DateTime _time) { time = _time; } public void setCourseId(int _courseId) { courseId = _courseId; } public void setGolfer(int _golfer) { golfer = _golfer; } } } 

Address

The Address object is used to provide a common address API for the server. GolfCourses and Golfers both hold references to objects of this type. This class could be extended later to support multiple address, driving directions, and other functionalities. The class definition can be found in Listing 9-1.

Listing 9-1 Address.cs: The Address utility class.

 using System; namespace TeeTimes { /// <summary> /// Summary description for Address. /// </summary> public class Address { public String street; public String city; public String state; public String postalCode; public String country; public Address() { street = ""; city = ""; state = ""; postalCode = ""; country = ""; } public String getStreet() { return street; } public String getCity() { return city; } public String getState() { return state; } public String getPostalCode() { return postalCode; } public String getCountry() { return country; } public void setStreet(String _street){ street = _street; } public void setCity(String _city){ city = _city; } public void setState(String _state){ state = _state; } public void setPostalCode(String __postalCode){ postalCode = _postalCode; } public void setCountry(String _country){ country = _country; } } } 

The GolfCourseService XML Web service

All Golf Reservation System Web methods will be assembled into a single XML Web service called GolfCourseService. The application publishes relatively few methods, so only a single XML Web service umbrella is required. All access to the server's business logic will pass through this API.

You will notice that most of the Web methods are small. .NET takes care of all the complex SOAP argument marshaling and our business object handles most of the data access and all of the nested object creation. The Web methods don't need to do too much at all.

FindAll( )

 Syntax: GolfCourse[] FindAll() 

FindAll( ) returns an array of GolfCourse objects. This method is called to get a complete listing of the courses maintained by the system. The method simply iterates over the entire course table in the GolfCourse data set.

 [WebMethod] public GolfCourse[] FindAll() { int nCourses = courseDataSet1.course.Count; GolfCourse[] golfCourses = new GolfCourse[nCourses]; int cnt = 0; foreach (courseDataSet.courseRow c in courseDataSet1.course) { GolfCourse gc = new GolfCourse(c); golfCourses[cnt] = gc; cnt++; } return golfCourses; } 

FindGeneric( )

 Syntax: GolfCourse[] FindGeneric(String[] fields, String[] values) 

FindGeneric( ) implements a simple yet flexible query interface into the course database. Clients can pass in arrays of property names and value expressions (including * wildcards) that can be used to generate a list of matching courses. The method makes use of the .NET DataView class to filter data in the courseDataSet1 data set.

An added bonus of this design is that this method will not need to change as fields are added to the course table and dataset.

 [WebMethod] public GolfCourse[] FindGeneric(String[] fields, String[] values) { DataView cv = new DataView(courseDataSet1.course); cv.Sort = "name"; String filterString = ""; for (int x = 0; x < fields.Length; x++) { if (x >= 1) { filterString += " AND"; } filterString += " "+fields[x]+" LIKE '"+values[x]+"'"; } cv.RowFilter = filterString; int nCourses = cv.Count; GolfCourse[] golfCourses = new GolfCourse[nCourses]; int cnt = 0; foreach (DataRowView c in cv) { int gcId = Int32.Parse(c["id"].ToString()); GolfCourse gc = new GolfCourse(courseDataSet1, gcId); golfCourses[cnt] = gc; cnt++; } return golfCourses; } 

GetCourseDetail( )

 Syntax: GolfCourse GetCourseDetail(String id) 

GetCourseDetail( ) returns a complete representation of a golf course. Tees and Holes are returned as nested arrays. This method is a good example of a coarse-grained Web method. Recall that the GolfCourse constructor that is called here recursively builds arrays of Tees and Holes. A single call to this method retrieves enough information to be displayed on three different Web forms.

 [WebMethod] public GolfCourse GetCourseDetail(String id) { int cid = Int32.Parse(id); GolfCourse gc = new GolfCourse(courseDataSet1,cid); return gc; } 

AddCourse( )

 Syntax: GolfCourse AddCourse(String name, String description, String 
price, String streetAddress, String city, String state, String
postalCode, String country, String telephone)

AddCourse( ) creates a new course entry in the golf course database.

 [WebMethod] public GolfCourse AddCourse(String name, String description, String price, 
String streetAddress, String city, String state, String
postalCode, String country, String telephone) { courseDataSet.courseRow newCourse = courseDataSet1.course.
AddcourseRow(city,country,description,name,
postalCode,price,state,streetAddress,telephone); courseDataAdapter.Update(courseDataSet1,"course"); courseDataSet1.AcceptChanges(); GolfCourse gc = new GolfCourse(newCourse); return gc; }

AddTee( )

 Syntax: GolfCourse AddTee(int courseId, String description, float slope 

AddTee( ) creates a new tee entry for a particular golf course.

 [WebMethod] public GolfCourse AddTee(int courseId, String description, float slope) { courseDataSet.teeRow newTee = courseDataSet1.tee.AddteeRow(courseId,description,0,slope); teeDataAdapter.Update(courseDataSet1,"tee"); courseDataSet1.AcceptChanges(); GolfCourse gc = new GolfCourse(courseDataSet1, courseId); return gc; } 

AddHole( )

 Syntax: GolfCourse AddHole(int teeId, int handicap, int length, int hole, int par) 

AddHole( ) adds a new hole record to the Hole database. The hole must be associated with a particular Tee object.

 [WebMethod] public GolfCourse AddHole(int teeId, int handicap, int length, int hole, int par) { courseDataSet.HoleRow newHole = courseDataSet1.Hole.AddHoleRow (handicap,length,teeId,hole,par); holeDataAdapter.Update(courseDataSet1,"Hole"); courseDataSet1.AcceptChanges(); courseDataSet.teeRow tr = courseDataSet1.tee.FindByid(teeId); int courseId = tr.courseId; GolfCourse gc = new GolfCourse(courseDataSet1, courseId); return gc; } 

AddTeeTime( )

 Syntax: TeeTime AddTeeTime(int courseId, int golfer, DateTime date, DateTime time) 

AddTeeTime( ) creates a new tee time record. The tee time record requires valid references to a golf course and a golfer record. The method also takes in two DateTime objects, one representing the date of the tee time and the other representing the time.

 [WebMethod] public TeeTime AddTeeTime(int courseId, int golfer, DateTime date, DateTime time) { String dateString = date.ToShortDateString(); courseDataSet.bookingsRow newBooking = courseDataSet1.
bookings.AddbookingsRow(courseId,golfer,dateString,time.ToString()); bookingsDataAdapter.Update(courseDataSet1,"bookings"); courseDataSet1.AcceptChanges(); TeeTime tt = new TeeTime(0, date, time, courseId, golfer); return tt; }

FindTeeTimesByGolfer( )

 Syntax: TeeTime[] FindTeeTimesByGolfer(int golferId) 

FindTeeTimesByGolfer( ) returns an array representing a complete tee time listing for a particular golfer. This listing can be used to remind a golfer of upcoming tee times. Again, we see the DataView object put to good use.

 [WebMethod] public TeeTime[] FindTeeTimesByGolfer(int golferId) { DataView ttv = new DataView(courseDataSet1.bookings); ttv.Sort = "teeTime"; ttv.RowFilter = "golfer = '"+golferId+"'"; int nTimes = ttv.Count; TeeTime[] teeTimes = new TeeTime[nTimes]; int cnt = 0; foreach (DataRowView c in ttv) { int ttId = Int32.Parse(c["id"].ToString()); TeeTime tt = new TeeTime(courseDataSet1, ttId); teeTimes[cnt] = tt; cnt++; } return teeTimes; } 

AddGolfer( )

 Syntax: Golfer AddGolfer(String firstName, String lastName, String 
streetAddress, String city, String state, String postalCode, String
country, String email, String phone, String username, String password)

AddGolfer( ) creates a new golfer record in the database. This method requires all pertinent address and profile information.

 [WebMethod] public Golfer AddGolfer(String firstName, String lastName, String 
streetAddress, String city, String state, String postalCode,
String country, String email, String phone, String username, String password) { courseDataSet.golfersRow newGolfer = courseDataSet1.golfers.
AddgolfersRow(city,country,email,firstName,lastName,phone,
postalCode,state,streetAddress,password,username); golferDataAdapter.Update(courseDataSet1,"golfers"); courseDataSet1.AcceptChanges(); Golfer g = new Golfer(firstName,lastName,email,phone,
streetAddress,city,state,postalCode,country,username,password); return g; }

FindGolferById( )

 Syntax: Golfer FindGolferById(int golferId) 

FindGolferById( ) looks up a specific golfer and returns a Golfer object with matching ID if one exists. It doesn't get any easier than this.

 [WebMethod] public Golfer FindGolferById(int golferId) { return new Golfer(courseDataSet1,golferId); } 

ValidateLogin( )

 Syntax: Golfer ValidateLogin(String username, String password) 

ValidateLogin( ) is used to validate username/password combinations against the Golfer database. This is necessary to prevent fake tee times from being created. If the validation succeeds, the method returns the entire Golfer object. If it fails, the method returns null.

 [WebMethod] public Golfer ValidateLogin(String username, String password) { DataView gv = new DataView(courseDataSet1.golfers); gv.RowFilter = "username = '"+username+"' AND password =  '"+password+"'"; int gCount = gv.Count; Golfer g = null; if (gCount >= 1) { DataRowView gr = gv[0]; int golferId = Int32.Parse(gr["id"].ToString()); g = new Golfer(courseDataSet1,golferId); } return g; } 

FindTeeTimesByDate( )

 Syntax: TeeTime[] FindTeeTimesByDate(int courseId, DateTime date) 

FindTeeTimesByDate( ) returns an array of tee times for a specified course on a specified date.

 [WebMethod] public TeeTime[] FindTeeTimesByDate(int courseId, DateTime date) { DataView ttv = new DataView(courseDataSet1.bookings); ttv.Sort = "teeTime"; ttv.RowFilter = "teeDate = '"+date.ToShortDateString()+"' AND courseId = '"+courseId+"'"; int nTimes = ttv.Count; TeeTime[] teeTimes = new TeeTime[nTimes]; int cnt = 0; foreach (DataRowView c in ttv) { int ttId = Int32.Parse(c["id"].ToString()); TeeTime tt = new TeeTime(courseDataSet1, ttId); teeTimes[cnt] = tt; cnt++; } return teeTimes; } 

FindTeeTimesByDateRange( )

 Syntax: TeeTime[] FindTeeTimesByDateRange(int courseId, DateTime startDate, DateTime endDate) 

FindTeeTimesByDateRange( ) returns an array of tee times for a specified course across a specified date range. Because of a strange problem in one of the earlier .NET Studio releases, we were unable to write Access records with fields of type DateTime. Thus, we were forced to serialize dates and times to strings before writing them out. This made the process of filtering by date range significantly more difficult.

 [WebMethod] public TeeTime[] FindTeeTimesByDateRange(int courseId, DateTime 
startDate, DateTime endDate) { DataView ttv = new DataView(courseDataSet1.bookings); ttv.Sort = "teeTime"; String filterText = "courseId = '"+courseId+"' AND ("; TimeSpan duration = endDate.Subtract(startDate); int nDays = duration.Days; for (int i = 0; i <= nDays; i++) { if (i > 0) filterText = filterText + " OR"; DateTime tDate = startDate.AddDays(i); filterText = filterText+" teeDate = '"+
tDate.ToShortDateString()+"'"; } filterText = filterText + ")"; ttv.RowFilter = filterText; int nTimes = ttv.Count; TeeTime[] teeTimes = new TeeTime[nTimes]; int cnt = 0; foreach (DataRowView c in ttv) { int ttId = Int32.Parse(c["id"].ToString()); TeeTime tt = new TeeTime(courseDataSet1, ttId); teeTimes[cnt] = tt; cnt++; } return teeTimes; }


XML Programming
XML Programming Bible
ISBN: 0764538292
EAN: 2147483647
Year: 2002
Pages: 134

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net