You can query Entity Data Models in a variety of ways. Some ways you will choose for personal preference and others you will choose so that you can leverage particular benefits.
In this chapter, you will finally get to write some code and retrieve some data by writing queries using the two query syntaxes that the Entity Framework provides: LINQ to Entities and Entity SQL. In addition, you will learn about query syntax versus method-based syntax, including the Entity Framework’s query builder methods. By the end of the chapter, you will have gained a high-level understanding of all of the query options and their basic uses. In further chapters, you will write more complex queries; the foundation you will receive from this chapter will make that task much easier.
In addition, at the end of this chapter you’ll find an extremely important section on query execution. Understanding the possibility of inadvertently executing a query in your Entity Framework applications can help you to avoid performance problems caused by your application unknowingly and unnecessarily making calls to the database.
Here is where you will experience the difference between writing
queries against a data model rather than the database. In this chapter,
you will learn how to construct queries against the EDM that you created
in Chapter 2, and you will
learn to let the Entity Framework take it from there. The Entity
Framework will process your queries and will leverage the ADO.NET
provider—in this case, System.Data.SqlClient
—to turn the EDM query
into a query the target database will comprehend. After the database has
executed the query, the results will be turned into objects that are
based on the entities in the model.
These returned objects are an important piece of the querying process, but surely you want to start querying, so first we’ll query and then we’ll take a peek under the covers.
In Chapter 2, you created
an EDM inside a Console Application. Here you’ll create your first
queries in that same project, so if you’ve closed it, open it and let’s
get started. The code in this section will execute the simplest form of
a query, which happens to be a shortcut to a ObjectQuery
, and then will display the results
in a console window.
Open the Module1.vb or Program.cs file.
Add the method in Example 3-1 beneath the
Main
method. IntelliSense will
assist you as you type. After you’ve written a few basic queries,
you’ll make the code a little more efficient.
Example 3-1. Querying Contacts and writing out their names
VB
Private Sub QueryContacts()
Using context As New ProgrammingEFDB1Entities
Dim contacts = context.Contacts
For Each contact In contacts
Console.WriteLine("{0} {1} {2}", _
contact.Title.Trim, contact.FirstName.Trim, _
contact.LastName.Trim)
Next
End Using
Console.Write("Press Enter...")
Console.ReadLine()
End Sub
C#
static private void QueryContacts()
{
using (var context = new ProgrammingEFDB1Entities())
{
var contacts = context.Contacts;
foreach (var contact in contacts)
{
Console.WriteLine("{0} {1} {2}",
contact.Title.Trim(),contact.FirstName.Trim(),
contact.LastName);
}
}
Console.Write("Press Enter...");
Console.ReadLine();
}
Add the following code into the Main
method:
VB
QueryContacts
C#
QueryContacts();
Press F5 to run this bit of code. When the code hits the
ReadLine()
method, all of the
names are listed in the console window.
You have just executed your first query against an EDM and seen the objects that result.
Press the Enter key to finish running the app.
Now you’ll run the query again, but this time you’ll look at some of what’s going on:
Set a breakpoint at the end of the For Each
/foreach
block. For VB the breakpoint is
Next
, and for C# it’s the closing
brace (}
).
Press F5 to run the code again.
When the debugger reaches the breakpoint, hover your mouse
pointer over the word con and you will see that
it is a Contact
entity (see Figure 3-1).
Next, hover your mouse pointer over the word
contacts in that same statement and you’ll see
that its type is a System.Data.Objects.ObjectQuery
of
Contact
types.
System.Data.Objects
is the
Entity Framework’s API for creating and managing entity objects. The
ObjectQuery
is what the Entity
Framework uses to construct and execute queries that will return
objects. Once the ObjectQuery
has
been executed, it contains results, which were all of the contacts
you saw listed in the console. Because you asked only for the
EntitySet
and did not request any
filtering, all of the contacts were retrieved from the database when
the query was executed.
Although this doesn’t really look like a query, it is—it’s just a very simple one. You’ll take a closer look at this after the next query.
You can continue the application or stop it by pressing Shift-F5.
The preceding query used a shortcut that produced a query for you. But it didn’t really feel like a query. Now you’ll write an actual query using LINQ to Entities.
Remove the breakpoint that you set in the previous steps. In the
line of code that created the contacts
memory variable, replace context.Contacts
with the query in Example 3-2, which retrieves a subset of the
contacts.
Example 3-2. A LINQ to Entities query
VB
Dim contacts=From c In context.Contacts
Where c.FirstName = "Robert"
C#
var contacts = from c in context.Contacts
where c.FirstName == "Robert" select c;
You’ll find many differences between VB and C# syntax when
writing LINQ queries. Besides the casing, notice that VB does not
require that you explicitly use the Select
operator, whereas C# does.
Run the application again and you will see that only a small number of contacts are listed and they all have Robert as their first name.
Since you just dove right into the code, you might have a few
questions. For instance, where did the Contact
type come from? How did you go from
an XML file (the EDMX file) to strongly typed .NET objects? Why is
context.Contacts
a query, and what
is that context anyway?
One of the features of the EDM Designer tools is that the Designer automatically performs code generation based on the model. If you set the Solution Explorer to Show All Files, you’ll see an extra code file attached to the model, as shown in Figure 3-2.
Expand the .edmx file in the Solution Explorer to see the generated code file. Open the file to see what’s in there.
Because the file is generated automatically, you don’t want to edit it directly. You’ll learn how to customize the classes in this file in Chapter 10.
The generated code file contains four classes. Figure 3-3 shows these
classes in Visual Studio’s Class Designer view. The first is ProgrammingEFDB1Entities
, which has taken
the model’s EntityContainer
name.
The others are for each entity—Address
, Contact
, and vOfficeAddresses
.
When you looked at the XML view of the model in Chapter 2, you saw an EntityContainer
that
contained the EntitySet
s and
AssociationSet
s.
The ProgrammingEFDB1Entities
class represents
that EntityContainer
and inherits
from an Entity Framework type called ObjectContext
. This is why context
is used for the variable in the
example. ProgrammingEFDB1Entities
has three properties—Addresses
,
Contacts
, and vOfficeAddresses
—which are the EntitySet
s defined in the model. The three
AddTo
methods were created by the
code generator to support the entity classes and the ObjectContext
itself.
Looking more closely at the Contacts
property, you can see that it
returns an ObjectQuery
of Contact
types:
VB
Public ReadOnly Property Contacts() As _
Global.System.Data.Objects.ObjectQuery(Of Contact)
C#
public global::System.Data.Objects.ObjectQuery<Contact> Contacts
This is why context.Contacts
in the first example is a
query even though you didn’t write any query-specific code.
The three entities defined in the model are the source for the
three entity classes. Each class inherits from the Entity
Framework’s EntityObject
class
and has properties based on the properties defined in the model,
including the Contact.Addresses
and Address.Contact
navigation
properties (see Figure 3-4).
But there’s something new in there, ContactReference
, which is another way to
access the Contact
property.
You’ll learn more about Reference
properties in detail in Chapter 15. These classes
have more members, but as they are not relevant to the querying
you’ll do in this chapter, we will dissect them later in the
book.
Dig deeper: don’t be afraid to poke around in the generated code file, but remember that any changes you make will be overwritten anytime the model is modified and saved.
The LINQ to Entities query syntax is easier to learn and to use than Entity SQL, and possibly already familiar to you if you have been using LINQ elsewhere in Visual Studio 2008. LINQ to Entities will very likely cover a large portion of your query needs. We’ll start with this first.
LINQ is a new language enhancement that was added to Visual Basic and C# in Visual Studio 2008. LINQ stands for Language INtegrated Query, and LINQ to Entities is one of its implementations.
LINQ was originally written to query in-memory CLR objects, but there are now many implementations of it. You just used an implementation created to work with entity objects. Visual Studio 2008 also includes LINQ to SQL, an implementation that queries directly against SQL Server databases. Many third parties are also writing LINQ implementations.
It is possible to get very creative with LINQ queries, and you will easily find a number of books devoted entirely to LINQ. When you’re starting out it’s helpful to understand the basic structure.
The query you wrote in Example 3-2
is a LINQ to Entities query.The most obvious sign of integration in LINQ
queries is that as you typed your query, you had the benefit of
IntelliSense assisting you—for example, providing LastName
as an option for the c
variable. That was because when you
identified the Contacts EntitySet
at
the beginning of the query, the compiler was able to determine that the
items in that collection are Contact
items. When you typed c
later in
the query in the SELECT
and WHERE
clauses, IntelliSense was able to
present a list of Contact
properties
in the IntelliSense suggestions.
In the query, c
is just a
random variable name that lets you reference the
thing you are working with further on in the query.
It’s referred to as a control variable. The control
variable provides another means by which IntelliSense and the compiler
are able to make LINQ more powerful for developers.
You’ve seen that ProgrammingEFDB1.Contacts
returns an
ObjectQuery
of Contact
types. This is evident in the
generated code as well as at runtime.
There is a small twist on this that you may have noticed. At
design time, when you hover your mouse pointer over the contacts
variable that the LINQ to Entities
query returns, the DataTip doesn’t say that contacts
is an ObjectQuery
. Instead, it says that contacts
is an IQueryable
.
IQueryable
is a LINQ query
type. At design time, the compiler recognizes the LINQ query and does
its best to tell you its return type. The compiler doesn’t realize
that because it is a LINQ to Entities query, it
will be processed by the Entity Framework and will result in an
ObjectQuery
. ObjectQuery
implements IQueryable
, so the two are very closely
related.
IQueryable
contains metadata
about the query, such as the query expression and the provider being
used. ObjectQuery
is an IQueryable
with additional query details
that are specific to Entity Framework queries.
At runtime, you’ll see evidence of this again if you open the
contacts
query variable in the
QuickWatch window (see Figure 3-5).
Once an IQueryable
(and an
ObjectQuery
because it implements
IQueryable
) has been executed, it
contains its query metadata as well as the new query results.
Don’t let this confuse you when LINQ to Entities displays its
type as IQueryable
in some places
and ObjectQuery
in others.
The results are described as an “enumerable type,” based on the
class IEnumerable
, which is similar
to a Collection
. An IEnumerable
allows you to enumerate or
iterate through each item in the collection as you did in the
preceding code sample (i.e., in For
Each
/foreach
). A Collection
is an enhanced IEnumerable
. Whereas an IEnumerable
is
read-only, the more familiar Collection
class allows you to perform
additional actions, such as adding or removing items from the
group.
A LINQ to Entities query implicitly creates an ObjectQuery
, whether written in Visual Basic
or C#. There is one other way to create an ObjectQuery
and that is by using the Entity
Framework’s Object Services (in the System.Data.Objects
namespace) directly. When
you create an ObjectQuery
directly,
you will use the Entity Framework’s T-SQL-like query language, called
Entity SQL, to build the query expression.
To see how this works, modify your example with the following steps:
Replace (or comment out) the line of code containing the LINQ to Entities query with the code in Example 3-3.
Example 3-3. Querying with Entity SQL
VB
Dim qStr = "SELECT VALUE c " & _
"FROM ProgrammingEFDB1Entities.Contacts AS c " & _
"WHERE c.FirstName='Robert'"
Dim contacts = context.CreateQuery(Of Contact)(qStr)
C#
var qStr = "SELECT VALUE c " +
"FROM ProgrammingEFDB1Entities.Contacts AS c " +
"WHERE c.FirstName='Robert'";
var contacts = context.CreateQuery<Contact>(qStr);
Hover your mouse pointer over the word
contacts and you will see that at design time
it is an ObjectQuery
(
.Of
Contact
)
Run the app again and the results will be the same as before.
Why would you need another means of querying the EDM in addition
to LINQ to Entities? The main reason lies in the use of the
string-based query that you passed into the CreateQuery
method. If you have constructed
SQL queries before, this syntax looks familiar but not quite right.
This is the specialized query language for the Entity Framework, and
it’s called Entity SQL.
The return type of this query at design time is an ObjectQuery
(
Of
Contact
)
, not IQueryable
. An ObjectQuery
has methods and properties that
are not available to an IQueryable
. But as you will learn later
in this book, it is possible to cast the IQueryable
to an ObjectQuery
and then access those properties
and methods. This means that even if you choose to use LINQ to
Entities, you will still get to benefit from these properties and
methods.
Entity SQL was actually the first syntax devised for querying entities. LINQ was being developed as a language extension by the VB and C# language teams, and eventually it became obvious that LINQ would be a fabulous addition to the Entity Framework, which is how LINQ to Entities came to be.
Entity SQL has its roots in SQL because it makes sense to start with something that is well known. However, because entities are different from relational data, Entity SQL deviates from SQL to provide the necessary capabilities for querying the EDM.
Looking more closely at the Entity SQL query string you built
earlier, you’ll notice that, like LINQ to Entities, it defines a
variable for use in the query: c
.
In LINQ this is referred to as a control
variable, but in Entity SQL it is just called a
variable.
Figure 3-6
deconstructs the query string without the WHERE
clause. The variable is defined using
the AS
keyword and is referenced in
the SELECT
clause. The VALUE
keyword specifies that you want to
return a collection of single items; in this case, it will be Contact
entities.
The VALUE
clause is required
if you are selecting a single item, which can be an entity, a single
property, or even an entity collection, as shown in the following code
snippet:
SELECT VALUE c FROM ProgrammingEFDB1Entities.Contacts ... SELECT VALUE c.FirstName FROM ProgrammingEFDB1Entities.Contacts ... SELECT VALUE c.Addresses FROM ProgrammingEFDB1Entities.Contacts ...
If you are selecting multiple items, you cannot use VALUE
, as shown here:
SELECT c, c.Addresses FROM ProgrammingEFDB1Entities.Contacts .... SELECT c.LastName,c.Title FROM ProgrammingEFDB1Entities.Contacts ...
If you forget to use VALUE
,
an InvalidOperationException
will
be thrown at runtime telling you that .NET is unable to cast a
System.Data.Object.MaterializedDataRecord
to
the type that you specified in the ObjectQuery
.
If you include VALUE
with
multiple items, an EntitySqlException
will be thrown that
specifically tells you the following:
"SELECT VALUE can have only one expression in the projection list."
It will even tell you the line number and column number of the problem.
Chapter 18 delves more deeply into Entity Framework exceptions.
Without the VALUE
clause, the
results will be wrapped in rows and you will have to dig into the rows
and columns to get at the data. Similar to the LINQ query, you are
selecting FROM
a collection. In
this query, that collection is the entity set, Contacts
; but it is necessary in Entity SQL
to specify the EntityContainer
as
well. Again, c
is a random variable
name the query used to represent contact items within the Contacts
entity set.
The WHERE
clause in the
Entity SQL uses SQL-like syntax, as in the following:
WHERE c.FirstName='Robert'
The Entity SQL language is very robust and offers a lot of functionality. Although it would be impossible to cover all of the operators and functions the language supports, you will see many of them used throughout this book and you can get the full list by looking at the Entity SQL documentation in the MSDN Library.
Entity SQL supports a large set of canonical functions, which are functions that all data providers should support. It also enables data providers to include their own specific functions. The .NET Framework provider for SQL Server, written by Microsoft, offers approximately 75 specific functions that you can use in Entity SQL queries when the target database is SQL Server; some of these overlap with the canonical functions. The provider additionally provides the primitive types and their facets as well as the internal logic for mapping between the EDM and SQL Server. Other providers that are written for the EDM will have their own lists of additional functions and features that are supported.
ObjectQuery
allows you to
create parameterized queries. Similar to other query languages, you
use an @
placeholder in the string,
and then define its value in a parameter.
To use a parameterized query, you can add parameters to an
ObjectQuery
created with the
CreateQuery
method of the ObjectContext
or to one which you have
instantiated explicitly, as shown in the following example. Using this
method, you also need to pass the ObjectContext
as a parameter when you
instantiate an ObjectQuery
.
Then you add parameters to the ObjectQuery
prior to execution. To see how
this works, you can rewrite the query you’ve been writing to enable
dynamic changes to the query, like this:
VB
queryString = _
"SELECT VALUE c FROM ProgrammingEFDB1Entities.Contacts AS c " & _
"WHERE c.firstname=@firstName"
Dim contacts As New ObjectQuery(Of Contact)(queryString, context)
contacts.Parameters.Add(New ObjectParameter("firstName", "Robert"))
C#
queryString =
"SELECT VALUE c FROM ProgrammingEFDB1Entities.Contacts AS c " +
"WHERE c.firstname=@firstName";
var contacts = new ObjectQuery<Contact>(queryString, context);
contacts.Parameters.Add(new ObjectParameter("firstName", "Robert"));
The namespaces in many of the examples are not spelled out
along with the classes. Be sure to reference the appropriate
namespaces at the top of your code files with
Include
for Visual Basic and
using
for C#. For example, for the
class you’ll
need ObjectQuer
yInclude System.Data.Objects
(VB) or using
System.Data.Objects
; (C#).
Although it may seem tempting, you cannot use parameters to
replace property names in the query string. In other words, if you
tried to create the Entity SQL string SELECT
@myproperty FROM ProgrammingEFDB1Entities.Contacts AS c
and
you created a parameter that set @myproperty
to c.LastName
, the SQL that results would look
like this:
SELECT 'c.LastName' FROM ProgrammingEFDB1.Entities.Contacts AS c
This is invalid SQL and will throw an error.
ObjectParameter
s do not
allow you to specify the length of the parameter. As with other
methods of querying the Entity Framework, this causes inefficiencies
with your database’s query plan caching. I will discuss this problem
further in Chapter 16.
So far, the LINQ to Entities and Object Services queries you have seen have been written as standard query expressions. Both LINQ to Entities and Object Services provide a way to write queries as methods, rather than as operators and functions (as in LINQ) or as a string (as in Entity SQL).
Each query language has a method syntax that you can use, but each exists for opposite reasons. The C# and Visual Basic implementations of LINQ sit on top of query methods in .NET 3.5. Your LINQ expressions are translated into these query methods, but you can use them directly if you like.
The Entity Framework processes Entity SQL directly; however, a method-based syntax is available that will construct Entity SQL expressions for you.
Although Visual Basic and C# understand LINQ syntax, the CLR
does not. One of the first things to happen when .NET processes LINQ
queries is that it translates the query into a set of method calls on
the collection being queried. All of the standard query operators
(WHERE
, SELECT
, JOIN
, etc.) have associated methods in .NET
3.5.
You can write your queries using the method syntax directly, if you prefer. Many developers do happen to prefer this, although many others would rather use the expression syntax. The MSDN documentation says, “In general, we recommend query syntax because it is usually simpler and more readable; however, there is no semantic difference between method syntax and query syntax.” Therefore, using one over the other is a matter of style and personal choice.
To write method-based queries, you will need to leverage a new feature introduced in .NET 3.5, called lambdas. Lambdas are inline methods that you can pass into a method to evaluate the method. If you are new to LINQ and lambdas and have never used anonymous delegates (which don’t even exist in Visual Basic), this will make more sense after you’ve seen some examples.
Let’s use the Where
clause to
compare working with a method rather than an operator. A standard
Where
clause is written as Where LastName='Hesse'
. The Where()
method requires the condition
LastName='Hesse'
as a parameter.
You would write this condition very differently in C# and Visual
Basic.
Here we’ll take a look at the query you used in the previous
examples, now written using method-based queries. In Visual Basic, the
expression begins with Function
, to
indicate that you are performing a function on a variable control;
then it states the condition. The control variable, c
in this example, is named on the
fly:
Dim contacts = context.Contacts _ .Where(Function(c) c.FirstName="Robert")
The C# LINQ to Entities query using the method-based syntax looks very different:
var contacts = context.Contacts .Where(c => c.FirstName=="Robert");
C# lambda expressions begin by identifying the control variable,
followed by =>
(the lambda) and
then the expression, [controlVariable].FirstName=='Robert'
.
In the Where
clauses, the
expression that returns a Boolean is called a
predicate. The query will return all of the
contacts for which the expression evaluates to True
.
Try it out:
Replace your existing query with one of the method queries. You will see that IntelliSense is still pretty helpful, even when writing the lambdas.
Press F5 to run the application. The results will be the same as before.
You can combine LINQ query methods to build more useful
expressions. This is referred to as chaining.
To try this, add an OrderBy
method to the previous query. Notice that the lambda expression for
OrderBy
does not need to evaluate
a condition to see whether it is true or false, as does the Where
method. It only needs to return a
property:
VB
Dim contacts = context.Contacts _
.Where(Function(c) c.FirstName="Robert") _
.OrderBy(Function(foo) foo.LastName)
C#
var contacts = context.Contacts
.Where((c) => c.FirstName == "Robert")
.OrderBy((foo) => foo.LastName);
When a method’s signature requests a predicate, remember
that this refers to an expression that returns a Boolean;
otherwise, the lambda only needs to be a function, as in the
OrderBy
method. You’ll see that
in Visual Basic, the signatures of all methods refer to this as a
function. The C# methods specifically refer to predicates in the
methods that require an expression that returns a Boolean. You can
view the signatures of the various LINQ to Entities methods in the
MSDN documentation topic “Supported and Unsupported Methods (LINQ
to Entities).”
Although you can easily use the same variable name throughout compound methods, the variables don’t represent the same instance. In the preceding LINQ query, I named the variables differently to highlight how the compiler evaluates the query.
LINQ actually evaluates the query one method at a time. First
it evaluates context.Contacts
. Then it applies the
Where
method to those results.
Finally, it applies the OrderBy
method to the results of the Where
method. The c
in the Where
method refers to the items returned
by context.Contacts
. The foo
in the OrderBy
method refers to the IQueryable
that is returned by context.Contacts.Where(....)
.
Evaluating one method at a time does not mean processing one method at a time. LINQ to Entities will evaluate this query one method at a time and then will create a store command based on the complete method, unless you are also using methods that must be performed on the client side. It does not execute each method separately.
Here is the T-SQL that results from the preceding query:
SELECT [Extent1].[ContactID] AS [ContactID], [Extent1].[FirstName] AS [FirstName], [Extent1].[LastName] AS [LastName], [Extent1].[Title] AS [Title], [Extent1].[AddDate] AS [AddDate], [Extent1].[ModifiedDate] AS [ModifiedDate] FROM [dbo].[Contact] AS [Extent1] WHERE N'Robert' = [Extent1].[FirstName] ORDER BY [Extent1].[LastName] ASC
It’s possible to use Entity SQL with method syntax as well,
although a limited number of methods are available—13 in fact,
including Where
and Select
. These methods are called
query builder methods. Query builder methods will
do as their name suggests: build an ObjectQuery
with the correct Entity SQL
expression for you.
Although the query builder methods may look like some of the LINQ methods, they are definitely different. The compiler can tell when you are using a query builder method based on the parameter expression, which will contain either a lambda expression for LINQ queries or an Entity SQL expression.
Since you have explored only WHERE
and SELECT
so far while learning about the
different ways to query, we’ll hold off on listing methods and
operators until the following chapter, which has many
queries.
Here is the latest query using Entity SQL as the method parameters:
VB
Dim contacts = context.Contacts _
.Where("it.FirstName = 'Robert'") _
.OrderBy("it.LastName")
C#
var contacts = context.Contacts
.Where("it.FirstName = 'Robert'")
.OrderBy("it.LastName");
The most common question regarding these expressions is “Where
did it
come from?” it
is the default alias for the control
variable. There is no opportunity to define the control variable as
you have had to do with all of the other queries we have looked at so
far, though it is possible to define your own for nested queries, as
you’ll see in the next example.
When debugging, you can inspect the CommandText
property of the contacts
ObjectQuery
to see that the
query builder did indeed build the Entity SQL for you as shown in
Example 3-4. It’s a
little more complex than what you might have written yourself. This is
a result of the query builder’s need to be flexible. Additionally, it
does not specify the EntityContainer
name in the expression,
something that you can’t get away with when building the Entity SQL
yourself.
Example 3-4. The Entity SQL built by the query builder methods
SELECT VALUE it FROM (SELECT VALUE it FROM ([Contacts]) AS it WHERE it.FirstName = 'Robert') AS it ORDER BY it.LastName
Even when used as a method parameter, Entity SQL provides the same benefits over LINQ to Entities as it does when being used in a query expression. If you need to dynamically build the strings for the expressions or if you need to leverage provider-specific functions, once again Entity SQL is the way to go. Another interesting difference is that using query builder methods with Entity SQL expressions removes any syntax differences between Visual Basic and C#.
Whether you use LINQ predicates or Entity SQL predicates, at compile time the Entity Framework will be able to determine which query compilation path to choose by looking at the predicate.
As you can see, you also can combine query builder methods.
The control variable is always it
by default, but you could actually break the query apart and
explicitly change the names of any control variables except for the
first one:
VB
Dim contacts = context.Contacts _
.Where("it.FirstName = 'Robert'")
contacts.Name = "cons"
Dim orderedContacts = contacts.OrderBy("cons.lastname")
C#
var contacts = context.Contacts.Where("it.FirstName = 'Robert'");
contacts.Name = "cons";
var orderedContacts = contacts.OrderBy("cons.lastname");
The preceding example demonstrated an additional feature,
called composable queries. A query was defined
(contacts
) and then another query
was written using that query. The first query is not executed
separately. It is compiled into the second query.
Remember the first query in this chapter?
Dim contacts = context.Contacts
In this case, context.Contacts
refers to the Contacts
property of
the entity container.
If you look back at the code generated from the model, you can see
that context.Contacts
returns the following
query:
VB
MyBase.CreateQuery(Of Contact)("[Contacts]")
C#
this._Contacts = base.CreateQuery<Contact>("[Contacts]");
This is an ObjectQuery
of
Contact
types, but it doesn’t use an
Entity SQL expression. This is a shortcut that works only when you are
not applying any functions to the query, or filters, sorting, or any of
the possible query functions. When you pass in just the name of the
EntitySet
, the Entity Framework will
do the rest of the work. You can use this shortcut yourself as well, but
it is no different from just calling context.Contacts
.
There is still one additional way to query the EDM: via EntityClient
. EntityClient
differs from LINQ to Entities and
Object Services because it does not materialize objects. Instead, it
streams data back to the requesting application as rows and columns in
an EntityDataReader
, which implements
DbDataReader
.
If you have experience with ADO.NET, EntityClient
is comparable to SqlClient
, OracleClient
, and other client providers;
these clients return SqlDataReader
,
OracleDataReader
, and so forth, which
also implement the DbDataReader
.
A data reader represents data in rows and columns. With the
familiar DataReader
s, each “cell”
contains a scalar value—in other words, a primitive type such as a
string or an integer. For example:
Column 1 | Column 2 | Column 3 | |
---|---|---|---|
Row 1 | 1 | John | Doe |
Row 2 | 2 | Jessica | Rabbit |
Row 3 | 3 | Cecil | De Mille |
EntityDataReader
s are designed
to represent the relationships that exist in an EDM; therefore, scalar
data is not enough. An EntityDataReader
has the ability to return
data as shaped results. In an EntityDataReader
, the cells in the preceding
example could contain not only scalar values, but also an entire
DbDataReader
, a DbDataRecord
(a single row from a DbDataReader
), or even an EntityKey
object. You saw EntityKey
as a property of an entity in the
EDM you built in Chapter 2; the
EntityKey
class is a full class
implementation based on that property, which you will learn more about
in Chapter 9.
EntityClient
uses Entity SQL
for its query syntax and contains methods and properties that will be
familiar if you have worked with ADO.NET previously, including
connections, commands, parameters, and transactions.
The next example will give you a chance to work with EntityClient
. Following the example is an
explanation of the code.
Add the following namespace declarations to the beginning of the code file:
VB
Imports System.Data.EntityClient
C#
using System.Data.EntityClient;
Add the method in Example 3-5 to your existing code
(Module.vb or Program.cs) to perform the same query you
wrote earlier with LINQ to Entities and Object Services. This time
you will be using the EntityClient
provider.
Example 3-5. Querying with EntityClient
VB
Private Sub EntityClientQueryContacts()
Using conn As EntityConnection = _
New EntityConnection("name=ProgrammingEFDB1Entities")
conn.Open()
Dim queryString = _
"SELECT VALUE c " & _
"FROM ProgrammingEFDB1Entities.Contacts AS c " & _
"WHERE c.FirstName='Robert'"
Dim cmd As EntityCommand = conn.CreateCommand()
cmd.CommandText = queryString
Using rdr As EntityDataReader = _
cmd.ExecuteReader(CommandBehavior.SequentialAccess)
Do While rdr.Read
Dim firstname = rdr.GetString(1)
Dim lastname = rdr.GetString(2)
Dim title = rdr.GetString(3)
Console.WriteLine("{0} {1} {2}", _
title.Trim, firstname.Trim, lastname)
Loop
End Using
conn.Close()
Console.Write("Press Enter...")
Console.ReadLine()
End Using
End Sub
C#
static void EntityClientQueryContacts()
{
using (EntityConnection conn = new
EntityConnection("name=ProgrammingEFDB1Entities"))
{
conn.Open();
var queryString = "SELECT VALUE c " +
"FROM ProgrammingEFDB1Entities.Contacts AS c " +
"WHERE c.FirstName='Robert'";
EntityCommand cmd = conn.CreateCommand();
cmd.CommandText = queryString;
using (EntityDataReader rdr =
cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess))
{
while (rdr.Read())
{
var firstname = rdr.GetString(1);
var lastname = rdr.GetString(2);
var title = rdr.GetString(3);
Console.WriteLine("{0} {1} {2}",
title.Trim(), firstname.Trim(), lastname);
}
}
conn.Close();
Console.Write("Press Enter...");
Console.ReadLine();
}
}
Call this new method from the Main
method.
Press F5 to test the new method.
The results will be the same as the previous two queries.
There is a bit to explain regarding the code for calling the
EntityCommand
.
With other client providers, the connection connects directly to
the data store. However, the EntityConnection
provides a connection to
the EDM. When you created the model with the ADO.NET Entity Data Model
Wizard, you may remember seeing the odd connection string in the
wizard’s page where you selected the connection. An EntityConnection
string
consists of pointers to the compiled EDM schema files as well as a
database connection string.
The wizard wrote the EntityConnection
string into the app.config file. You can open this file
from the Solution Explorer and see that the ConnectionString
named ProgrammingEFDB1Entities
is composed of
three parts: the metadata, provider, and provider connection
string.
The metadata contains file path pointers to the three schema
files that are created from the model when the project is built. The
data provider refers to the SqlClient
provider that is being used to
connect to the SQL Server database in this example. And finally, the
provider connection string is a standard database connection
string:
metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl; provider=System.Data.SqlClient; provider connection string= "Data Source=MyServer; Initial Catalog=ProgrammingEFDB1; Integrated Security=True; MultipleActiveResultSets=True"
The *
in the metadata
indicates that the files are embedded into the assembly file of the
project that contains the model and its classes. This is the
default, although you can specify that the files be saved to the
filesystem. You’ll learn more about this in Chapter 7.
EntityConnection
provides for
an easy way to reference the connection string in the app.config file, which is to set a name
property to the same name of the
connection string: for example, "name=ProgrammingEFDB1Entities"
. As you saw
in Example 3-5, the quotes are
required.
Creating the EntityCommand
is
no different from creating any other provider command and setting its
CommandText
. The CommandText
here is the Entity SQL
expression defined in the variable, queryString
.
With EntityClient
, the
SequentialAccess CommandBehavior
is
required for the ExecuteReader
method. With other
DbDataReader
s, rows must be
accessed sequentially, but the columns within the rows need not be.
This rule exists to control memory consumption.
DbDataReader
s are streams of
data and are, by definition, forward-only. This also means that the
columns must be read in this way, which makes the next bit of code in
Example 3-5 a little
cumbersome.
In the string concatenation, you want to combine the fields to
read Title FirstName LastName
. But
this is not the order of the fields returned in the DataReader
. Title
is the fourth column in the row,
whereas FirstName
is the second
column and LastName
is the third;
therefore, you cannot read the Title
data first, and instead must read the
fields in the order in which they are streaming.
That is why this method creates the variables prior to building
the string—so the data can be extracted in sequential order. Once the
variables exist, you can build the string. This is an important lesson
to remember, regardless of how you plan to use the streamed data
returned by the EntityClient
.
Although we will explore query processing in detail later in the book, you may already be wondering what kind of query the Entity Framework is sending to your database.
The Entity Framework will break down the LINQ or Entity SQL query into a command tree and, with the help of the EDM and the database provider, will create another command tree that is specific to the database.
You can imagine how flexible the API needs to be to pull this off, no matter what query you write. Although the examples so far have been simplistic, it is possible to write very complex LINQ to Entities or Entity SQL queries. The Entity Framework needs to be able to deal with anything you throw at it. Therefore, queries may not look exactly the same as you might write them directly in your database’s query syntax, because they are being constructed in a somewhat formulaic manner.
Sometimes the queries may look more complex but have no negative impact whatsoever on performance. But don’t expect this to always be the case.
You can see the actual T-SQL built from your query in a few
different ways. ObjectQuery
has a ToTraceString
method that will let you see the
store command. You would have to add this to your code to see it,
however; we’ll do this in a later chapter. A simpler way for now (for
those of you who are working with SQL Server Professional and above
versions) is to use the SQL Profiler tool in SQL Server.
Here is the T-SQL rendered from the LINQ to Entities and Entity
SQL queries that returned Contact
s
named Robert
:
SELECT [Extent1].[ContactID] AS [ContactID], [Extent1].[FirstName] AS [FirstName], [Extent1].[LastName] AS [LastName], [Extent1].[Title] AS [Title], [Extent1].[AddDate] AS [AddDate], [Extent1].[ModifiedDate] AS [ModifiedDate] FROM [dbo].[Contact] AS [Extent1] WHERE [Extent1].[FirstName] = 'Robert'
Both queries result in the same T-SQL because they are fairly simple queries.
As you write queries, it’s a good idea to keep an eye on the generated SQL, since you can write queries in different ways and get the same results, but perhaps not generate the same store command.
A good example of this is when you combine .NET methods with
LINQ. The String
class has a handy
method named StartsWith
that
evaluates the first character in a string:
VB
From c In context.Contacts _
Where c.LastName.StartsWith("S")
C#
from c in PEF.Contacts
where c.LastName.StartsWith("S")
select c
Even though T-SQL has a handy LEFT
function, because of the way .NET
evaluates StartsWith
it is not
interpreted as LEFT
. Instead, you
get the following WHERE
clause in
SQL Server:
WHERE (CAST(CHARINDEX(N'S', [Extent1].[LastName]) AS int)) = 1
The use of CAST
has a
negative performance impact in SQL.
Even some of the T-SQL filtering operators are not very good
for the performance of SQL queries. A database administrator will
tell you that the LEFT
and
SUBSTRING
operators are equally
bad because without a specific index on the database table, those
operators cannot perform efficient queries. Chapter 16 spends more
time on these performance issues and makes some recommendations for
writing queries to get better performance from your database.
Visual Basic actually has a LEFT
method that you can use in LINQ:
From c In PEF.Contacts _ Where Left(c.LastName, 1) = "S"
This query does cause the T-SQL LEFT
function to be used:
WHERE N'S' = (LEFT([Extent1].[LastName], 1))
Unfortunately, C# does not have a comparable function. The best
choice for C# is to use Substring
:
from c in PEF.Contacts where c.LastName.Substring(0, 1) == "S" select c
This reults in the following WHERE
clause:
WHERE N'S' = (SUBSTRING([Extent1].[LastName], 0 + 1, 1))
You may have noticed when debugging some of the queries in this
chapter that next to the Results
property it says “Expanding will process the collection.” This is a very
important concept to be aware of and it impacts all LINQ queries
(including in-memory queries and LINQ to SQL) as well as ObjectQuery
queries. Whether you do it in
debug mode or in code, every time you do anything to force the
enumeration of a query, the query will be processed again.
In the Entity Framework, this means that even if you have already
done something to enumerate the query (e.g., bound it to a control, run
it through a foreach
iteration,
called ToList()
on the query, etc.),
anytime you repeat one of these methods that forces execution it will go
back to the database, run the query again, bring back the results again,
and then merge the results into the cache that’s in memory.
ToList
is a convenient way to
force query execution and provide a variable to work with. That variable
will be a System.Collections.Generic.List(Of
T)
(List<T>
in C#) of
whatever type the query returns.
Another method you can use is ObjectQuery.Execute
, which will also force
execution. Execute
returns a System.Data.Objects.ObjectResult(Of T)
(<T>
in C#). ObjectResult
has some
special functionality that makes it the right choice for data-binding
scenarios; you’ll see ObjectResult
in
use in later chapters where you will be doing data binding in various
applications. Execute
takes a
MergeOption
parameter that specifies
how the query results should be merged into existing entities; you’ll
learn more about MergeOption
in Chapter 9.
You will see ToList
and other
methods used throughout this book to avoid accidentally repeating query
execution.
In this chapter, you learned about the many different ways to
query an EDM, LINQ to Entities, and the ObjectQuery
with Entity SQL, LINQ methods,
query builder methods, and streaming data with EntityClient
. Along the way, you learned about
many of the fundamentals that will make it easier for you to construct
intelligent queries.
In Chapter 9, you will spend some time comparing how these queries are processed so that you can see the different paths the various query methods embark on as they are resolved. Chapter 16 will cover the performance differences between the various query methods and will demonstrate ways to impact performance directly.
Although this chapter focused on a single simple query with a twist here and there, the next chapter will delve more deeply into querying, demonstrating ways to retrieve more complex data using all of the methods you are now familiar with.
3.138.120.136