Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

.pdf
Скачиваний:
75
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

528 C H A P T E R 1 2 A D O . N E T A N D D A T A B A S E D E V E L O P M E N T

Figure 12-8. Nontransactional database access

1.Create a link to the database with a SqlConnection.

2.Open the database with the Open() method.

3.Create a database command with SqlCommand.

C H A P T E R 1 2 A D O . N E T A N D D A T A B A S E D E V E L O P M E N T

529

4.Execute the command by using one of the three methods within SqlCommand (see Table 12-6). The database is immediately updated.

Table 12-6. The Main SqlCommand SQL Statement Execution Methods

Method

Description

ExecuteNonQuery

Executes a statement that updates the database.

ExecuteReader

Executes a query to the database that could potentially return multiple

 

rows from a database. This method returns a SqlDataReader object that

 

provides forward-only read access to the retrieved data or result set.

ExecuteScalar

Executes a statement that returns a single value.

 

 

5.Repeat steps 3 and 4 until completed.

6.Close the database with the Close() method.

Note If you are using the SQL Server managed provider, use classes prefixed with Sql. On the other hand, when you are using the OLE DB managed provider, use classes starting with OleDb; when you are using the ODBC managed provider, use classes starting with Odbc; and when you are using the Oracle managed provider, use classes starting with Oracle.

Connecting to, Opening, and Closing a Database

With connected nontransactional access to a database, you will always be connecting to, opening, and closing your database. To handle this, you need to work with one of the Connection classes:

SqlConnection, OleDbConnection, OdbcConnection, or OracleConnection. Which one of these you use depends on the managed provider you use.

This book uses Microsoft SQL Server, so you’ll use the SQL Server managed provider. If you are using the OLE DB, ODBC, or Oracle managed provider, just remember to replace the prefix of every class starting with Sql with OleDb, Odbc, or Oracle and, of course, you will have to change the connection string, but I’ll get to that shortly.

Listing 12-5 shows how to connect, open, and close a database in a nontransactional method.

Listing 12-5. Connecting, Opening, and Closing a Database

using namespace System;

using namespace System::Data;

using namespace System::Data::SqlClient;

void main()

{

SqlConnection^ connection = gcnew SqlConnection();

#ifdef SQLAuth

// SQL Server authentication connection->ConnectionString =

"User ID=sa; Password=;"

"Data Source=(local); Initial Catalog=DCV_DB;";

530 C H A P T E R 1 2 A D O . N E T A N D D A T A B A S E D E V E L O P M E N T

#else

// Windows Integrated Security connection->ConnectionString =

"Persist Security Info=False; Integrated Security=SSPI;" "Data Source=(local); Initial Catalog=DCV_DB;";

#endif

try

{

connection->Open();

Console::WriteLine("We got a connection!");

}

catch (SqlException ^e)

{

Console::WriteLine("No connection the following error occurred: {0}", e->Message);

}

finally

{

connection->Close();

Console::WriteLine("The connection to the database has been closed");

}

}

The first thing you do (as with any other .NET application) is import the namespaces needed to access the ADO.NET basic functionality:

using namespace System;

using namespace System::Data;

using namespace System::Data::SqlClient;

For those of you using a database other than Microsoft SQL Server, use one of the following namespaces instead of System::Data::SqlClient: System::Data::OleDb, System::Data::Odbc, or System::Data::Oracle.

There is nothing special about creating a SqlConnection class. It is just a default constructor:

SqlConnection ^connection = gcnew SqlConnection();

The hardest part of this piece of coding is figuring out what the connection string is. For the SQL Server managed provider, this is fairly easy because it is usually made up of a combination of four out of six clauses:

Data Source: The location of the database server. This field will normally be (local) for your local machine, or the server name or IP address when the server is remote. Since the database is local for me, I need to use (local).

Initial Catalog: The name of the database. I am using the DCV_DB database.

Persist Security Info: Use True when security-sensitive information is returned as part of the connection. Since this is not the case in this example, I use False.

Integrated Security: When False (the default)True (or the equivalent and recommended SSPI). Since both are common, I show both types of security. Which gets implemented is determined by whether you define SQLAuth.

C H A P T E R 1 2 A D O . N E T A N D D A T A B A S E D E V E L O P M E N T

531

User ID: The user ID (not recommended with Windows Integrated Security). I use the systemdefined sa user ID, but I would recommend that you use one of your own creation.

Password: The user password (not recommended with Windows Integrated Security). I use a blank password to simplify things, but this is severely frowned upon in a production environment.

Tip You can find the connection string in the connection string property when you select the database connection in the Server Explorer.

The connection string will look like this in the code:

connection->ConnectionString =

"User ID=sa; Password=; Data Source=(local); Initial Catalog=DCV_DB;";

or

connection->ConnectionString =

"Persist Security Info=False;Integrated Security=SSPI;" "Data Source=(local); Initial Catalog=DCV_DB;";

The connection string for the Oracle managed provider is very similar to the SQL Server managed provider, whereas the OLE DB and ODBC managed providers always add an additional clause: for OLE DB, the Provider clause, and for ODBC, the Driver clause. For example:

//OLE DB Connection string connection->ConnectionString =

"Provider=SQLOLEDB; Data Source=(local); Initial Catalog=DCV_DB; " "User ID=sa; Password=;";

and

// ODBC Connection string connection->ConnectionString =

"Driver={SQL Server}; Data Source=(local); Initial Catalog=DCV_DB; " "User ID=sa; Password=;";

Note In the preceding code example, I define two of the more common connection strings I use and use the compile-time directive #ifdef SQLAuth to allow me to choose the one I want. I do this to simplify things. In most cases, it would be better not to hard-code the connection string at all and instead retrieve it from a configuration file or registry.

You open and close the database in virtually the same way as you do a file, except the Open() method doesn’t have any parameters:

connection->Open(); connection->Close();

You need to pay attention to the try statement. ADO.NET commands can abort almost anywhere, so it is always a good thing to enclose your ADO.NET logic within a try clause and capture any exceptions by catching SQLException (OleDbException, OdbcException, or OracleException).

It is also possible for ADO.NET to abort with the database still open. (Probably not in this example, but I felt having the correct code right from the beginning would make things clearer.) Therefore, it is a good idea to place your Close() method within a finally clause so that it will always be executed.

532 C H A P T E R 1 2 A D O . N E T A N D D A T A B A S E D E V E L O P M E N T

Figure 12-9 shows the results of the preceding example program. Impressive, no?

Figure 12-9. The database is successfully opened and closed.

Querying a Database

All queries made to a connected database are done using the SqlCommand, OleDbCommand, OdbcCommand, or OracleCommand class. As noted previously, the SqlCommand class provides three methods to send SQL commands to the database, with each depending on the type of command. To query the database, you need to use the ExecuteReader() method.

Before you run the ExecuteReader() method, you need to configure SqlCommand by placing the SQL command into it. There are two common ways of doing this. You can either place the SQL command, in text form, into the CommandText property or place the name of the stored procedure containing the SQL command into the same property. The default method is the command in text form. If you plan to use a stored procedure, you need to change the CommandType property to

CommandType::StoredProcedure.

Listing 12-6 shows both methods. The first command uses a text-formatted command and retrieves the contents of the Authors database for authors with a specified LastName, in this case hard-coded to “Doors”. The second command, using a stored procedure, retrieves all Stories view records where LastName equals the value passed to the stored procedure, in this case also “Doors”.

Both calls to the ExecuteReader() method after being configured return an instance of SqlDataReader, which is then iterated through to display the retrieved content.

Listing 12-6. The “Doors” Stories

using namespace System;

using namespace System::Data;

using namespace System::Data::SqlClient;

void main()

{

String ^Name = "Doors";

SqlConnection ^connection = gcnew SqlConnection();

#ifdef SQLAuth

// SQL Server authentication connection->ConnectionString =

"User ID=sa; Password=;"

"Data Source=(local); Initial Catalog=DCV_DB;";

#else

// Windows Integrated Security connection->ConnectionString =

"Persist Security Info=False; Integrated Security=SSPI;" "Data Source=(local); Initial Catalog=DCV_DB;";

#endif

C H A P T E R 1 2 A D O . N E T A N D D A T A B A S E D E V E L O P M E N T

533

try

{

SqlCommand ^cmd = gcnew SqlCommand(); cmd->Connection = connection;

cmd->CommandType = CommandType::Text; cmd->CommandText =

String::Format("SELECT FirstName, LastName FROM Authors " "WHERE LastName = '{0}'",

Name);

connection->Open();

SqlDataReader ^reader = cmd->ExecuteReader();

while(reader->Read())

{

Console::WriteLine("{0} {1}", reader["FirstName"], reader["LastName"]);

}

reader->Close();

//CREATE PROCEDURE dbo.StoriesWhereLastName

//(

//@LastName NVARCHAR(32) = NULL

//)

//AS

///* SET NOCOUNT ON */

//SELECT StoryID, Headline, Story FROM Stories

//WHERE LastName = @LastName

//

 

 

//

RETURN

 

cmd->CommandType = CommandType::StoredProcedure;

 

cmd->CommandText = "StoriesWhereLastName";

 

cmd->Parameters->Add(

 

 

gcnew SqlParameter("@LastName",SqlDbType::VarChar));

 

cmd->Parameters["@LastName"]->Value = Name;

 

reader = cmd->ExecuteReader();

 

Console::WriteLine("------------------------------------------------

");

while(reader->Read())

 

{

Console::WriteLine(reader["StoryID"]);

Console::WriteLine(reader["Headline"]);

Console::WriteLine(reader["Story"]);

Console::WriteLine();

}

reader->Close();

}

534 C H A P T E R 1 2 A D O . N E T A N D D A T A B A S E D E V E L O P M E N T

catch (SqlException ^e)

{

Console::WriteLine("No connection the following error occurred: {0}", e->Message);

}

finally

{

connection->Close();

}

}

The code to query a database with a CommandType of Text is pretty easy (if you know SQL, that is). First, you set the SqlCommand class’s CommandType property to Text:

cmd->CommandType = CommandType::Text;

Next, you place the SQL command you want to execute in the CommandText property. What makes this process easy is that you can use standard String formatting to build the command, as you see here:

cmd->CommandText =

String::Format("SELECT * FROM Authors WHERE LastName='{0}'", Name);

Finally, you run the SqlCommand class’s ExecuteReader() method. This method returns a SqlDataReader class from which you process the result set produced from the query:

SqlDataReader ^reader = cmd->ExecuteReader();

The code to query a database with a CommandType of StoredProcedure is a little more difficult if passing parameters is required. (It is a little easier if no parameters are passed, as no SQL code has to be written by the application developer.) First, you set the SqlCommand class’s CommandType property to StoredProcedure:

cmd->CommandType = CommandType::StoredProcedure;

Next, you place the name of the stored procedure you want to execute in the CommandText property:

cmd->CommandText = "StoriesWhereLastName";

Now comes the tricky part. You need to build a collection of SqlParameters, within which you will place all the parameters that you want sent to the stored procedure. The SqlCommand class provides a property called Parameters to place your collection of SqlParameters.

The first step is to use the Add() method off of the Parameters property collection to add all the SqlParameters making up all the parameters that will be passed to the stored procedure. The constructor for the SqlParameters class takes two or three parameters depending on the data type of the parameter that will be passed to the stored procedure. If the data type has a predefined length like int or a variable length like VarChar, then only two parameters are needed.

cmd->Parameters->Add(gcnew SqlParameter("@LastName", SqlDbType::VarChar));

C H A P T E R 1 2 A D O . N E T A N D D A T A B A S E D E V E L O P M E N T

535

On the other hand, if the data type needs its length specified like Char, then the third parameter is used to specify the length.

cmd->Parameters->Add(gcnew SqlParameter("@FixedSizeString",SqlDbType::Char,32));

When all the parameters are specified, you need to assign values to them so that the stored procedure can use them. You do this by assigning a value to the Value property of the indexed property, off of the Parameters property collection of the SqlCommand class. Clear as mud? The example should help:

cmd->Parameters["@LastName"]->Value = Name;

Finally, when all the parameters are assigned values, you call the SqlCommand class’s

ExecuteReader() method just like you did for a CommandType of Text:

reader = cmd->ExecuteReader();

The processing of the result set within the SqlDataReader object is handled in a forward-only manner. The basic process is to advance to the next record of the result set using the Read() method. If the return value is false, you have reached the end of the result set and you should call the Close() method to close the SqlDataReader. If the value is true, then you continue and process the next result set record.

while(reader->Read())

{

Console::WriteLine(reader["StoryID"]);

Console::WriteLine(reader["Headline"]);

Console::WriteLine(reader["Story"]);

Console::WriteLine("");

}

reader->Close();

There are two different methods of processing the record set. You can, as I did, use the indexed property to get the value based on the column header. You can also process the columns using an assortment of type-specific Getxxx() methods. The following code generates the same output as the preceding code:

while(reader->Read())

{

Console::WriteLine(reader->GetInt32(0)); Console::WriteLine(reader->GetString(1)); Console::WriteLine(reader->GetString(2)); Console::WriteLine("");

}

reader->Close();

Note the parameter passed in the position of the column starting at zero.

I personally find using column names easier, but the style you choose to use is up to you. Figure 12-10 shows the results of the preceding example program.

536 C H A P T E R 1 2 A D O . N E T A N D D A T A B A S E D E V E L O P M E N T

Figure 12-10. Retrieving Bill Doors’s stories

Insert, Update, and Delete Commands

The code to modify the database (i.e., insert, update, and delete rows of the database) isn’t much different from the code to query the database. Obviously, the SQL is different. The only other difference is that you call the SqlCommand class’s ExecuteNonQuery() method instead of the ExecuteReader() method.

You can still use both CommandTypes and you still need to set up the SQLParameters the same way for stored procedures.

In Listing 12-7 you insert a new record into the database, you change the LastName on the record, and then you delete the record. (A lot of work for nothing, don’t you think?)

Listing 12-7. Modifying the Database

using namespace System;

using namespace System::Data;

using namespace System::Data::SqlClient;

void main()

{

String ^Name = "Doors";

SqlConnection ^connection = gcnew SqlConnection();

#ifdef SQLAuth

// SQL Server authentication connection->ConnectionString =

"User ID=sa; Password=;"

"Data Source=(local); Initial Catalog=DCV_DB;";

#else

// Windows Integrated Security connection->ConnectionString =

"Persist Security Info=False; Integrated Security=SSPI;" "Data Source=(local); Initial Catalog=DCV_DB;";

#endif

try

{

SqlCommand ^cmd = gcnew SqlCommand(); cmd->Connection = connection; connection->Open();

C H A P T E R 1 2 A D O . N E T A N D D A T A B A S E D E V E L O P M E N T

537

cmd->CommandType = CommandType::StoredProcedure; cmd->CommandText = "InsertAuthor";

cmd->Parameters->Add(gcnew SqlParameter("@LastName", SqlDbType::VarChar)); cmd->Parameters->Add(gcnew SqlParameter("@FirstName",SqlDbType::VarChar));

cmd->Parameters["@LastName"]->Value = "Dope"; cmd->Parameters["@FirstName"]->Value = "John";

int affected = cmd->ExecuteNonQuery(); Console::WriteLine("Insert - {0} rows are affected", affected);

cmd->CommandType = CommandType::Text;

cmd->CommandText = "UPDATE Authors SET LastName = 'Doe'" "WHERE LastName = 'Dope'";

affected = cmd->ExecuteNonQuery();

Console::WriteLine("Update - {0} rows are affected", affected);

cmd->CommandType = CommandType::Text;

cmd->CommandText = "DELETE FROM Authors WHERE LastName = 'Doe'";

affected = cmd->ExecuteNonQuery();

Console::WriteLine("Delete - {0} rows are affected", affected);

}

catch (SqlException ^e)

{

Console::WriteLine("No connection the following error occurred: {0}", e->Message);

}

finally

{

connection->Close();

}

}

As you can see, there is not much new going on here in the C++/CLI code, other than the call to ExecuteNonQuery(). This method returns the number of rows affected by the SQL command.

int affected = cmd->ExecuteNonQuery();

Figure 12-11 shows the results of the preceding example program.

Figure 12-11. A lot of modifications to the database for no gain