Click here to Skip to main content
Click here to Skip to main content

LINQ to SQL: Advanced Concepts and Features

By , 2 Mar 2013
Rate this:
Please Sign up or sign in to vote.

Introduction

In my first three articles on CodeProject.com, I have explained the fundamentals of Windows Communication Foundation (WCF), including:

From last month, I have started to write a few articles to explain LINQ, LINQ to SQL, Entity Framework, and LINQ to Entities. Followings are those articles I wrote or plan to write for LINQ, LINQ to SQL, and LINQ to Entities:

After finishing these five articles, I will come back to write some more articles on WCF from my real work experience, which will be definitely helpful to your real world work, if you are using WCF right now.

Q & A

Before going any further, let me answer a question raised by a reader of my previous article: what is the future of LINQ to SQL? Is it dead?

The answer is, yes, it is dead. Microsoft has officially announced that Entity Framework will be the preferred ORM from Microsoft. Actually, LINQ to SQL might be conisdred just an intermediate product for the C# team to prove the success of the then new language C#. But since it has been there for a while, and it is so easy to use, and a few people are already using it in production, I think it is still worth to learn or at least to understand it.

Overview

Now let's come back to LINQ to SQL. In the previous article, we learned some basic concepts and features of LINQ to SQL, such as querying and updating databases with tables and views, and changing loading behaviors with load options.

In this article, we will learn some advanced features of LINQ to SQL, such as Stored Procedure support, concurrency control, and transactional processing.

We will cover the following topics in this article:

  • Calling a Stored Procedure
  • Compiled query
  • Direct SQL
  • Dynamic query
  • Inheritance support
  • Concurrency control
  • Transaction support
  • Entity class validation
  • Debugging LINQ to SQL programs

Calling a Stored Procedure

Calling a Stored Procedure is different from a table or a view, because a Stored Procedure can have input parameters, output parameters, and it can return multiple result-sets. It can also return different result-sets dynamically, making it even harder to interpret the results. The modeling of a Stored Procedure is also different from a table or view. In the following sections, we will see how to call a simple Stored Procedure, how to map the return result of a Stored Procedure to an entity class, and how to handle output parameters, return code, and multiple result-sets.

We will reuse the same application that we used in the previous article, and add more testing methods to the program. If you haven't read my previous article (LINQ to SQL: Basic Concepts and Features), please do so now, or you can just go to download the source code.

Calling a Simple Stored Procedure

First, we will try to call a simple Stored Procedure. In the sample database, there is a Stored Procedure called “Ten Most Expensive Products”. We will call this Stored Procedure to get the top ten most expensive products.

  • Before we can call this Stored Procedure, we need to model it.
  • Open the Northwind.dbml designer.
  • From Server Explorer, expand the node Stored Procedures.
  • Drag the Stored Procedure Ten Most Expensive Products to the right hand panel of the Northwind.dbml design surface.

This will add the method Ten_Most_Expensive_Products to the NorthwindDataContext class, and add a new class Ten_Most_Expensive_ProductsResult as the result data type of the Stored Procedure.

Now, from Program.cs, we can call this Stored Procedure like this:

var tenProducts = from p in db.Ten_Most_Expensive_Products() select p;
foreach (var p in tenProducts)
{
    Console.WriteLine("Product Name: {0}, Price; {1}", 
                      p.TenMostExpensiveProducts, p.UnitPrice);
}

Because we know exactly the return result of the Stored Procedure, we can also replace the var data type with the specific return type, as in the following code:

IEnumerable<Ten_Most_Expensive_ProductsResult> 
            tenProducts = from p in db.Ten_Most_Expensive_Products() select p;
foreach (Ten_Most_Expensive_ProductsResult p in tenProducts)
{
    Console.WriteLine("Product Name: {0}, Price; {1}", 
                      p.TenMostExpensiveProducts, p.UnitPrice);
}

The output will be like this diagram:

LINQtoSQLAdvanced/6620_11_01.png

Mapping a Stored Procedure to an Entity Class

In the above example, LINQ to SQL creates a new type for the return result of the Stored Procedure. It actually just added the word “Result” after the Stored Procedure name to name the return data type. If we know the return result is a kind of entity, we can tell LINQ to SQL to use that entity as the return type instead of creating a new type.

For example, let’s create a Stored Procedure like this:

Create PROCEDURE [dbo].[GetProduct]
(
@ProductID int
)
AS
SET NOCOUNT ON
Select * from Products where ProductID = @ProductID

You can create this Stored Procedure in Microsoft SQL Server Management Studio, or by right clicking on the Stored Procedures node in the Server Explorer of Visual Studio 2008 and selecting Add New Stored Procedure from the context menu.

After the Stored Procedure has been created, drag and drop it onto the Product class on the Northwind.dbml design surface. Now LINQ to SQL will use the Product class as the return type of this Stored Procedure. The method for this Stored Procedure will be like this:

[Function(Name="dbo.GetProduct")]
public ISingleResult<Product> GetProduct([Parameter(Name="ProductID", 
       DbType="Int")] System.Nullable<int> productID)
{
    IExecuteResult result = this.ExecuteMethodCall(this, 
       ((MethodInfo)(MethodInfo.GetCurrentMethod())), productID);
    return ((ISingleResult<Product>)(result.ReturnValue));
}

From the signature of the method, we know the return type is Product.

Interestingly, if you drag and drop the same Stored Procedure to the right panel of the Northwind.dbml design surface, instead of the Product class, LINQ to SQL will automatically create a new class for the return type. The new method might be like this:

[Function(Name="dbo.GetProduct")]
public ISingleResult<GetProductResult> GetProduct1(
   [Parameter(Name="ProductID", DbType="Int")] 
    System.Nullable<int> productID)
{
    IExecuteResult result = this.ExecuteMethodCall(this, 
      ((MethodInfo)(MethodInfo.GetCurrentMethod())), productID);
    return ((ISingleResult<GetProductResult>)(result.ReturnValue));
}

And the generated return type class GetProductResult is almost identical to the Product class, except there are no event handling methods.

Another difference between the method GetProduct and GetProduct1 is, the product you retrieve using GetProduct is within the DataContext, so any changes you make to it will be committed back to the database if you call db.SubmitChanges() later, while the product you retrieve using GetProduct1 is not within the DataContext, and thus won’t be committed back to the database if you call db.SubmitChanges() later.

Also, when a Stored Procedure is dropped to an entity class, LINQ to SQL will first check the return result of the Stored Procedure to make sure it is compatible with the target class. If not, you will get a warning message and the Stored Procedure won’t be mapped on the model. For example, if you drag and drop the Stored Procedure Ten Most Expensive Products to the Product class, you will see a dialog box like this:

LINQtoSQLAdvanced/6620_11_02.png

Handling Output Parameter, Return Code, Multiple Shapes of a Single Result-set, and Multiple Result-sets

Now that we have a basic understanding of LINQ to SQL Stored Procedure processing, we will create a fairly complex Stored Procedure with an input parameter, output parameter, return code, multiple shapes of a single result-set, and multiple result-sets.

Creating a Complex Stored Procedure

Before we explain the LINQ to SQL comprehensive Stored Procedure support, we will need to create a complex Stored Procedure. We will create a Stored Procedure called GetCategoryDetails. The stored procedure will have an input parameter CategoryID to specify which category it is for, and an output parameter AveProductPrice to give the average price of all products in that category.

The first result-set of this Stored Procedure will give some information of the category depending on the value of another input parameter FullOrPartial. If FullOrPartial is true (1), this result-set will contain all columns of the Categories table for the requested category. Otherwise, it will contain only the CategoryID and CategoryName of the category.

The second result-set will contain all products for that category.

If the input parameter is not a valid category ID, it returns an error code of 10001, and stops; otherwise it returns 0 at the end of the Stored Procedure to indicate success.

This is the SQL to create this Stored Procedure:

CREATE PROCEDURE [dbo].[GetCategoryDetails]
@CategoryID int,
@FullOrPartial bit,
@AveProductPrice money OUTPUT
AS
 
  SET NOCOUNT ON

  if not exists (select 1 
    from categories 
    where categoryID = @categoryID)
  return 10001
  
  if @FullOrPartial = 1
    select * from Categories
    where categoryID = @categoryID
  else
    select categoryID, categoryName from Categories
    where categoryID = @categoryID
    
  select * from products
  where categoryID = @categoryID
  
  select @AveProductPrice = avg(UnitPrice)
  from products
  where categoryID = @CategoryID
 
return 0

Modeling the Stored Procedure

In order to call this complex Stored Procedure, we first need to add it into the Northwind.dbml model. Just drag and drop it from Server Explorer to the right hand panel of the Northwind.dbml design surface. If you have created it in SQL Management Studio and can’t see it from Server Explorer, try to refresh your Server Explorer.

the LINQ to SQL designer will create following method in the class NorthwindDataContext within the file Northwind.designer.cs:

[Function(Name="dbo.GetCategoryDetails")]
public ISingleResult<GetCategoryDetailsResult> 
       GetCategoryDetails([Parameter(Name="CategoryID", 
       DbType="Int")] System.Nullable<int> categoryID, 
       [Parameter(Name="FullOrPartial", DbType="Bit")] 
       System.Nullable<bool> fullOrPartial, 
       [Parameter(Name="AveProductPrice", DbType="Money")] 
       ref System.Nullable<decimal> aveProductPrice)
{
  IExecuteResult result = this.ExecuteMethodCall(this, 
    ((MethodInfo)(MethodInfo.GetCurrentMethod())), 
    categoryID, fullOrPartial, aveProductPrice);
  aveProductPrice = ((System.Nullable<decimal>)(result.GetParameterValue(2)));
  return ((ISingleResult<GetCategoryDetailsResult>)(result.ReturnValue));
}

Note, the variable aveProductPrice is passed to the method call ExecuteMethodCall, but its actual value doesn’t come back after the call. The output value has to be retrieved using result.GetParameterValue.

And this class is also added for the return result (this is really the first result-set in the Stored Procedure):

public partial class GetCategoryDetailsResult

However, this is not what we want. The method GetCategoryDetails only returns one result-set, instead of two. We have to customize it for our needs.

Customizing the DataContext Class for the Stored Procedure

In the previous sections, we modeled the Stored Procedure with the LINQ to SQL designer, but the retuning result was not correct. In this section, we will customize it.

  • Extend the class NorthwindDataContext by adding a new class file called NorthwindDataContext.cs.
  • Inside this new class file NorthwindDataContext.cs, add the following using statements:
  • using System.Data.Linq;
    using System.Data.Linq.Mapping;
    using System.Reflection;
  • Add this class inside the file NorthwindDataContext.cs for one of the return results:
  • public class PartialCategory
    {
      public int CategoryID;
      public string CategoryName;
    }

    This class is in parallel to the class NorthwindDataContext. Next, we will use this class to define a new method.

  • Change the class definition to the following code (note: it should be changed to a partial class):
  • public partial class NorthwindDataContext
    {
      // modified GetCategoryDetails, to overwrite the generated one
      [Function(Name = "dbo.GetCategoryDetails")]
      [ResultType(typeof(PartialCategory))]
      [ResultType(typeof(Category))]
      [ResultType(typeof(Product))]
      public IMultipleResults GetWholeOrPartialCategoryDetails(
          [Parameter(Name="CategoryID", DbType="Int")] 
          System.Nullable<int> categoryID, 
          [Parameter(Name="FullOrPartial", DbType="Bit")] 
          System.Nullable<bool> fullOrPartial, 
          [Parameter(Name="AveProductPrice", DbType="Money")] 
          ref System.Nullable<decimal> aveProductPrice)
      {
        IExecuteResult result = this.ExecuteMethodCall(this, 
          ((MethodInfo)(MethodInfo.GetCurrentMethod())), 
            categoryID, fullOrPartial, aveProductPrice);
        aveProductPrice = ((System.Nullable<decimal>)(result.GetParameterValue(2)));
      return ((IMultipleResults)(result.ReturnValue));
      }
    }

As you can see, we defined a method GetWholeOrPartialCategoryDetails to map the results of the Stored Procedure to different types.

We can also modify the generated method inside the file Northwind.designer.cs to meet our needs, but it is recommended not doing so, because if you modify it, and later on it is regenerated, you will lose all your changes.

Testing the Stored Procedure

Now inside the file program.cs, we can add this test method:

static void TestComplexStoredProcedure(int categoryID, bool wholeOrPartial)
{
  decimal? avePrice = 0;
  IMultipleResults result = 
    db.GetWholeOrPartialCategoryDetails(categoryID, wholeOrPartial, ref avePrice);
  int returnCode = (int)result.ReturnValue;
  
  if (returnCode == 0)
  {
    if (wholeOrPartial == true)
    {
      Category wholeCategory = result.GetResult<Category>().FirstOrDefault();
      Console.WriteLine("Category name: {0}", wholeCategory.CategoryName);
      Console.WriteLine("Category description: {0}", wholeCategory.Description);
    }
    else
    {
      PartialCategory partialCategory = 
         result.GetResult<PartialCategory>().FirstOrDefault();
      Console.WriteLine("Category name: {0}", partialCategory.CategoryName);
    }
    Console.WriteLine("Average product price: {0}", avePrice);
    IEnumerable<Product> products = result.GetResult<Product>();
    Console.WriteLine("Total products in category: {0}", products.Count());
  }
  else
  {
    Console.WriteLine("No category is retrieved, return code : {0}", returnCode);
  }
}

And inside the Main method, we call the above method thrice, like this:

// get full category details
TestComplexStoredProcedure (2, true);
// get partail category details
TestComplexStoredProcedure (6, false);
// invalid category ID
TestComplexStoredProcedure (999, true);

The first call will return the full category info for category 2, including the category ID, name, description, and picture. The second call will return only partial information for category 6, including category ID and name. In both cases, it will return the products in the category and the average product price in that category. The third call will print an error message because there is no category with ID 999.

The output is like this:

Compiled Query

It is common in many applications to execute structurally similar queries many times. In such cases, it is possible to increase performance by compiling the query once and executing it several times in the application with different parameters. This result is obtained in LINQ to SQL by using the CompiledQuery class.

The following code shows how to define a compiled query:

Func<NorthwindDataContext, string, IQueryable<Product>> fn = 
        CompiledQuery.Compile((NorthwindDataContext db2, string category) => 
from p in db2.Products
where p.Category.CategoryName == category
select p);

var products1 = fn(db, "Beverages");
Console.WriteLine("Total products in category Beverages: {0}", products1.Count());

var products2 = fn(db, "Seafood");
Console.WriteLine("Total products in category Seafood: {0}", products2.Count());

As you can see, a compiled query is actually a function. The function contains a compiled LINQ query expression, and can be called just like a regular function.

Direct SQL

LINQ to SQL is part of the ADO.NET family of technologies. It is based on services provided by the ADO.NET provider model, so it is possible to mix LINQ to SQL code with existing ADO.NET applications. For example, you can create a DataContext using an existing ADO.NET connection.

In some cases, you might find that the query or submit changes facility of the DataContext is insufficient for the specialized task you may want to perform. In these circumstances, it is possible to use the DataContext to issue raw SQL commands directly to the database.

The ExecuteQuery() method lets you execute a raw SQL query and converts the result of your query directly into objects.

The ExecuteCommand() method lets you execute SQL commands directly on the database.

For example, the following code will retrieve all discontinued products, and update the price for one product:

var products = db.ExecuteQuery<Product>(
  "SELECT ProductID, ProductName " +
  "FROM Products " +
  "WHERE Discontinued = 0 " +
  "ORDER BY ProductName;"
);
 
Console.WriteLine("Total discontinued products :{0}", products.Count());

int rowCount = db.ExecuteCommand(
  " update products "
  + "set UnitPrice=UnitPrice+1 "
  + "where productID=35");

if (rowCount < 1)
  Console.WriteLine("No product is updated");
else
  Console.WriteLine("Product price is updated");

Dynamic Query

Besides using LINQ syntax, we can also build queries at runtime dynamically using Expressions. For example, the following code will create two method expressions, one for the where clause and one for the order by clause.

ParameterExpression param = Expression.Parameter(typeof(Product), "p");
 
Expression left = 
  Expression.Property(param, typeof(Product).GetProperty("UnitPrice"));
Expression right = 
  Expression.Constant((decimal)100.00, typeof(System.Nullable<decimal>));
Expression filter = Expression.GreaterThanOrEqual(left, right);
Expression pred = Expression.Lambda(filter, param);
 
IQueryable products = db.Products;
 
Expression expr = Expression.Call(typeof(Queryable), "Where",
  new Type[] { typeof(Product) }, Expression.Constant(products), pred);
  expr = Expression.Call(typeof(Queryable), "OrderBy",
  new Type[] { typeof(Product), typeof(string) }, expr, 
  Expression.Lambda(Expression.Property(param, "ProductName"), param));
 
IQueryable<Product> query = db.Products.AsQueryable().Provider.CreateQuery<Product>(expr);
 
foreach (var p in query)
  Console.WriteLine("Product name: {0}", p.ProductName);

To build the first expression, we first create a left expression and a right expression, then use them to create a filter expression. The predicate expression is then created based on this filter expression.

The second expression takes the first expression as an argument, so it expands the first expression to include an order by expression.

The sentence with the method CreateQuery is the one that creates the query dynamically, according to the expressions we have created before this sentence. And of course, the query won’t get executed until the foreach statement is executed.

Before running this program, you need to add this using statement in the beginning:

using System.Linq.Expressions;

The output of the above code is like this:

Inheritance

The LINQ to SQL Object Relational Designer (O/R Designer) supports the concept of single-table inheritance as it is often implemented in relational systems. In the following sections, we will explore what single-table inheritance is, and how to use it with LINQ.

LINQ to SQL Single-Table Inheritance

In single-table inheritance, there is a single database table that contains fields for both parent information and child information. With relational data, a discriminator column contains the value that determines which class any given record belongs to.

For example, consider a Persons table that contains everyone employed by a company. Some people are employees and some people are managers. The Persons table contains a column named EmployeeType that has a value of 1 for managers and a value of 2 for employees; this is the discriminator column.

In this scenario, you can create a subclass of employees and populate the class with only records that have an EmployeeType value of 2. You can also remove columns that do not apply from each of the classes.

In our Northwind database, the Products table contains all products from 8 categories. Suppose all products share some common properties, and each category also has some unique properties by itself, we can then define a BaseProduct entity class for all common properties of the products, and define a unique child entity class for each category.

We assume all products have the following properties: ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder.

To simplify the example, we will define only two child entity classes in this example, one for beverage products and another for sea food products. We assume beverage products have one more property Discontinued and sea food products have one more property ReorderLevel.

Modeling the BaseProduct and Beverage Classes

We will first model these classes with the LINQ to SQL designer.

  • Open Server Explorer, and drag the Products table to the Northwind.dbml design surface. Change the entity class name from Product1 to BaseProduct, and delete its member properties Discontinued and ReorderLevel.
  • Drag another instance of the Products table from Server Explorer to the Northwind.dbml design surface, change its name from Product1 to Beverage. Click the association line between Category and Beverage and delete it. Then delete all of its properties except Discontinued.
  • Now we need to set up the inheritance relationship between the BaseProduct and Beverage classes. Open the Object Relational Designer Toolbox, and click the shape Inheritance.

LINQtoSQLAdvanced/6620_11_05.png

  • While the cursor is being changed, click the Beverage class, then click the BaseProduct class to connect them together.
  • Click the newly created association line between the BaseProduct and Beverage classes, and set the required properties.

The properties of the association between BaseProduct and Beverage should be like this:

LINQtoSQLAdvanced/6620_11_05.png

Modeling the Seafood Class

Next we need to model the Seafood class. This class will inherit from the BaseProduct class, with an extra property ReorderLevel.

  • Drag another instance of the Products table from the Server Explorer to the Northwind.dbml design surface, change its name from Product1 to Seafood.
  • Click the association line between Category and Seafood and delete it. Then delete all of its properties except ReorderLevel.
  • Do the same thing as we did for the Beverage class to set up the inheritance relationship between BaseProduct and Seafood class, except set the Derived Class Discriminator Value to 8. Actually, this is the only inheritance value you need to set for this class because all the other three properties (Inheritance Default, Base Class Discriminator Value, and Discriminator) have been set previously.

The finished model should be like this diagram:

The Generated Classes With Inheritance

Save the model and open the Northwind.designer.cs file, you will see three classes were added to the DataContext. The first class is the BaseProduct class, which has this signature:

[Table(Name="dbo.Products")]
[InheritanceMapping(Code="0", Type=typeof(BaseProduct), IsDefault=true)]
[InheritanceMapping(Code="1", Type=typeof(Beverage))]
[InheritanceMapping(Code="8", Type=typeof(Seafood))]
public partial class BaseProduct : INotifyPropertyChanging, INotifyPropertyChanged

Its class body is almost identical to the Product class, except without the properties ReorderLevel and Discontinued. The three inheritance mappings attributes are generated from the inheritance properties we set in the model.

So why don’t we just use the existing Product class as the base class?

Actually, it is OK, and realistically should be the preferred way to use the Product class as the base class. However, we have used the Product class in all our previous examples and if we delete two of its properties, some of those examples might not work. So we decided to create a new class from the same table for this example only.

The other two classes are for the derived classes, each has only one property:

public partial class Beverage : BaseProduct
public partial class Seafood : BaseProduct

Testing Inheritance

Now we can write a query to show the inheritance between BaseProduct and those two derived classes.

First, we can retrieve all the beverage products using the is operator, like this:

var beverages1 = from b in db.BaseProducts
where b is Beverage
select b;

Or we can use the OfType operator to retrieve the same products:

var beverages2 = from b in db.BaseProducts.OfType<Beverage>()
select b;
 
Console.WriteLine("Total number of beverage products: {0}", beverages1.Count());
Console.WriteLine("Total number of beverage products: {0}", beverages2.Count());

Run the program and you will see both queries return 12.

LINQtoSQLAdvanced/6620_11_08.png

We can also use the as operator to search all products for beverages:

var beverages3 = from b in db.BaseProducts
select b as Beverage;
 
foreach (var b in beverages3)
{
  if (b != null)
  {
    Console.WriteLine("Found a beverage: {0}, it is {1} discontinued", 
    b.ProductName, (b.Discontinued?"":"not"));
  }
}

In the above code, if a product is not a beverage, it will return null.

In all the above three queries, Discontinued is a property of the returning item, which means it is of type Beverage. Also, all of the BaseProduct properties are available, because the returning item’s data type is a child of the BaseProduct type.

LINQtoSQLAdvanced/6620_11_09.png

Similarly, we can retrieve all sea food products and use the property ReorderLevel, like this:

var seafood = from s in db.BaseProducts.OfType<Seafood>()
select s;
 
foreach (var s in seafood)
    Console.WriteLine("Product name: {0} Reorder level: {1}", 
          s.ProductName, s.ReorderLevel);

The output is like this:

LINQtoSQLAdvanced/6620_11_10.png

Handling Simultaneous (Concurrent) Updates

If two users are updating the same record at the same time, some conflicts will occur. There are normally three different ways to handle conflicts. The first method is to let the last update win, so no controlling mechanism is needed. The second one is to use a pessimistic lock, in which case before updating a record, a user will first lock the record, then process and update the record. At the same time, all other users have to wait for the lock to be released in order to start the updating process.

The third and the most used mechanism in an enterprise product is optimistic locking. A user doesn’t lock a record for update, but when it is ready to commit the changes, it will first check if any other user has updated the same record. If nobody else has ever changed the same record, the update will be committed. If any other user has changed the same record, the update will fail, and the user has to decide what to do with the conflicts. Some possible options include overwriting the previous changes, discarding its own changes, and refreshing the record then reapplying (merging) the changes.

LINQ to SQL supports optimistic concurrency control in two ways. Next, we will explain both of them.

Detecting Conflicts Using the Update Check Property

The first way is using the Update Check property. At design time, this property can be set for a column to be one of these three values:

  • Always
  • Never
  • WhenChanged

For a column, there are three values to remember: the original value before update, the current value to be updated, and the database value when the change is submitted. For example, if you fetch a product record from a database with a Unit Price of 25.00, and you update it to 26.00. After you fetch this product but before you submit your changes back to the database, somebody else may have updated this product’s price to 27.00. In this example, the original value of the price is 25.00, the current value to update is 26.00, and the database value when the change is submitted is 27.00.

When the change is submitted to the database, the original value and the database value will be compared. If they are different, a conflict is detected.

Now let’s look at those three settings. The first setting of the property Update Check is Always, which means the column will always be used for conflict detecting. Whenever a record is being changed, this column will always be checked to see if it has been updated by other users. If it has, it raises a conflict. This is the default setting of this property, so by default, all columns will be used for conflict detecting.

The second setting of Never means this column will never be used for conflict detecting. When a change is submitted to the database, it will not check the status of this column, so even if this column has been updated by other users, it won’t raise any error.

The third setting, WhenChanged, is in between. It will be used for conflict detecting, but only if the current process has changed its value. If the current process hasn’t changed its value, it won’t care if some other processes have updated its value.

Writing the Test Code

To show how to use these three settings, we can write the following code:

// first user
Console.WriteLine("First User ...");
Product product = (from p in db.Products
    where p.ProductID == 2
    select p).First();

Console.WriteLine("Original price: {0}", product.UnitPrice);
product.UnitPrice = 26;
Console.WriteLine("Current price to update: {0}", product.UnitPrice);
// process more products
 
// second user
Console.WriteLine("Second User ...");
NorthwindDataContext db2 = new NorthwindDataContext();
Product product2 = (from p in db2.Products
    where p.ProductID == 2
    select p).First();
Console.WriteLine("Original price: {0}", product2.UnitPrice);
product2.UnitPrice = 26;
Console.WriteLine("Current price to update: {0}", product2.UnitPrice);
db2.SubmitChanges();
db2.Dispose();
 
// first user is ready to submit changes
Console.WriteLine("First User ...");
try
{
    db.SubmitChanges();
}
catch (ChangeConflictException)
{
    Console.WriteLine("Conflict is detected");
    foreach (ObjectChangeConflict occ in db.ChangeConflicts)
    {
        MetaTable metatable = db.Mapping.GetTable(occ.Object.GetType());
        Product entityInConflict = (Product)occ.Object;
        Console.WriteLine("Table name: {0}", metatable.TableName);
        Console.Write("Product ID: ");
        Console.WriteLine(entityInConflict.ProductID);
        foreach (MemberChangeConflict mcc in occ.MemberConflicts)
        {
            object currVal = mcc.CurrentValue;
            object origVal = mcc.OriginalValue;
            object databaseVal = mcc.DatabaseValue;
            MemberInfo mi = mcc.Member;
            Console.WriteLine("Member: {0}", mi.Name);
            Console.WriteLine("current value: {0}", currVal);
            Console.WriteLine("original value: {0}", origVal);
            Console.WriteLine("database value: {0}", databaseVal);
        }
    }
}

In this example, we first retrieved product 2 and update its price from 19.00 to 26.00. Then we simulate another user to retrieve the same product, and also update its price to 26.00. The second user submits the changes first with no error, but when the first user tries to submit the changes, a conflict is detected because at that time the original value 19.00 is different from the database value 26.00. We can also use ChangeConflicts of the DataContext to get the list of conflicts.

Testing the Conflicts

Add the following using statements first:

using System.Data.Linq.Mapping;
using System.Reflection;

Run the program, and you will get an output as shown below:

LINQtoSQLAdvanced/6620_11_11.png

Now open Northwind.dbml, click on the UnitPrice member of the Product class, change its UpdateCheck property to be Never, and run the program again; you won’t see the exception this time, because this column is not used for conflict detecting. The output is like this (you need to change its price back to 19.00 before you re-run the program):

Or you can open SQL Server Management Studio and add the column from there.

Modeling the Products Table with a Version Column

After saving the changes, drag the Products table from Server Explorer to the Northwind.dbml design surface, and keep the name Product1. Now, this table has a version controlling column LastUpdateVersion, with properties as shown below:

LINQtoSQLAdvanced/6620_11_14.png

Note that its UpdateCheck Property is set to Never. Actually, all other members’ UpdateCheck properties have been set to Never, because for this class, the LastUpdateVersion column and only this column will be used for conflict detecting.

Open the file Northwind.designer.cs, and you will see the column LastUpdateVersion has the following attributes:

[Column(Storage="_LastUpdateVersion", AutoSync=AutoSync.Always, 
  DbType="rowversion NOT NULL", CanBeNull=false, IsDbGenerated=true, 
  IsVersion=true, UpdateCheck=UpdateCheck.Never)]
public System.Data.Linq.Binary LastUpdateVersion

Writing the Test Code

We can write similar code to test this new version controlling mechanism:

// first user
Console.WriteLine("First User ...");
Product product = (from p in db.Products
  where p.ProductID == 3
  select p).First();
Console.WriteLine("Original unit in stock: {0}", product.UnitsInStock);
product.UnitsInStock = 26;
Console.WriteLine("Current unit in stock to update: {0}", product.UnitsInStock);
// process more products
 
// second user
Console.WriteLine("Second User ...");
NorthwindDataContext db2 = new NorthwindDataContext();
Product product2 = (from p in db2.Products
  where p.ProductID == 3
  select p).First();
Console.WriteLine("Original unit in stock: {0}", product2.UnitsInStock);
product2.UnitsInStock = 27;
Console.WriteLine("Current unit in stock to update: {0}", product2.UnitsInStock);
db2.SubmitChanges();
db2.Dispose();
 
// first user is ready to submit changes
Console.WriteLine("First User ...");
try
{
  db.SubmitChanges();
}
catch (ChangeConflictException)
{
  Console.WriteLine("Conflict is detected");
  foreach (ObjectChangeConflict occ in db.ChangeConflicts)
  {
    MetaTable metatable = db.Mapping.GetTable(occ.Object.GetType());
    Product entityInConflict = (Product)occ.Object;
    Console.WriteLine("Table name: {0}", metatable.TableName);
    Console.Write("Product ID: ");
    Console.WriteLine(entityInConflict.ProductID);
    foreach (MemberChangeConflict mcc in occ.MemberConflicts)
    {
      object currVal = mcc.CurrentValue;
      object origVal = mcc.OriginalValue;
      object databaseVal = mcc.DatabaseValue;
      MemberInfo mi = mcc.Member;
      Console.WriteLine("Member: {0}", mi.Name);
      Console.WriteLine("current value: {0}", currVal);
      Console.WriteLine("original value: {0}", origVal);
      Console.WriteLine("database value: {0}", databaseVal);
    }
  }
}

Testing the Conflicts

This time we try to update UnitInStock for product 3. From the output, we can see a conflict is detected again when the first user submits changes to the database.

Transactions Support

In the previous section, we learned that simultaneous changes by different users can be controlled by using a version column or the UpdateCheck property. Sometimes the same user may have made several changes and some of the changes might not succeed, in which case we need a way to control the behavior of the overall update result. This is handled by transaction support.

LINQ to SQL uses same transaction mechanism as ADO.NET, i.e. using implicit transactions, or explicit transactions. It can also participate in an existing ADO.NET transaction to let the outsider code decide the result of the updates.

Implicit Transactions

By default, LINQ to SQL uses an implicit transaction for each SubmitChanges call. All updates between two SubmitChanges calls are wrapped within one transaction.

For example, in the following code, we are trying to update two products. The second update will fail due to a constraint, so both updates will fail. Nothing will be written to the database.

Product prod1 = (from p in db.Products
  where p.ProductID == 4
  select p).First();
Product prod2 = (from p in db.Products
  where p.ProductID == 5
  select p).First();
prod1.UnitPrice += 1;
// update will fail because UnitPrice can't be < 0
prod2.UnitPrice = -5;
// both updates will fail because they are wihtin one transaction
db.SubmitChanges();

The output will be like this:

Explicit Transactions

Besides implicit transactions, you can also define a transaction scope to explicitly control the update behavior. All updates within a transaction scope will be within a single transaction, thus they will all succeed or fail.

For example, in the following code, we first start a transaction scope. Then within this transaction scope, we update one product and submit the change to the database. However, at this point, the update has not been really committed, as the transaction scope was still not close. We then try to update another product, which fails due to the same constraint as in the previous example. The final result is neither of those two products has been updated. Or we can say the first update has been rolled back.

using (TransactionScope ts = new TransactionScope())
{
  try
  {
    Product prod1 = (from p in db.Products
    where p.ProductID == 4
    select p).First();
    prod1.UnitPrice += 1;
    db.SubmitChanges();
    // now let's try to update another product
    Product prod2 = (from p in db.Products
      where p.ProductID == 5
      select p).First();
    
    // update will fail because UnitPrice can't be < 0
    prod2.UnitPrice = -5;
    db.SubmitChanges();
  }
  catch (System.Data.SqlClient.SqlException e)
  {
    // both updates will fail because they are wihtin one transaction
    Console.WriteLine("Updates failed. Error Message: {0}", e.Message);
  }
}

Note: TransactionScope is in the .NET assembly System.Transactions, so you need to first add a reference to System.Transactions, then add the following using statement to Program.cs:

using System.Transactions;

The output of the program is the same as in the previous example when implicit transaction is used.

If you start the program in debugging mode, after the first SubmitChanges is called, you can go to SQL Server Management Studio and query product 4’s price using the following statement:

select UnitPrice from products (nolock) where productID = 4

The nolock hint is equivalent to READUNCOMMITTED and it is used to retrieve dirty data which has not been committed. With this hint, you will see its price is added by 1. Then after the second SubmitChanges is called, an exception is thrown, and the transaction scope is closed. At this point, if you run the query again, you will see product 4’s price is rolled back to its original value.

Note after the first call to the method SubmitChanges, you shouldn’t use the following statement to query the price value of the product, otherwise you will not be able to get any result back; instead, you will be waiting forever as it is waiting for the transaction to get committed:

select UnitPrice from products where productID = 4

Participating in Existing ADO.NET Transactions

Because LINQ to SQL is part of the ADO.NET family, it can also participate in an existing ADO.NET transaction. No matter if the update is done in traditional ADO.NET code, or in LINQ to SQL, all of them will be committed at the same time, or rolled back if any of them fails.

In the following code, we will first update a product using a traditional ADO.NET connection, then update another product using LINQ to SQL. The second update will fail, making the whole transaction roll back.

string connString = "Server=your_db_name\\your_db_instance;initial " + 
                    "cataLog=Northwind;user=your_user_name;pwd=your_password";
SqlConnection conn = null;
SqlCommand cmd = null;
 
try
{
  // open the connection
  conn = new SqlConnection(connString);
  conn.Open();
  // Use pre-existing ADO.NET connection to create DataContext:
  NorthwindDataContext db2 = new NorthwindDataContext(conn);
  SqlTransaction trans = conn.BeginTransaction();
  try
  {
    //update first product using ADO.NET
    using (cmd = new SqlCommand())
    {
      cmd.CommandText = "UPDATE Products SET UnitPrice " + 
                        "= UnitPrice+1 WHERE ProductID = 4";
      cmd.Connection = conn;
      cmd.Transaction = trans;
     cmd.ExecuteNonQuery();
    }
 
    // update second product using LINQ to SQL
    // Share pre-existing ADO.NET transaction:
    db2.Transaction = trans;
    Product prod2 = (from p in db2.Products
      where p.ProductID == 5
      select p).First();
 
    // update will fail because UnitPrice can't be < 0
    prod2.UnitPrice = -5;
    db2.SubmitChanges();
    db2.Dispose();
 
    //commit the transaction
    trans.Commit();
  }
  catch (Exception e)
  {
    // both updates will fail because they are wihtin one transaction
    Console.WriteLine("Updates failed. Error Message: {0}", 
                      e.Message);
  }
}
catch (Exception e)
{
  Console.WriteLine("Can not connect to database. Error: {0}", 
                    e.Message);
}
finally
{
  if (cmd != null)
    cmd.Dispose();
  if (conn != null)
    conn.Dispose();
}

There are two things to note for the above code. First, we can’t re-use this connection string:

global::TestLINQToSQLApp.Properties.Settings.Default.NorthwindConnectionString

It is because the password is stripped out from this string.

Secondly, the following using statement has to be added to the beginning of the Program.cs file:

using System.Data.SqlClient;

The output of the program is still the same as in previous examples.

Adding Validations to Entity Classes

Validating data is the process of confirming that the values entered into data objects comply with the constraints in an object's schema, in addition to the rules established for your application. Validating data before you send updates to the underlying database is a good practice that reduces errors and the potential number of round trips between an application and the database.

The Object Relational Designer (O/R Designer) provides partial methods that enable users to extend the designer-generated code that runs during Inserts, Updates, and Deletes of complete entities, and also during and after individual column changes.

These validation methods are all partial methods, so there is no overhead at all if you didn’t implement them, because unimplemented partial methods are not compiled into IL.

You can implement a validation method in another partial class. In our example, we can add the following method to the existing NorthwindDataContext.cs file:

public partial class Product
{
  partial void OnProductNameChanging(string value)
  {
    if (value.IndexOf("@") >= 0)
      throw new Exception("ProductName can not contain @");
  }
}

Note that this method should be inside the partial class Product, not inside NorthwindDataContext.

Now we can test it using the following code:

Product product = (from p in db.Products
  where p.ProductID == 5
  select p).First();
 
try
{
  product.ProductName = "Name @ this place";
  db.SubmitChanges();
}
catch (Exception e)
{
  Console.WriteLine("Update failed. Reason: {0}", e.Message);
}

Run this program and you will get output like this:

You can implement any of the validation methods for any property, before or after the change.

Debugging LINQ to SQL Programs

Within Visual Studio 2008, when debugging a LINQ to SQL program, we can use the traditional Watch or QuickWatch window to inspect a variable. For example, after the following line is executed, we can right click on the products variable, and select QuickWatch … or Add Watch to see the content of this variable:

var products = from p in db.Products
where p.CategoryID == 1
select p;

The QuickWatch window will be like this:

LINQtoSQLAdvanced/6620_11_18.png

We can also hover the mouse over the products variable and wait for the Quick Info popup window to appear, then inspect it on the fly. The popup Quick Info window will be like this:

From the watch window, we can inspect the return result of the variable, all of its properties, and even its children.

Note: this inspecting may trigger a real query to the database. For example, if you hover your mouse over db.Products and try to open the Results View of it, the database will be queried to get all the products. In an environment with a big database, this may cause some problems.

Summary

In this article, we learned some advanced features of LINQ to SQL. At this point, we have a good understanding of LINQ to SQL, so in the next article, we will apply these skills to the data access layer of our WCF service to connect to databases securely and reliably with LINQ to SQL.

The key points in this article include:

  • LINQ to SQL fully supports Stored Procedures with return code, output parameters, and multiple result sets.
  • Compiled query can increase performance of repeatedly executed LINQ queries.
  • LINQ to SQL allows direct SQL queries to a database.
  • Dynamic Queries can be built at runtime using Expressions.
  • LINQ to SQL supports single-table inheritance by the use of a discriminator column.
  • Concurrent updates can be controlled using the Update Check property or a Version column.
  • By default, LINQ to SQL updates are within one implicit transaction.
  • Explicit transaction can be defined for LINQ to SQL updates using TransactionScope.
  • LINQ to SQL updates can also participate in traditional ADO.NET transactions.
  • Customized validation code can be added to LINQ to SQL entity classes.
  • A debugging process may trigger a real query to the database.

Note: this article is based on chapter 11 of my old book "WCF Multi-tier Services Development with LINQ" (ISBN 1847196624). Since LINQ to SQL is now not preferred from Microsoft, this book has been upgraded to using LINQ to Entities in my new book "WCF 4.0 Multi-tier Services Development with LINQ to Entities" (ISBN 1849681147). Both books are hands-on guides to learn how to build SOA applications on the Microsoft platform, with the old one using WCF and LINQ to SQL in Visual Studio 2008, and the new one using WCF and LINQ to Entities in Visual Studio 2010.

With either book, you can learn how to master WCF and LINQ to SQL/LINQ to Entities concepts by completing practical examples and applying them to your real-world assignments. They are among the first of few books to combine WCF and LINQ to SQL/LINQ to Entities in a multi-tier real-world WCF Service. They are ideal for beginners who want to learn how to build scalable, powerful, easy-to-maintain WCF Services. Both books are rich with example code, clear explanations, interesting examples, and practical advice. They are truly hands-on guides for C++ and C# developers.

You don't need to have any experience in WCF or LINQ to SQL/LINQ to Entities to read either book. Detailed instructions and precise screenshots will guide you through the whole process of exploring the new worlds of WCF and LINQ to SQL/LINQ to Entities. These two books are distinguished from other WCF and LINQ to SQL/LINQ to Entities books by that, they focus on how to do it, not why to do it, in such a way so you won't be overwhelmed by tons of information about WCF and LINQ to SQL/LINQ to Entities. Once you have finished the books, you will be proud that you have been working with WCF and LINQ to SQL/LINQ to Entities in the most straightforward way.

You can buy either book from Amazon (search WCF and LINQ), or from the publisher's website at https://www.packtpub.com/wcf-4-0-multi-tier-services-development-with-linq-to-entities/book.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Mike_Liu
Software Developer (Senior)
United States United States
Mike is a senior software engineer at an investment management firm, and a faculty member at Brandeis University, teaching Windows Communication Foundation programming with C#. He is a Microsoft Certified Solution Developer and a Sun Certified Java Programmer. He has authored a few books for Unix C/C++, C#, WCF and LINQ. Mike has deep expertise in software development with WCF, WPF, ASP.NET, MVC, BPMS, JavaScript, and SQL Server. He has been a key developer, architect and project lead for many software products in various industries, such as statistical data reporting, telecommunication, resource control, call center, student loan processing, and investment management.

Comments and Discussions

 
Questionlast record from database PinmemberBhosale sonali5-Jan-13 6:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140415.2 | Last Updated 2 Mar 2013
Article Copyright 2011 by Mike_Liu
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid