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

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

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

842 CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

name itself. Given the additional compile-time checking, consider it a best practice to make use of Field<T>() when processing the roles of a EnumerableRowCollection, rather than the DataRow indexer.

Beyond the fact that we call the AsEnumerable() method, the overall format of the LINQ query is identical to what you have already seen in Chapter 14. Given this point, I won’t bother to repeat the details of the various LINQ operators here. If you wish to see additional examples, look up the topic “LINQ to DataSet Examples” using the .NET Framework 3.5 SDK documentation.

Hydrating New DataTables from LINQ Queries

It is also possible to easily populate the data of a new DataTable based on the results of a LINQ query, provided that you are not using projections. When you have a result set where the underlying type can be represented as IEnumerable<T>, you can call the CopyToDataTable<T>() extension method on the result. For example:

static void BuildDataTableFromQuery(DataTable data)

{

var cars = from car in data.AsEnumerable() where

car.Field<int>("CarID") > 5 select car;

//Use this result set to build a new DataTable.

DataTable newTable = cars.CopyToDataTable();

//Print the DataTable.

for (int curRow = 0; curRow < newTable.Rows.Count; curRow++)

{

for (int curCol = 0; curCol < newTable.Columns.Count; curCol++)

{

Console.Write(newTable.Rows[curRow][curCol].ToString().Trim() + "\t");

}

Console.WriteLine();

}

}

Note It is also possible to transform a LINQ query to a DataView type, via the AsDataView<T>() extension method.

This approach can be very helpful when you wish to use the result of a LINQ query as the source of a data binding operation. For example, the DataGridView of Windows Forms (as well as the GridView of ASP.NET) each support a property named DataSource. You could bind a LINQ result to the grid as follows:

// Assume myDataGrid is a GUI-based grid object. myDataGrid.DataSource = (from car in data.AsEnumerable()

where car.Field<int>("CarID") > 5

select car).CopyToDataTable();

Now that you have seen the role of LINQ to DataSet, let’s turn our attention to LINQ to SQL.

CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

843

Source Code The LinqOverDataSet example can be found under the Chapter 24 subdirectory.

Programming with LINQ to SQL

LINQ to SQL is an API that allows you to apply well-formed LINQ query expressions to data held within relational databases. LINQ to SQL provides a number of types (within the System.Data.Linq. dll assembly) that facilitate the communication between your code base and the physical database engine.

The major goal of LINQ to SQL is to provide consistency between relational databases and the programming logic used to interact with them. For example, rather than representing database queries using a big clunky string, we can use strongly typed LINQ queries. As well, rather than having to treat relational data as a stream of records, we are able to interact with the data using standard object-oriented programming techniques. Given the fact that LINQ to SQL allows us to integrate data access directly within our C# code base, the need to manually build dozens of custom classes and data access libraries that hide ADO.NET grunge from view is greatly minimized.

When programming with LINQ to SQL, you see no trace of common ADO.NET types such as SqlConnection, SqlCommand, or SqlDataAdapter. Using LINQ query expressions, entity classes (defined shortly) and the DataContext type, you are able to perform all the expected database CRUD (create, remove, update, and delete), as well as define transactional contexts, create new database entities (or entire databases), invoke stored procedures, and perform other database-centric activities.

Furthermore, the LINQ to SQL types (again, such as DataContext) have been developed to integrate with standard ADO.NET data types. For example, one of the overloaded constructors of DataContext takes as an input an IDbConnection-comparable object, which as you may recall is a common interface supported by all ADO.NET connection objects. In this way, existing ADO.NET data access libraries can integrate with C# 2008 LINQ query expressions (and vice versa). In reality, as far as Microsoft is concerned, LINQ to SQL is simply a new member of the ADO.NET family.

The Role of Entity Classes

When you wish to make use of LINQ to SQL within your applications, the first step is to define entity classes. In a nutshell, entity classes are types that represent the relational data you wish to interact with. Programmatically speaking, entity classes are class definitions that are annotated with various LINQ to SQL attributes (such as [Table] and [Column]) that map to a physical table in a specific database. A majority of the LINQ to SQL attributes are defined with the System.Data.Linq.Mapping namespace (see Figure 24-2).

As you will see in just a bit, the .NET Framework 3.5 SDK (as well as Visual Studio 2008) ships with tools that automate the construction of the entity types required by your application. Until that point, our first LINQ to SQL example will illustrate how to build entity classes by hand.

844 CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

Figure 24-2. The System.Data.Linq.Mapping namespace defines numerous LINQ to SQL attributes.

The Role of the DataContext Type

Once you have defined your entity classes, you are then able to pass your query expressions to the relational database using a DataContext type. This LINQ to SQL–specific class type is in charge of translating your LINQ query expressions into proper SQL queries as well as communicating with the specified database. In some ways, the DataContext looks and feels like an ADO.NET connection object, in that it requires a connection string. However, unlike a typically connection object, the DataContext type has numerous members that will map the results of your query expressions back into the entity classes you define.

Furthermore, the DataContext type defines a factory pattern to obtain instances of the entity classes used within your code base. Once you obtain an entity instance, you are free to change its state in any way you desire (adding records, updating records, etc.) and submit the modified object back for processing. In this way, the DataContext is similar to an ADO.NET data adapter type.

A Simple LINQ to SQL Example

Before we dive into too many details, let’s see a simple example of using LINQ to SQL to interact with the Inventory table of the AutoLot database created in Chapter 22. In this example, we will not be making use of our AutoLotDAL.dll library, but will instead author all the code by hand. Create a new Console Application named SimpleLinqToSqlApp and reference the System.Data.Linq.dll assembly.

Next, insert a new C# class file named Inventory.cs. This file will define our entity class, which requires decorating the type with various LINQ-centric attributes; therefore, be sure to specify you are using the System.Data.Linq.Mapping and System.Data.Linq namespaces. With this detail out of the way, here is the definition of the Inventory type:

[Table]

public class Inventory

{

CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

845

[Column]

public string Make; [Column]

public string Color; [Column]

public string PetName;

// Identify the primary key.

[Column(IsPrimaryKey = true)] public int CarID;

public override string ToString()

{

return string.Format("ID = {0}; Make = {1}; Color = {2}; PetName = {3}", CarID, Make.Trim(), Color.Trim(), PetName.Trim());

}

}

First of all notice that our entity class has been adorned with the [Table] attribute, while each public field has been marked with [Column]. In both cases, the names are a direct mapping to the physical database table. However, this is not a strict requirement, as the TableAttribute and ColumnAttribute types both support a Name property that allows you to decouple your programmatic representation of the data table from the physical table itself. Also notice that the CarID field has been further qualified by setting the IsPrimaryKey property of the ColumnAttribute type using named property syntax.

Here, for simplicity, each field has been declared publicly. If you require stronger encapsulation, you could most certainly define private fields wrapped by public properties (or automatic properties if you so choose). If you do so, it will be the property, not the fields, that will be marked with the [Column] attribute.

It is also worth pointing out that an entity class can contain any number of members that do not map to the data table it represents. As far as the LINQ runtime is concerned, only items marked with LINQ to SQL attributes will be used during the data exchange. For example, this Inventory class definition provides a custom implementation of ToString() to allow the application to quickly display its state.

Now that we have an entity class, we can make use of the DataContext type to submit (and translate) our LINQ query expressions to the specified database. Ponder the following Main() method, which will display the result of all items in the Inventory table maintained by the AutoLot database:

class Program

{

const string cnStr =

@"Data Source=(local)\SQLEXPRESS;Initial Catalog=AutoLot;" + "Integrated Security=True";

static void Main(string[] args)

{

Console.WriteLine("***** LINQ to SQL Sample App *****\n");

//Create a DataContext object.

DataContext db = new DataContext(cnStr);

//Now create a Table<> type.

Table<Inventory> invTable = db.GetTable<Inventory>();

846 CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

// Show all data using a LINQ query.

Console.WriteLine("-> Contents of Inventory Table from AutoLot database:\n"); foreach (var car in from c in invTable select c)

Console.WriteLine(car.ToString());

Console.ReadLine();

}

}

Notice that when you create a DataContext type, you will feed in a proper connection string, which is represented here as a simple string constant. Of course, you are free to store this in an application configuration file and/or make use of the SqlConnectionStringBuilder type to treat this string type in a more object-oriented manner.

Next up, we obtain an instance of our Inventory entity class by calling the generic GetTable<T>() method of the DataContext type, specifying the entity class as the type parameter when doing so. Finally, we build a LINQ query expression and apply it to the invTable object. As you would expect, the end result is a display of each item in the Inventory table.

Building a Strongly Typed DataContext

While our first example is strongly typed as far as the database query is concerned, we do have a bit of a disconnect between the DataContext and the Inventory entity class it is maintaining. To remedy this situation, it is typically preferable to create a class that extends the DataContext type that defines member variables for each table it operates upon. Insert a new class called AutoLotDatabase, specify you are using the System.Core and System.Data.Linq namespaces, and implement the type as follows:

class AutoLotDatabase : DataContext

{

public Table<Inventory> Inventory;

public AutoLotDatabase(string connectionString) : base(connectionString){}

}

With this new class type, we are now able to simplify the code within Main() quite a bit:

static void Main(string[] args)

{

Console.WriteLine("***** LINQ to SQL Sample App *****\n");

//Create an AutoLotDatabase object.

AutoLotDatabase db = new AutoLotDatabase(cnStr);

//Note we can now use the Inventory field of AutoLotDatabase.

Console.WriteLine("-> Contents of Inventory Table from AutoLot database:\n"); foreach (var car in from c in db.Inventory select c)

Console.WriteLine(car.ToString());

Console.ReadLine();

}

One aspect of building a strongly typed data context that may surprise you is that the DataContext-derived type (AutoLotDatabase in this example) does not directly create the Table<T> member variables and has no trace of the expected GetTable() method call. At runtime, however, when you iterate over your LINQ result set, the DataContext will create the Table<T> type transparently in the background.

CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

847

Of course, any LINQ query can be used to obtain a given result set. Assume we have authored the following helper method that is called from Main() before exiting (note this method expects us to pass in an AutoLotDatabase instance):

static void ShowOnlyBimmers(AutoLotDatabase db)

{

Console.WriteLine("***** Only BMWs *****\n");

// Get the BMWs.

var bimmers = from s in db.Inventory where s.Make == "BMW"

orderby s.CarID select s;

foreach (var c in bimmers) Console.WriteLine(c.ToString());

}

Figure 24-3 shows the output of this first LINQ to SQL example.

Figure 24-3. A first look at LINQ to SQL

Source Code The SimpleLinqToSqlApp example can be found under the Chapter 24 subdirectory.

The [Table] and [Column] Attributes: Further Details

As you have seen, entity classes are adorned with various attributes that are used by LINQ to SQL to translate queries for your objects into SQL queries against the database. At absolute minimum, you will make use of the [Table] and [Column] attributes; however, additional attributes exist to mark the methods that perform SQL insert, update, and delete commands. As well, each of the LINQ to SQL attributes defines a set of properties that further qualify to the LINQ to SQL runtime engine how to process the annotated item.

The [Table] attribute is very simple, given that it only defines a single property of interest: Name. As mentioned, this allows you to decouple the name of the entity class from the physical table.

848CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

If you do not set the Name property at the time you apply the [Table] attribute, LINQ to SQL assumes the entity class and database table names are one and the same.

The [Column] attribute provides a bit more meat than [Table]. Beyond the IsPrimaryKey property, ColumnAttribute defines additional members that allow you to fully qualify the details of each field in the entity class and how it maps to a particular column in the physical database table.

Table 24-1 documents the additional properties of interest.

Table 24-1. Select Properties of the [Column] Attribute

ColumnAttribute Property

Meaning in Life

CanBeNull

This property indicates that the column can contain null values.

DbType

LINQ to SQL will automatically infer the data types to pass to the

 

database engine based on declaration of your field data. Given this, it

 

is typically only necessary to set DbType directly if you are dynamically

 

creating databases using the CreateDatabase() method of the

 

DataContext type.

IsDbGenerated

This property establishes that a field’s value is autogenerated by the

 

database.

IsVersion

This property identifies that the column type is a database timestamp

 

or a version number. Version numbers are incremented and timestamp

 

columns are updated every time the associated row is updated.

UpdateCheck

This property controls how LINQ to SQL should handle database

 

conflicts via optimistic concurrency.

 

 

Generating Entity Classes Using SqlMetal.exe

Our first LINQ to SQL example was fairly simplistic, partially due to the fact that our DataContext was operating on a single data table. A production-level LINQ to SQL application may instead be operating on multiple interrelated data tables, each of which could define dozens of columns. In these cases, it would be very tedious to author each and every required entity class by hand. Thankfully, we do have two approaches to generate these types automatically.

The first option is to make use of the sqlmetal.exe command-line utility, which can be executed using a Visual Studio 2008 command prompt. This tool automates the creation of entity classes by generating an appropriate C# class type from the database metadata. While this tool has numerous command-line options, Table 24-2 documents the major flags of interest.

Table 24-2. Options of the sqlmetal.exe Command

sqlmetal.exe Command-Line

 

Option

Meaning in Life

/server

Specifies the server hosting the database

/database

Specifies the name of the database to read metadata from

/user

Specifies user ID to log in to the server

/password

Specifies password to log in to the server

/views

Informs sqlmetal.exe to generate code based on existing database

 

views

/functions

Informs sqlmetal.exe to extract database functions

/sprocs

Informs sqlmetal.exe to extract stored procedures

CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

849

sqlmetal.exe Command-Line

 

Option

Meaning in Life

/code

Informs sqlmetal.exe to output results as C# code (or as VB, if you

 

set the /language flag)

/language

Specifies the language used to defined the generated types

/namespace

Specifies the namespace to define the generated types

 

 

By way of an example, the following command set will generate entity classes for each table within the AutoLot database, expose the GetPetName stored procedure, and wrap all generated C# code within a namespace named AutoLotDatabase (of course, this would be entered on a single line within a Visual Studio 2008 command prompt):

sqlmetal /server:(local)\SQLEXPRESS /database:AutoLot /namespace:AutoLotDatabase /code:autoLotDB.cs /sprocs

Once you have executed the command, create a new Console Application named LinqWithSqlMetalGenedCode, reference the System.Data.Linq.dll assembly, and include the autoLotDB.cs file into your project using the Project Add Existing Item menu option. As well, insert a new class diagram into your project (via Project Add New Item) and expand each of the generated classes (see Figure 24-4).

Figure 24-4. The sqlmetal.exe-generated entity classes

850 CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

Notice that you have a new type extending DataContext that contains properties for each data table in the specified database (as well, notice that the GetPetName() stored procedure is represented by a public method of the same name). Before we program against these new types, let’s examine this autogenerated code in a bit more detail.

Examining the Generated Entity Classes

As you can see, sqlmetal.exe defined a separate entity class for each table in the AutoLot database (Inventory, Customers, Orders, CreditRisks), with each column encapsulated by a type property. In addition, notice that each entity class implements two interfaces (INotifyPropertyChanging and INotifyPropertyChanged), each of which defines a single event:

namespace System.Data.Linq

{

public interface INotifyPropertyChanging

{

// This event fires when a property is being changed. event PropertyChangedEventHandler PropertyChanging;

}

}

namespace System.ComponentModel

{

public interface INotifyPropertyChanged

{

// This event fires when a property value has changed. event PropertyChangedEventHandler PropertyChanged;

}

}

Collectively, these interfaces define a total of two events named PropertyChanging and

PropertyChanged, both of which work in conjunction with the PropertyChangedEventHandler delegate defined in the System.ComponentModel namespace. This delegate can call any method taking an object as the first parameter and a PropertyChangedEventArgs as the second. Given the interface contract, each entity class supports the following members:

[Table(Name="Inventory")]

public partial class Inventory : INotifyPropertyChanging, INotifyPropertyChanged

{

public event PropertyChangedEventHandler PropertyChanging; public event PropertyChangedEventHandler PropertyChanged;

...

}

If you were to examine the implementation of the properties of any of the three entity classes, you will note that the set scope fires each event to any interested listener. By way of an example, here is the PetName property of the Inventory type:

[Column(Storage="_PetName", DbType="VarChar(50)")] public string PetName

{

get

{

return this._PetName;

}

set

{

CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

851

if ((this._PetName != value))

{

this.OnPetNameChanging(value);

this.SendPropertyChanging(); this._PetName = value; this.SendPropertyChanged("PetName"); this.OnPetNameChanged();

}

}

}

Notice that the set scope invokes the OnPetNameChanging() and OnPetNameChanged() methods on the entity class type to actually fire the events themselves. However, these members are defined as partial methods, which you may recall from Chapter 13 perform a type of lightweight event handling, allowing interested callers to provide an implementation if they so choose (if not, they are removed from the type definition at compile time):

partial void OnPetNameChanging(string value); partial void OnPetNameChanged();

Defining Relationships Using Entity Classes

Beyond simply defining properties with backing fields to represent data table columns, the sqlmetal.exe utility will also model the relationships between interrelated tables using the EntitySet<T> type. Recall from Chapter 22 that the AutoLot database defined three interrelated tables, connected by primary and foreign keys. Rather than forcing us to author SQL-centric join syntax to navigate between these tables, LINQ to SQL allows us to navigate using the object-centric C# dot operator.

To account for this sort of table relationship, the parent entity class may encode the child table as property references. This property is marked with the [Association] attribute to establish an association relationship made by matching column values between tables. For example, consider the (partial) generated code for the Customer type, which can have any number of orders:

[Table(Name="Customers")] public partial class Customers :

INotifyPropertyChanging, INotifyPropertyChanged

{

private EntitySet<Orders> _Orders;

[Association(Name="FK_Orders_Customers", Storage="_Orders", OtherKey="CustID", DeleteRule="NO ACTION")]

public EntitySet<Orders> Orders

{

get { return this._Orders; }

set { this._Orders.Assign(value); }

}

...

}

Here, the Orders property is understood by the LINQ to SQL runtime engine as the member that allows navigation from the Customers table to the Orders table via the column defined by the OtherKey named property. The EntitySet<T> member variable is used to represent the one-to-many nature of this particular relationship.