LINQ to SQL (Part 8 - Executing Custom SQL Expressions)

advertisement
LINQ to SQL (Part 8 - Executing Custom SQL
Expressions)
Over the last few weeks I've been writing a series of blog posts that cover LINQ to SQL. LINQ to
SQL is a built-in O/RM (object relational mapper) that ships in the .NET Framework 3.5 release,
and which enables you to model relational databases using .NET classes. You can use LINQ
expressions to query the database with them, as well as update/insert/delete data.
Below are the first seven parts in this series:







Part 1: Introduction to LINQ to SQL
Part 2: Defining our Data Model Classes
Part 3: Querying our Database
Part 4: Updating our Database
Part 5: Binding UI using the ASP:LinqDataSource Control
Part 6: Retrieving Data Using Stored Procedures
Part 7: Updating our Database using Stored Procedures
In my last two posts (Part 6 and Part 7) I demonstrated how you can optionally use database
stored procedures (SPROCs) to query, insert, update and delete data using a LINQ to SQL data
model.
One of the questions a few people have asked me since doing these posts has been "what if I
want total control over the SQL expressions used by LINQ to SQL - but I don't want to use
SPROCs to-do it?" Today's blog post will cover that - and discuss how you can use custom SQL
expressions that you provide to populate your LINQ to SQL data model classes, as well as
perform insert, updates, and deletes.
Using LINQ Query Expressions with LINQ to SQL
For the purposes of this blog post, let's assume we've used the LINQ to SQL ORM designer in
VS 2008 to define a set of data model classes like below for the Northwind database (note: read
Part 2 of this series to learn how to use the LINQ to SQL ORM designer to do this):
In Part 3 of this blog series I covered how you can use the new LINQ language support in VB and
C# to query the above data model classes and return back a set of objects that represent the
rows/columns in the database.
For example, we could add a "GetProductsByCategory" helper method to the DataContext class
of our data model that uses a LINQ query to return back Product objects from the database:
VB:
C#:
Once we've defined our encapsulated LINQ helper method, we can then write code like below
that uses it to retrieve the products, and iterate over the results:
VB:
When the LINQ expression within our "GetProductsByCategory" method is evaluated, the LINQ
to SQL ORM will automatically execute dynamic SQL to retrieve the Product data and populate
the Product objects. You can use the LINQ to SQL Debug Visualizer to see in the debugger how
this LINQ expression is ultimately evaluated.
Using Custom SQL Queries with LINQ to SQL
In our sample above we didn't have to write any SQL code to query the database and retrieve
back strongly-typed Product objects. Instead, the LINQ to SQL ORM automatically translated the
LINQ expression to SQL for us and evaluated it against the database.
But what if we wanted total control over the SQL that is run against our database, and don't want
LINQ to SQL to-do it for us in this scenario? One way to accomplish this would be to use a
SPROC like I discussed in Part 6 and Part 7 of this series. The other approach is to use the
"ExecuteQuery" helper method on the DataContext base class and use a custom SQL expression
that we provide.
Using the ExecuteQuery Method
The ExecuteQuery method takes a SQL query expression as an argument, along with a set of
parameter values that we can use to optionally substitute values into the query. Using it we can
execute any raw SQL we want against the database (including custom JOINs across multiple
tables).
What makes the ExecuteQuery method really useful is that it allows you to specify how you want
the return values of your SQL expression to be typed. You can do this either by passing a typeobject as a parameter to the method, or by using a generic-based version of the method.
For example, we could change the GetProductsByCategory() helper method we created earlier using a LINQ expression - to instead use the ExecuteQuery method to execute our own raw SQL
expression against the database and return "Product" objects as a result:
VB:
C#:
We can then call the GetProductsByCategory() helper method using the exact same code as
before:
But unlike before it will be our custom SQL expression that will run against the database - and not
dynamic SQL executed in response to using a LINQ query expression.
Custom SQL Expressions and Object Tracking for Updates
By default when you retrieve a data model object using LINQ to SQL, it will track all changes and
updates you make to it. If you call the "SubmitChanges()" method on the DataContext class, it
will then transactionally persist all of the updates back to the database. I cover this in more depth
in Part 4 of this LINQ to SQL series.
One of the cool features of the ExecuteQuery() method is that it can fully participate in this object
tracking and update model. For example, we could write the code below to retrieve all products
from a specific category and discount their prices by 10%:
Because we typed the return value of our ExecuteQuery call in the GetProductsByCategory
method to be of type "Product", LINQ to SQL knows to track the Product objects we returned from
it. When we call "SubmitChanges()" on the context object they will be persisted back to the
database.
Custom SQL Expressions with Custom Classes
The ExecuteQuery() method allows you to specify any class as the return type of a SQL query.
The class does not have to be created using the LINQ to SQL ORM designer, or implement any
custom interface - you can pass in any plain old class to it.
For example, I could define a new ProductSummary class that has a subset of Product properties
like below (notice the use of the new C# Automatic Properties feature):
We could then create a GetProductSummariesByCategory() helper method on our
NorthwindDataContext that returns results based on it. Notice how our SQL statement below
requests just the subset of product values we need - the ExecuteQuery method then handles
automatically setting these on the ProductSummay objects it returns:
We can then invoke this helper method and iterate over its results using the code below:
Custom SQL Expressions for Inserts/Updates/Deletes
In addition to using custom SQL expressions for queries, we can also execute them to perform
custom Insert/Update/Delete logic.
We can accomplish this by creating the appropriate partial Insert/Update/Delete method for the
entity we want to change in a partial class on our DataContext. We can then use the
ExecuteCommand method on the DataContext base class to write the SQL we want to execute.
For example, to override the Delete behavior for Product classes we could define this
DeleteProduct partial method:
And now if we write the below code to remove a specific Product instance from our database,
LINQ to SQL will call the DeleteProduct method - which will cause our custom SQL to execute in
place of the default dynamic SQL that LINQ to SQL would otherwise use:
Summary
The LINQ to SQL ORM automatically generates and executes dynamic SQL to perform queries,
updates, inserts and deletes against a database.
For advanced scenarios, or cases where you want total control over the SQL query/command
executed, you also have the ability to customize the ORM to use either SPROCs, or your own
custom SQL Expressions, instead. This provides you with a great deal of flexibility when building
and extending your data access layer.
In future blog posts in this series I'll cover some remaining LINQ to SQL concepts including:
Single Table Inheritance, Deferred/Eager Loading, Optimistic Concurrency, and handling MultiTier scenarios.
Download