饭饭TXT > 学习管理 > 《Visual Studio 2010 高级编程(英文出书版)》作者:Nick Randolph/等【完结】 > [Visual.Studio.2010.高级编程].Professional.Visual.Studio.2010.txt

第 87 页

作者:Nick Randolph/等 当前章节:15405 字 更新时间:2026-6-18 14:51

If orders.Length > 0 Then

Dim cr As New SearchResult

cr.Customer = c.FirstName & " " & c.LastName

For Each o As Order In orders

cr.Quantity += o.Quantity

cr.Count += 1

Next

results.Add(cr)

End If

End If

Next

results.Sort(AddressOf CompareSearchResults)

ObjectDumper.Write(results, Writer)

End Sub

Code snippet MainForm.vb

Before we jump in and show how LINQ can improve this snippet, let’s examine how this snippet

works. The opening line calls out to a method that simply generates Customer objects. This will

be used throughout the snippets in this chapter. The main loop in this method iterates through

the array of customers searching for those customers with a first name longer than five characters.

Upon finding such a customer, you use the Array.FindAll method to retrieve all orders where the

predicate is true. Prior to the introduction of anonymous methods you couldn’t supply the predicate

function inline with the method. As a result, the usual way to do this was to create a simple class

that could hold the query variable (in this case, the product, Milk) that you were searching for,

and that had a method that accepted the type of object you were searching through, in this case an

Order. With the introduction of Lambda expressions, you can now rewrite this line:

590 .

chaPter 28 lAnguAge inTegrATed QuerieS (linQ)

c#

var orders = Array.FindAll(c.Orders, order=>order.Product =="Milk");

Vb

Dim orders = Array.FindAll(c.Orders,

Function(o As Order) o.Product = "Milk")

Here you have also taken advantage of type inferencing to determine the type of the variable orders,

which is of course still an array of orders.

Returning to the snippet, once you have located the orders you still need to iterate through them and

sum up the quantity ordered and store this, along with the name of the customer and the number of

orders. This is your search result, and as you can see you are using a SearchResult object to store

this information. For convenience, the SearchResult object also has a read-only Average property,

which simply divides the total quantity ordered by the number of orders. Because you want to

sort the customer list, you use the Sort method on the List class, passing in the address of a

comparison method. Again, using Lambda expressions, this can be rewritten as an inline statement:

c#

results.Sort((r1, r2) => string.Compare(r1.Customer, r2.Customer));

Vb

results.Sort( Function(r1 as SearchResult, r2 as SearchResult) _

String.Compare(r1.Customer, r2.Customer))

The last part of this snippet is to print out the search

results. This is using one of the samples that ships with

Visual Studio 2010 called ObjectDumper. This is a simple

class that iterates through a collection of objects printing

out the values of the public properties. In this case the

output would look like Figure 28-2.

fiGure 28-2

As you can see from this relatively simple query, the code

to do this in the past was quite prescriptive and required additional classes in order to carry out the

query logic and return the results. With the power of LINQ you can build a single expression that

clearly describes what the search results should be.

query Pieces

This section introduces you to a number of the query operations that make up the basis of LINQ. If

you have written SQL statements, these will feel familiar, although the ordering and syntax might

take a little time to get used to. You can use a number of query operations, and numerous reference

web sites provide more information on how to use them. For the moment, you will focus on those

operations necessary to improve the search query introduced at the beginning of this chapter.

Query Pieces .

591

from

Unlike SQL, where the first statement is Select, in LINQ the first statement is typically From.

One of the key considerations in the creation of LINQ was providing IntelliSense support

within Visual Studio 2010. If you’ve ever wondered why there is no IntelliSense support in SQL

Management Studio for SQL Server 2005 for writing queries, this is because to determine what

to select, you need to know where the data is coming from. By reversing the order of the statements,

LINQ is able to generate IntelliSense as soon as you start typing.

As you can see from the tooltip in Figure 28-3,

the From statement is made up of two parts,

< element > and < collection > . The latter is

the source collection from which you will be

extracting data, and the former is essentially

fiGure 28-3

an iteration variable that can be used to refer

to the items being queried. This pair can then be repeated for each source collection.

In this case you can see you are querying the customers collection, with an iteration variable c, and

the orders collection c.Orders using the iteration variable o. There is an implicit join between the

two source collections because of the relationship between a customer and that customer’s orders.

As you can imagine, this query will result in the cross-product of items in each source collection.

This will lead to the pairing of a customer with each order that this customer has.

Note that you don’t have a Select statement, because you are simply going to return all elements,

but what does each result record look like? If you were to look at the tooltip for results, you

would see that it is a generic IEnumerable of an anonymous type. The anonymous type feature is

heavily used in LINQ so that you don’t have to create classes for every result. If you recall from

the initial code, you had to have a SearchResult class in order to capture each of the results.

Anonymous types mean that you no longer have to create a class to store the results. During

compilation, types containing the relevant properties are dynamically created, thereby giving

you a strongly typed result set along with

IntelliSense support. Though the tooltip

for results may report only that it is an

IEnumerable of an anonymous type, when

you start to use the results collection you

will see that the type has two properties,

c and o, of type Customer and Order,

respectively. Figure 28-4 displays the output

of this code, showing the customer-order

pairs.

fiGure 28-4

C# actually requires a Select clause to be present in all LINQ, even if you are

returning all objects in the From clause.

592 .

chaPter 28 lAnguAge inTegrATed QuerieS (linQ)

select

In the previous code snippet the result set was a collection of customer-order pairs, when in fact

what you want to return is the customer name and the order information. You can do this by using

a Select statement in a way similar to the way you would when writing a SQL statement:

c#

private void LinqQueryWithSelect(){

var customers = BuildCustomers();

var results = from c in customers

from o in c.Orders

select new{c.FirstName,

c.LastName,o.Product,o.Quantity};

ObjectDumper.Write(results, Writer);

}

Code snippet MainForm.cs

Vb

Private Sub LinqQueryWithSelect()

Dim customers = BuildCustomers()

Dim results = From c In customers, o In c.Orders

Select c.FirstName, c.LastName, o.Product, o.Quantity

ObjectDumper.Write(results, Writer)

End Sub

Code snippet MainForm.vb

Now when you execute this code the result set

is a collection of objects that have FirstName,

LastName, Product, and Quantity properties. This

is illustrated in the output shown in Figure 28-5.

where

So far all you have seen is how you can effectively

flatten the customer-order hierarchy into a result set

containing the appropriate properties. What you haven’t done is filter these results so that they only

return customers with a first name greater than or equal to five characters, and who are ordering

Milk. The following snippet introduces a Where statement, which restricts the source collections on

both these axes:

c#

private void LinqQueryWithWhere(){

var customers = BuildCustomers();

var results = from c in customers

fiGure 28-5

Query Pieces .

593

from o in c.Orders

where c.FirstName.Length >= 5 &&

o.Product == "Milk"

select new { c.FirstName, c.LastName, o.Product, o.Quantity };

ObjectDumper.Write(results, Writer);

}

Code snippet MainForm.cs

Vb

Private Sub LinqQueryWithWhere()

Dim customers = BuildCustomers()

Dim results = From c In customers, o In c.Orders

Where c.FirstName.Length >= 5 And

o.Product = "Milk"

Select c.FirstName, c.LastName, o.Product, o.Quantity

ObjectDumper.Write(results, Writer)

End Sub

Code snippet MainForm.vb

The output of this query is similar to the previous one in that it is a result set of an anonymous type

with the four properties FirstName, LastName, Product, and Quantity.

Group by

You are getting close to your initial query, except that your current query returns a list of all the

Milk orders for all the customers. For a customer who might have placed two orders for Milk, this

will result in two records in the result set. What you actually want to do is to group these orders by

customer and take an average of the quantities ordered. Not surprisingly, this is done with a Group

By statement, as shown in the following snippet:

c#

private void LinqQueryWithGroupingAndWhere(){

var customers = BuildCustomers();

var results = from c in customers

from o in c.Orders

where c.FirstName.Length >= 5 &&

o.Product == "Milk"

group o by c into avg

select new { avg.Key.FirstName, avg.Key.LastName,

avg = avg.Average(o => o.Quantity) };

ObjectDumper.Write(results, Writer);

}

Code snippet MainForm.cs

594 .

chaPter 28 lAnguAge inTegrATed QuerieS (linQ)

Vb

Private Sub LinqQueryWithGroupingAndWhere()

Dim customers = BuildCustomers()

Dim results = From c In customers, o In c.Orders _

Where c.FirstName.Length >= 5 And _

o.Product = "Milk" _

Group By c Into avg = Average(o.Quantity) _

Select c.FirstName, c.LastName, avg

ObjectDumper.Write(results)

End Sub

Code snippet MainForm.vb

What is a little confusing about the Group By statement is the syntax that it uses. Essentially, what it

is saying is “group by dimension X” and place the results “Into” an alias that can be used elsewhere.

In this case the alias is avg, which will contain the average you are interested in. Because you are

grouping by the iteration variable c, you can still use this in

the Select statement, along with the Group By alias. Note

that the C# example is slightly different in that although the

grouping is still done on c, you then have to access it via

the Key property of the alias. Now when you run this you

get the output shown in Figure 28-6, which is much closer

to your initial query.

custom Projections

fiGure 28-6

You still need to tidy up the output so that

you are returning a well-formatted customer

name and an appropriately named average

property, instead of the query results,

FirstName, LastName, and avg. You can do

this by customizing the properties that

fiGure 28-7

are contained in the anonymous type

that is created as part of the Select statement

projection. Figure 28-7 shows how you can create

anonymous types with named properties.

This figure also illustrates that the type of the AverageMilkOrder property is indeed a Double,

which is what you would expect based on the use of the Average function. It is this strongly typed

behavior that can really assist you in the creation and use of rich LINQ statements.

order by

The last thing you have to do with the LINQ statement is to order the results. You can do this by

ordering the customers based on their FirstName property, as shown in the following snippet:

Query Pieces .

595

c#

private void LinqQueryWithGroupingAndWhere(){

var customers = BuildCustomers();

var results = from c in customers

from o in c.Orders

orderby c.FirstName

where c.FirstName.Length >= 5 &&

o.Product == "Milk"

group o by c into avg

select new { Name = avg.Key.FirstName + " " + avg.Key.LastName,

AverageMilkOrder = avg.Average(o => o.Quantity) };

ObjectDumper.Write(results, Writer);

}

Code snippet MainForm.cs

Vb

Private Sub FinalLinqQuery()

Dim customers = BuildCustomers()

Dim results = From c In customers, o In c.Orders

Order By c.FirstName

Where c.FirstName.Length >= 5 And

o.Product = "Milk

Group By c Into avg = Average(o.Quantity)

Select New With {.Name = c.FirstName & " " & c.LastName,

.AverageMilkOrder = avg}

ObjectDumper.Write(results)

End Sub

Code snippet MainForm.vb

One thing to be aware of is how you can easily reverse the order of the query results. Here you can

do this either by supplying the keyword Descending (Ascending is the default) at the end of the

Order By statement, or by applying the Reverse transformation on the entire result set:

Order By c.FirstName Descending

or

ObjectDumper.Write(results.Reverse)

As you can see from the final query you have built up, it is much more descriptive than the initial

query. You can easily see that you are selecting the customer name and an average of the order

quantities. It is clear that you are filtering based on the length of the customer name and on

orders for Milk, and that the results are sorted by the customer’s first name. You also haven’t

needed to create any additional classes to help perform this query.

596 .

chaPter 28 lAnguAge inTegrATed QuerieS (linQ)

debuGGinG and execution

One of the things you should be aware of with LINQ is that the queries are not executed until they

are used. In fact, each time you use a LINQ query you will find that the query is re-executed. This

目录
设置
设置
阅读主题
字体风格
雅黑 宋体 楷书 卡通
字体大小
适中 偏大 超大
保存设置
恢复默认
手机
手机阅读
扫码获取链接,使用浏览器打开
书架同步,随时随地,手机阅读
首 页 < 上一章 章节列表 下一章 > 尾 页