Chapter 3. Querying Entity Data Models

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.

Query the Model, Not 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.

Your First EDM Query

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.

  1. Open the Module1.vb or Program.cs file.

  2. 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();
    }
  3. Add the following code into the Main method:

    VB
    QueryContacts
    C#
    QueryContacts();
  4. 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.

  5. 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:

  1. 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 (}).

  2. Press F5 to run the code again.

  3. 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).

The query results containing Contact entities at runtime

Figure 3-1. The query results containing Contact entities at runtime

  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.

  2. You can continue the application or stop it by pressing Shift-F5.

A More Query-Like Query

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;

Note

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.

Where Did the Context and Classes Come from?

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.

An extra code file attached to the model

Figure 3-2. An extra code file attached to the model

Expand the .edmx file in the Solution Explorer to see the generated code file. Open the file to see what’s in there.

Note

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.

The four classes in Visual Studio’s Class Designer view

Figure 3-3. The four classes in Visual Studio’s Class Designer view

The ObjectContext class, ProgrammingEFDB1Entities

When you looked at the XML view of the model in Chapter 2, you saw an EntityContainer that contained the EntitySets and AssociationSets.

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 EntitySets 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 entity classes

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).

The entity classes in the Class Designer

Figure 3-4. The entity classes in the Class Designer

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.

LINQ to Entities Queries

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.

Note

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.

ObjectQuery and LINQ to Entities

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).

ObjectQuery implementing LINQ’s IQueryable

Figure 3-5. ObjectQuery implementing LINQ’s IQueryable

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.

Note

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.

Entity SQL Queries That Return Objects

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:

  1. 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);
  2. Hover your mouse pointer over the word contacts and you will see that at design time it is an ObjectQuery (Of Contact).

  3. Run the app again and the results will be the same as before.

Why Another Way to Query?

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

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.

Deconstructing a simple Entity SQL query

Figure 3-6. Deconstructing a simple Entity SQL query

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.

Note

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'

Entity SQL canonical functions

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.

Note

If you are familiar with T-SQL, you’ll be happy to know that one of the canonical functions is Trim(), which means you won’t have to use the silly LTRIM(RTRIM()) combo anymore.

The Parameterized ObjectQuery

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"));

Note

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 ObjectQuery class you’ll need Include 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.

Warning

ObjectParameters 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.

Method-Based Syntax Queries for LINQ and Entity SQL

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.

LINQ Method-Based Queries

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:

  1. Replace your existing query with one of the method queries. You will see that IntelliSense is still pretty helpful, even when writing the lambdas.

  2. Press F5 to run the application. The results will be the same as before.

Chaining methods

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);

Note

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

ObjectQuery’s Query Builder Methods

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.

Note

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.

Specifying the control variable

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.

The Shortest 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.

EntityClient: The Lowest-Level Method for Returning Streamed Data Through EDM Queries

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 DataReaders, 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

EntityDataReaders 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.

  1. Add the following namespace declarations to the beginning of the code file:

    VB
    Imports System.Data.EntityClient
    C#
    using System.Data.EntityClient;
  2. 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();
     }
    }
  3. Call this new method from the Main method.

    Note

    You may want to comment out the call to QueryContacts so that only the new method is run.

  4. 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.

EntityConnection and the Connection String

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"

Note

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.

EntityCommand

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.

ExecuteReader

With EntityClient, the SequentialAccess CommandBehavior is required for the ExecuteReader method. With other DbDataReaders, rows must be accessed sequentially, but the columns within the rows need not be. This rule exists to control memory consumption.

Forward-Only Access to the Fields

DbDataReaders 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.

Translation to Database Queries

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 Contacts 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.

Pay Attention to the .NET Method’s Impact on Generated SQL

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.

Warning

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))

Avoid Inadvertent Query Execution

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.120.136