Chapter 19Using LINQ to Entities After completing this chapter, you will be able to: ■ ■ Create LINQ queries that access content from an Entity Framework data model ■ ■ Call database-lev
Trang 1Chapter 19
Using LINQ to Entities
After completing this chapter, you will be able to:
■
■ Create LINQ queries that access content from an Entity Framework data model
■
■ Call database-level functions from LINQ queries
■
■ Understand how LINQ treats entities differently from other data sources
The Entity Framework (EF) is a model-based interface between your application and an ex-ternal database As discussed earlier in this book, the Framework presents logical, database-centric data content in a conceptual form expressed through object instances This focus on objects makes the Entity Framework a great match for LINQ
The LINQ to Entities provider brings the world of entities and LINQ queries together This chapter introduces this EF-centric LINQ provider, a system that takes advantage of the Entity Framework’s model-focused data and LINQ’s capability to develop native language queries that target entities and their properties
Note The exercises in this chapter all use the same sample project, a tool that makes queries using LINQ to Entities Although you can run the application after each exercise, the expected results for the full application might not appear until you complete all exercises in the chapter.
Understanding the LINQ to Entities Provider
The ObjectSet(Of TEntity) and ObjectQuery(Of T) classes—the two key collection-style base
classes used in the Entity Framework—are ready to use in LINQ queries Both collection
types implement IEnumerable(Of T) and IQueryable(Of T), the same interfaces that LINQ
requires to enable query operations on a collection Any entity collection exposed by an EF
context or built from an ObjectQuery instance—such as a query processed with Entity SQL—
can form the basis of a LINQ query
EF entities referenced in a LINQ query expose properties that are available for projection, filtering, sorting, and other LINQ operations LINQ to Entities queries are much cleaner than their LINQ to DataSet counterparts because each entity property already expresses its model-defined data type
Trang 2(from the database) does not occur at the time you build a data query; instead, data is pro-cessed and returned to your application only when you attempt to reference specific entities and properties When you write an EF query, the Framework prepares a T-SQL query (when SQL Server is used as the backend database) that it runs on the database server to obtain the desired records That database action occurs only when you access the results of the query statement
When you craft LINQ queries that involve EF objects, the application employs this same form
of delayed processing The clauses in a LINQ query—and ultimately the extension methods and lambda expressions that make up a LINQ expression tree—translate into SQL statements
and clauses that are played out on the data server For this reason, all LINQ to Entities queries can involve only objects and data elements that can be represented within a remotely run SQL statement While other LINQ providers can be mixed—Chapter 18, “Using LINQ to DataSet,”
combined LINQ to Objects and LINQ to DataSet content—LINQ to Entities imposes restric-tions on the type of data involved in the queries
Note One of the exercises in this chapter will demonstrate one way that LINQ to Entities can be used indirectly with other forms of LINQ.
Some LINQ features available with other LINQ providers are not supported by LINQ to Entities Projections, comparisons, and joins that are based on a locally-defined function won’t work in LINQ to Entities because the local function cannot be represented in a SQL
query running elsewhere Also, the Last, SkipWhile, and TakeWhile extension methods are not available; Skip and Take (in both their SQL-style and extension method forms) will work.
Writing Queries with LINQ to Entities
As with all LINQ providers, the general structure of LINQ to Entities queries varies only a little from the LINQ to Objects standard In fact, looking at a LINQ to Entities query, it’s hard to see that it isn’t working with standard NET objects The telltale sign is the use of an active Entity Framework object context, either as a direct source for entities or as a way to run an
ObjectQuery that will feed data into LINQ.
Trang 3Here is a query that returns some properties from a Customer entity:
C#
using (SalesOrderEntities context = new SalesOrderEntities(connectionString))
{
var results = from cu in context.Customers
orderby cu.FullName
select new { CustomerID = cu.ID, CustomerName = cu.FullName };
}
Visual Basic
Using context As New SalesOrderEntities(connectionString)
Dim results = From cu In context.Customers
Order By cu.FullName
Select CustomerID = cu.ID, CustomerName = cu.FullName
End Using
Most of the standard LINQ clauses are included, in both their LINQ expression and their
extension method/lambda expression forms, including Where, Join, Group By, and so on
As far as the LINQ syntax is concerned, LINQ to Entities is pretty full-featured But there are
limitations Some, such as the inability to use the SkipWhile and TakeWhile extension meth-ods, were listed previously Others follow this general rule: If it can’t be converted easily into a storage-level function, it can’t be used directly in LINQ to Entities.
Querying with LINQ to Entities: C#
Note This exercise parallels the exercise found in Chapter 18 It is nearly identical in functionality and purpose, but uses LINQ to Entities instead of LINQ to DataSet to process database content.
1 Open the “Chapter 19 CSharp” project from the installed samples folder The project
includes three Windows.Forms classes: OrderViewer, StatesByYear, and Switchboard This example focuses on the OrderViewer form.
Trang 42 Open the source code view for the General class Locate the GetConnectionString
func-tion; this is a routine that uses a SqlConnectionStringBuilder to create a valid connection
string to the sample database It currently includes the following statements:
sqlPortion.DataSource = @"(local)\SQLExpress";
sqlPortion.InitialCatalog = "StepSample";
sqlPortion.IntegratedSecurity = true;
Adjust these statements as needed to provide access to your own test database
3 Open the source code view for the OrderViewer form Locate the ActView_Click event
handler This routine displays a list of orders, either for all customers in the database or for a specific customer by ID number Just after the “Retrieve all customer orders” com-ment, add the following statement:
var result = from cu in OrderContext.Customers
from ord in OrderContext.OrderEntries
where cu.ID == ord.Customer
orderby cu.FullName, ord.ID
select new { CustomerID = cu.ID,
CustomerName = cu.FullName,
OrderID = ord.ID,
OrderDate = ord.OrderDate,
OrderTotal = ord.Total,
ord.StatusCode };
This query combines two entity collections, Customers and OrderEntries, both of which are members of the SalesOrderEntities class, a derived Entity Framework context It forms implicit inner joins between the entity collections via the where clause and
per-forms a sorted projection of fields from each source table
4 Just after the “Add in the status code” comment, add the following query:
var result2 = from cu in result.ToArray()
from sts in statusTable
where cu.StatusCode == sts.Code
select new { cu.CustomerID, cu.CustomerName, cu.OrderID,
OrderStatus = sts.Description, cu.OrderDate, cu.OrderTotal };
This query extends the original query by linking in a local object collection This is nec-essary because LINQ to Entities cannot transmit an entire local collection to the data-base for SQL processing Instead, the original query must be converted into a regular
.NET collection, as is done with the result.ToArray() clause The original query is
pro-cessed at that moment, and the results are placed in a standard anonymous array The
result2 query is actually doing its work using LINQ to Objects.
Trang 55 Run the program When the Switchboard form appears, click Order Viewer When the
OrderViewer form appears, select the Include All Customers option and then click View.
The grid displays content from the Customer and OrderEntries entities, plus a column from the local statusTable collection.
Querying with LINQ to Entities: Visual Basic
Note This exercise parallels the exercise found in Chapter 18 It is nearly identical in functionality and purpose, but uses LINQ to Entities instead of LINQ to DataSet to process database content.
1 Open the “Chapter 19 VB” project from the installed samples folder The project
in-cludes three Windows.Forms classes: OrderViewer, StatesByYear, and Switchboard This example focuses on the OrderViewer form.
2 Open the source code view for the General module Locate the GetConnectionString
function; this is a routine that uses a SqlConnectionStringBuilder to create a valid
con-nection string to the sample database It currently includes the following statements: sqlPortion.DataSource = "(local)\SQLExpress"
sqlPortion.InitialCatalog = "StepSample"
sqlPortion.IntegratedSecurity = True
Adjust these statements as needed to provide access to your own test database
Trang 63 Open the source code view for the OrderViewer form Locate the ActView_Click event
handler This routine displays a list of orders, either for all customers in the database or for a specific customer by ID number Just after the “Retrieve all customer orders” com-ment, add the following statement:
Dim result = From cu In OrderContext.Customers,
ord In OrderContext.OrderEntries
Where cu.ID = ord.Customer
Select CustomerID = cu.ID,
CustomerName = cu.FullName,
OrderID = ord.ID,
OrderDate = ord.OrderDate,
OrderTotal = ord.Total,
ord.StatusCode
Order By CustomerName, OrderID
This query combines two entity collections, Customers and OrderEntries, both of which are members of the SalesOrderEntities class, a derived Entity Framework context It forms implicit inner joins between the entity collections via the Where clause and
per-forms a sorted projection of fields from each source table
4 Just after the “Add in the status code” comment, add the following query:
Dim result2 = From cu In result.ToArray(), sts In statusTable
Where cu.StatusCode = sts.Code
Select cu.CustomerID, cu.CustomerName, cu.OrderID,
OrderStatus = sts.Description, cu.OrderDate, cu.OrderTotal
This query extends the original query by linking in a local object collection This is nec-essary because LINQ to Entities cannot transmit an entire local collection to the database for SQL processing Instead, the original query must be converted into a regular NET
collection, as is done with the result.ToArray() clause The original query is processed
at that moment and the results are placed in a standard anonymous array The result2
query is actually doing its work using LINQ to Objects
5 Run the program When the Switchboard form appears, click Order Viewer When the OrderViewer form appears, select the Include One Customer By ID option, enter 1 in
the Customer ID field and then click View
Trang 7The grid displays content from the Customer and OrderEntries entities, plus a column from the local statusTable collection.
Working with Entity and Database Functions
Calling your own custom function within the Where clause isn’t supported.
C#
private decimal? AdjustTotal(decimal? origValue)
{
// - Add tax to the amount.
if (origValue.HasValue == false) return new decimal?();
return Math.Round((decimal)origValue * LocalTaxRate, 2);
}
// - Later, try this code, although it will fail.
var result = from ord in context.OrderEntries
where AdjustTotal(ord.Total) > 500M
select new { ord.ID, ord.OrderCustomer.FullName, ord.Total };
Visual Basic
Private Function AdjustTotal(ByVal origValue As Decimal?) As Decimal?
' - Add tax to the amount.
If (origValue.HasValue = False) Then Return New Decimal?
Return Math.Round(CDec(origValue) * LocalTaxRate, 2)
End Function
' - Later, try this code, although it will fail.
Dim result = From ord In context.OrderEntries
Where AdjustTotal(ord.Total) > 500@
Select ord.ID, ord.OrderCustomer.FullName, ord.Total
Trang 8But converting this code to use the calculation inline does work.
C#
// - This will work.
var result = from ord in context.OrderEntries
where Math.Round(ord.Total * LocalTaxRate, 2) > 500M
select new { ord.ID, ord.OrderCustomer.FullName, ord.Total };
Visual Basic
' - This will work.
Dim result = From ord In context.OrderEntries
Where Math.Round(ord.Total * LocalTaxRate, 2) > 500@
Select ord.ID, ord.OrderCustomer.FullName, ord.Total
This works because although LINQ to Entities cannot easily migrate your custom and possibly
complex AdjustTotal function to a SQL equivalent, it does know how to convert the Math Round reference into something that the database engine will recognize (the T-SQL ROUND
function)
Only certain NET methods have database-level equivalents, and it’s not always immedi-ately clear which local methods will be passed to the database without your interaction
Math.Round converts to SQL Server’s ROUND, but Math.Sqrt generates an error, even though Transact-SQL includes a SQRT function.
If you would like to have a little more confidence when writing your LINQ to Entities queries, you can forgo the automated conversion and decide up front which Entity Framework or database-level functions you want to include in your query
LINQ to Entities includes a set of canonical functions which are all hosted in the System.Data Objects.EntityFunctions class These functions somewhat parallel the Entity SQL canonical
functions discussed in the “Using Literals, Operators, and Expressions” section on page 249 of Chapter 15, although only a subset is available with LINQ
■
■ Date and time functions All the Add functions (such as AddMinutes) are included,
as are Diff functions that return an integral time span CreateTime, CreateDateTime, and CreateDateTimeOffset build new date and time values from their components TruncateTime maps to the Entity SQL Truncate function, which returns a date with the
time portion removed
■
■ String functions Left and Right return string subsets Reverse returns the content of a
string in reverse order AsUnicode and AsNonUnicode perform Unicode-related
conver-sions on existing strings These two functions are specific to LINQ to Entities and do not have Entity SQL equivalents
Trang 9■ Math and statistical functions The Truncate canonical function performs
nu-meric rounding Three statistical functions—StandardDeviation, Var, and VarP are also
included
To use the canonical functions, be sure to have a using (C#) or Imports (Visual Basic) ref-erence to System.Data.Objects and then prefix the function calls in your query with the EntityFunctions class name.
C#
var result = from cu in context.Customers
where EntityFunctions.Left(cu.FullName, 1) == "A"
select cu;
Visual Basic
Dim result = From cu In context.Customers
Where EntityFunctions.Left(cu.FullName, 1) = "A"
Select cu
Beyond the canonical functions, LINQ to Entities also exposes database-level functions The
SQL Server functions appear in the System.Data.Objects.SqlClient.SqlFunctions class and
par-allel their T-SQL counterparts The following list touches lightly on the functions available
■
■ Server identity functions HostName, CurrentUser, and UserName equate to the
T-SQL HOST_NAME, CURRENT_USER, and USER_NAME functions, respectively.
■
■ Math functions Most, but not all the native SQL Server math functions are
in-cluded: Acos, Asin, Atan, Atan2, Cos, Cot, Degrees, Exp, Log, Log10, Pi, Radians, Rand, Sign, Square, SquareRoot (a renaming of SQRT), and Tan Missing from this list are ABS, CEILING, FLOOR, POWER, and ROUND, although each of these can be accomplished either by using their System.Math or EntityFunctions equivalents.
■
■ String functions Various string and string-conversion functions from SQL Server can
be called from LINQ: Ascii, Char, CharIndex, Difference (a Soundex-related function), IsDate, IsNumeric, NChar, PatIndex, QuoteName, Replicate, SoundCode (more Soundex), Space, StringConvert (known as STR in T-SQL), Stuff, and Unicode.
■
■ Date and time functions This set includes some of the query-level and system-level
date-related functions: CurrentTimestamp (known as CURRENT_TIMESTAMP in the da-tabase), DateAdd, DateDiff, DateName, DatePart, GetDate, and GetUtcDate.
■
■ Other functions The Checksum and DataLength functions map to their CHECKSUM
and DATALENGTH function counterparts in SQL Server.
The database functions work just like the canonical functions First include an Imports (Visual Basic) or using (C#) reference to System.Data.Objects.SqlClient and then attach the SqlFunctions class name to the start of each database function used in your query.
Trang 10var result = from ord in context.OrderEntries
select new { ord.ID, ord.OrderCustomer.FullName,
LateDate = SqlFunctions.DateAdd("day", 90, ord.OrderDate) };
Visual Basic
Dim result = From ord In context.OrderEntries
Select ord.ID, ord.OrderCustomer.FullName,
LateDate = SqlFunctions.DateAdd("day", 90, ord.OrderDate)
Working with Custom Database Functions
In addition to calling database-supplied functions from your LINQ queries, you can also call
user-defined functions added to SQL Server with the CREATE FUNCTION command Like
standard stored procedures, custom functions let you add business logic within the database with standard Transact-SQL syntax, or with Visual Basic or C# via SQL Server’s support for the Common Language Runtime (CLR)
Making direct calls to database-level functions through a LINQ to Entities query involves four distinct steps:
1 Create the target function in SQL Server using the CREATE FUNCTION DDL command
Make note of the exact spelling and capitalization of the function name and its param-eters because you will need to replicate them within your application The exercise
shown later in this section references AdmittedInYear, a custom function from the
book’s sample database Here is its T-SQL definition:
CREATE FUNCTION AdmittedInYear(@whichDate AS DATETIME)
RETURNS int AS
BEGIN
- Return the number of states admitted to the union
during the year of the specified date
DECLARE @result int;
SELECT @result = COUNT(*) FROM StateRegion
WHERE DATEPART(year, Admitted) = DATEPART(year, @whichDate);
RETURN @result;
END
This function returns a count of the number of states admitted to the United States during the year specified by the supplied date