Applying LINQ to Entities to a WCF Service

Exclusive offer: get 50% off this eBook here
WCF 4.5 Multi-Layer Services Development with Entity Framework

WCF 4.5 Multi-Layer Services Development with Entity Framework — Save 50%

Build SOA applications on Microsoft platforms with this hands-on guide book and ebook.

$29.99    $15.00
by Mike Liu | February 2013 | Enterprise Articles Microsoft

In this article by Mike Liu, the author of WCF 4.5 Multi-Layer Services Development with Entity Framework, we will learn how to update a product with LINQ to Entities in the data access layer. We will see how to apply changes of a client object to LINQ to Entities and leave all of the update work to LINQ to Entities, and will also see how to control the concurrency of updates with LINQ to Entities.

(For more resources related to this topic, see here.)

Creating the LINQNorthwind solution

The first thing we need to do is create a test solution. In this article, we will start from the data access layer. Perform the following steps:

  1. Start Visual Studio.

  2. Create a new class library project LINQNorthwindDAL with solution name LINQNorthwind (make sure the Create directory for the solution is checked to specify the solution name).

  3. Delete the Class1.cs file.

  4. Add a new class ProductDAO to the project.

  5. Change the new class ProductDAO to be public.

Now you should have a new solution with the empty data access layer class. Next, we will add a model to this layer and create the business logic layer and the service interface layer.

Modeling the Northwind database

In the previous section, we created the LINQNorthwind solution. Next, we will apply LINQ to Entities to this new solution.

For the data access layer, we will use LINQ to Entities instead of the raw ADO.NET data adapters. As you will see in the next section, we will use one LINQ statement to retrieve product information from the database and the update LINQ statements will handle the concurrency control for us easily and reliably.

As you may recall, to use LINQ to Entities in the data access layer of our WCF service, we first need to add an entity data model to the project.

  1. In the Solution Explorer, right-click on the project item LINQNorthwindDAL, select menu options Add | New Item..., and then choose Visual C# Items | ADO.NET Entity Data Model as Template and enter Northwind.edmx as the name.

  2. Select Generate from database, choose the existing Northwind connection, and add the Products table to the model.

  3. Click on the Finish button to add the model to the project.

  4. The new column RowVersion should be in the Product entity . If it is not there, add it to the database table with a type of Timestamp and refresh the entity data model from the database

  5. In the EMD designer, select the RowVersion property of the Product entity and change its Concurrency Mode from None to Fixed. Note that its StoreGeneratedPattern should remain as Computed.

This will generate a file called Northwind.Context.cs, which contains the Db context for the Northwind database. Another file called Product.cs is also generated, which contains the Product entity class. You need to save the data model in order to see these two files in the Solution Explorer.

In Visual Studio Solution Explorer, the Northwind.Context.cs file is under the template file Northwind.Context.tt and Product.cs is under Northwind.tt. However, in Windows Explorer, they are two separate files from the template files.

Creating the business domain object project

During Implementing a WCF Service in the Real World, we create a business domain object (BDO) project to hold the intermediate data between the data access objects and the service interface objects. In this section, we will also add such a project to the solution for the same purpose.

  1. In the Solution Explorer, right-click on the LINQNorthwind solution.

  2. Select Add | New Project... to add a new class library project named LINQNorthwindBDO.

  3. Delete the Class1.cs file.

  4. Add a new class file ProductBDO.cs.

  5. Change the new class ProductBDO to be public.

  6. Add the following properties to this class:

    • ProductID

    • ProductName

    • QuantityPerUnit

    • UnitPrice

    • Discontinued

    • UnitsInStock

    • UnitsOnOrder

    • ReorderLevel

    • RowVersion

The following is the code list of the ProductBDO class:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LINQNorthwindBDO { public class ProductBDO { public int ProductID { get; set; } public string ProductName { get; set; } public string QuantityPerUnit { get; set; } public decimal UnitPrice { get; set; } public int UnitsInStock { get; set; } public int ReorderLevel { get; set; } public int UnitsOnOrder { get; set; } public bool Discontinued { get; set; } public byte[] RowVersion { get; set; } } }

As noted earlier, in this article we will use BDO to hold the intermediate data between the data access objects and the data contract objects. Besides this approach, there are some other ways to pass data back and forth between the data access layer and the service interface layer, and two of them are listed as follows:

  • The first one is to expose the Entity Framework context objects from the data access layer up to the service interface layer. In this way, both the service interface layer and the business logic layer—we will implement them soon in following sections—can interact directly with the Entity Framework. This approach is not recommended as it goes against the best practice of service layering.

  • Another approach is to use self-tracking entities. Self-tracking entities are entities that know how to do their own change tracking regardless of which tier those changes are made on. You can expose self-tracking entities from the data access layer to the business logic layer, then to the service interface layer, and even share the entities with the clients. Because self-tracking entities are independent of entity context, you don't need to expose the entity context objects. The problem of this approach is, you have to share the binary files with all the clients, thus it is the least interoperable approach for a WCF service. Now this approach is not recommended by Microsoft, so in this book we will not discuss it.

Using LINQ to Entities in the data access layer

Next we will modify the data access layer to use LINQ to Entities to retrieve and update products. We will first create GetProduct to retrieve a product from the database and then create UpdateProduct to update a product in the database.

Adding a reference to the BDO project

Now we have the BDO project in the solution, we need to modify the data access layer project to reference it.

  1. In the Solution Explorer, right-click on the LINQNorthwindDAL project.

  2. Select Add Reference....

  3. Select the LINQNorthwindBDO project from the Projects tab under Solution.

  4. Click on the OK button to add the reference to the project.

Creating GetProduct in the data access layer

We can now create the GetProduct method in the data access layer class ProductDAO, to use LINQ to Entities to retrieve a product from the database. We will first create an entity DbContext object and then use LINQ to Entities to get the product from the DbContext object. The product we get from DbContext will be a conceptual entity model object. However, we don't want to pass this product object back to the upper-level layer because we don't want to tightly couple the business logic layer with the data access layer. Therefore, we will convert this entity model product object to a ProductBDO object and then pass this ProductBDO object back to the upper-level layers.

To create the new method, first add the following using statement to the ProductBDO class:

using LINQNorthwindBDO;

Then add the following method to the ProductBDO class:

public ProductBDO GetProduct(int id) { ProductBDO productBDO = null; using (var NWEntities = new NorthwindEntities()) { Product product = (from p in NWEntities.Products where p.ProductID == id select p).FirstOrDefault(); if (product != null) productBDO = new ProductBDO() { ProductID = product.ProductID, ProductName = product.ProductName, QuantityPerUnit = product.QuantityPerUnit, UnitPrice = (decimal)product.UnitPrice, UnitsInStock = (int)product.UnitsInStock, ReorderLevel = (int)product.ReorderLevel, UnitsOnOrder = (int)product.UnitsOnOrder, Discontinued = product.Discontinued, RowVersion = product.RowVersion }; } return productBDO; }

Within the GetProduct method, we had to create an ADO.NET connection, create an ADO. NET command object with that connection, specify the command text, connect to the Northwind database, and send the SQL statement to the database for execution. After the result was returned from the database, we had to loop through the DataReader and cast the columns to our entity object one by one.

With LINQ to Entities, we only construct one LINQ to Entities statement and everything else is handled by LINQ to Entities. Not only do we need to write less code, but now the statement is also strongly typed. We won't have a runtime error such as invalid query syntax or invalid column name. Also, a SQL Injection attack is no longer an issue, as LINQ to Entities will also take care of this when translating LINQ expressions to the underlying SQL statements.

Creating UpdateProduct in the data access layer

In the previous section, we created the GetProduct method in the data access layer, using LINQ to Entities instead of ADO.NET. Now in this section, we will create the UpdateProduct method, using LINQ to Entities instead of ADO.NET.

Let's create the UpdateProduct method in the data access layer class ProductBDO, as follows:

public bool UpdateProduct( ref ProductBDO productBDO, ref string message) { message = "product updated successfully"; bool ret = true; using (var NWEntities = new NorthwindEntities()) { var productID = productBDO.ProductID; Product productInDB = (from p in NWEntities.Products where p.ProductID == productID select p).FirstOrDefault(); // check product if (productInDB == null) { throw new Exception("No product with ID " + productBDO.ProductID); } NWEntities.Products.Remove(productInDB); // update product productInDB.ProductName = productBDO.ProductName; productInDB.QuantityPerUnit = productBDO.QuantityPerUnit; productInDB.UnitPrice = productBDO.UnitPrice; productInDB.Discontinued = productBDO.Discontinued; productInDB.RowVersion = productBDO.RowVersion; NWEntities.Products.Attach(productInDB); NWEntities.Entry(productInDB).State = System.Data.EntityState.Modified; int num = NWEntities.SaveChanges(); productBDO.RowVersion = productInDB.RowVersion; if (num != 1) { ret = false; message = "no product is updated"; } } return ret; }

Within this method, we first get the product from database, making sure the product ID is a valid value in the database. Then, we apply the changes from the passed-in object to the object we have just retrieved from the database, and submit the changes back to the database. Let's go through a few notes about this method:

  1. You have to save productID in a new variable and then use it in the LINQ query. Otherwise, you will get an error saying Cannot use ref or out parameter 'productBDO' inside an anonymous method, lambda expression, or query expression.

  2. If Remove and Attach are not called, RowVersion from database (not from the client) will be used when submitting to database, even though you have updated its value before submitting to the database. An update will always succeed, but without concurrency control.

  3. If Remove is not called and you call the Attach method, you will get an error saying The object cannot be attached because it is already in the object context.

  4. If the object state is not set to be Modified, Entity Framework will not honor your changes to the entity object and you will not be able to save any change to the database.

Creating the business logic layer

Now let's create the business logic layer.

  1. Right click on the solution item and select Add | New Project.... Add a class library project with the name LINQNorthwindLogic.

  2. Add a project reference to LINQNorthwindDAL and LINQNorthwindBDO to this new project.

  3. Delete the Class1.cs file.

  4. Add a new class file ProductLogic.cs.

  5. Change the new class ProductLogic to be public.

  6. Add the following two using statements to the ProductLogic.cs class file:

    using LINQNorthwindDAL; using LINQNorthwindBDO;

  7. Add the following class member variable to the ProductLogic class:

    ProductDAO productDAO = new ProductDAO();

  8. Add the following new method GetProduct to the ProductLogic class:

    public ProductBDO GetProduct(int id) { return productDAO.GetProduct(id); }

  9. Add the following new method UpdateProduct to the ProductLogic class:

    public bool UpdateProduct( ref ProductBDO productBDO, ref string message) { var productInDB = GetProduct(productBDO.ProductID); // invalid product to update if (productInDB == null) { message = "cannot get product for this ID"; return false; } // a product cannot be discontinued // if there are non-fulfilled orders if (productBDO.Discontinued == true && productInDB.UnitsOnOrder > 0) { message = "cannot discontinue this product"; return false; } else { return productDAO.UpdateProduct(ref productBDO, ref message); } }

Build the solution. We now have only one more step to go, that is, adding the service interface layer.

Creating the service interface layer

The last step is to create the service interface layer.

  1. Right-click on the solution item and select Add | New Project.... Add a WCF service library project with the name of LINQNorthwindService.

  2. Add a project reference to LINQNorthwindLogic and LINQNorthwindBDO to this new service interface project.

  3. Change the service interface file IService1.cs, as follows:

    • Change its filename from IService1.cs to IProductService.cs.

    • Change the interface name from IService1 to IProductService, if it is not done for you.

    • Remove the original two service operations and add the following two new operations:

      [OperationContract] [FaultContract(typeof(ProductFault))] Product GetProduct(int id); [OperationContract] [FaultContract(typeof(ProductFault))] bool UpdateProduct(ref Product product, ref string message);

    • Remove the original CompositeType and add the following data contract classes:

      [DataContract] public class Product { [DataMember] public int ProductID { get; set; } [DataMember] public string ProductName { get; set; } [DataMember] public string QuantityPerUnit { get; set; } [DataMember] public decimal UnitPrice { get; set; } [DataMember] public bool Discontinued { get; set; } [DataMember] public byte[] RowVersion { get; set; } } [DataContract] public class ProductFault { public ProductFault(string msg) { FaultMessage = msg; } [DataMember] public string FaultMessage; }

    • The following is the content of the IProductService.cs file:

      using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace LINQNorthwindService { [ServiceContract] public interface IProductService { [OperationContract] [FaultContract(typeof(ProductFault))] Product GetProduct(int id); [OperationContract] [FaultContract(typeof(ProductFault))] bool UpdateProduct(ref Product product, ref string message); } [DataContract] public class Product { [DataMember] public int ProductID { get; set; } [DataMember] public string ProductName { get; set; } [DataMember] public string QuantityPerUnit { get; set; } [DataMember] public decimal UnitPrice { get; set; } [DataMember] public bool Discontinued { get; set; } [DataMember] public byte[] RowVersion { get; set; } } [DataContract] public class ProductFault { public ProductFault(string msg) { FaultMessage = msg; } [DataMember] public string FaultMessage; } }

  4. Change the service implementation file Service1.cs, as follows:

    • Change its filename from Service1.cs to ProductService.cs.

    • Change its class name from Service1 to ProductService, if it is not done for you.

    • Add the following two using statements to the ProductService.cs file:

      using LINQNorthwindLogic; using LINQNorthwindBDO;

    • Add the following class member variable:

      ProductLogic productLogic = new ProductLogic();

    • Remove the original two methods and add following two methods:

      public Product GetProduct(int id) { ProductBDO productBDO = null; try { productBDO = productLogic.GetProduct(id); } catch (Exception e) { string msg = e.Message; string reason = "GetProduct Exception"; throw new FaultException<ProductFault> (new ProductFault(msg), reason); } if (productBDO == null) { string msg = string.Format("No product found for id {0}", id); string reason = "GetProduct Empty Product"; throw new FaultException<ProductFault> (new ProductFault(msg), reason); } Product product = new Product(); TranslateProductBDOToProductDTO(productBDO, product); return product; } public bool UpdateProduct(ref Product product, ref string message) { bool result = true; // first check to see if it is a valid price if (product.UnitPrice <= 0) { message = "Price cannot be <= 0"; result = false; } // ProductName can't be empty else if (string.IsNullOrEmpty(product.ProductName)) { message = "Product name cannot be empty"; result = false; } // QuantityPerUnit can't be empty else if (string.IsNullOrEmpty(product.QuantityPerUnit)) { message = "Quantity cannot be empty"; result = false; } else { try { var productBDO = new ProductBDO(); TranslateProductDTOToProductBDO(product, productBDO); result = productLogic.UpdateProduct( ref productBDO, ref message); product.RowVersion = productBDO.RowVersion; } catch (Exception e) { string msg = e.Message; throw new FaultException<ProductFault> (new ProductFault(msg), msg); } } return result; }

    • Because we have to convert between the data contract objects and the business domain objects, we need to add the following two methods:

      private void TranslateProductBDOToProductDTO( ProductBDO productBDO, Product product) { product.ProductID = productBDO.ProductID; product.ProductName = productBDO.ProductName; product.QuantityPerUnit = productBDO.QuantityPerUnit; product.UnitPrice = productBDO.UnitPrice; product.Discontinued = productBDO.Discontinued; product.RowVersion = productBDO.RowVersion; } private void TranslateProductDTOToProductBDO( Product product, ProductBDO productBDO) { productBDO.ProductID = product.ProductID; productBDO.ProductName = product.ProductName; productBDO.QuantityPerUnit = product.QuantityPerUnit; productBDO.UnitPrice = product.UnitPrice; productBDO.Discontinued = product.Discontinued; productBDO.RowVersion = product.RowVersion; }

  5. Change the config file App.config, as follows:

    • Change Service1 to ProductService.

    • Remove the word Design_Time_Addresses.

    • Change the port to 8080.

    • Now, BaseAddress should be as follows: http://localhost:8080/LINQNorthwindService/ProductService/

    • Copy the connection string from the App.config file in the LINQNorthwindDAL project to the following App.config file:

      <connectionStrings> <add name="NorthwindEntities" connectionString="metadata=res://*/Northwind. csdl|res://*/Northwind.ssdl|res://*/Northwind. msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=Northwind;integrated security=True;Multipl eActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" /> </connectionStrings>

      You should leave the original connection string untouched in the App.config file in the data access layer project. This connection string is used by the Entity Model Designer at design time. It is not used at all during runtime, but if you remove it, whenever you open the entity model designer in Visual Studio, you will be prompted to specify a connection to your database.

Now build the solution and there should be no errors.

Testing the service with the WCF Test Client

Now we can run the program to test the GetProduct and UpdateProduct operations with the WCF Test Client.

You may need to run Visual Studio as administrator to start the WCF Test Client.

First set LINQNorthwindService as the startup project and then press Ctrl + F5 to start the WCF Test Client. Double-click on the GetProduct operation, enter a valid product ID, and click on the Invoke button. The detailed product information should be retrieved and displayed on the screen, as shown in the following screenshot:

Now double-click on the UpdateProduct operation, enter a valid product ID, and specify a name, price, quantity per unit, and then click on Invoke.

This time you will get an exception as shown in the following screenshot:

From this image we can see that the update failed. The error details, which are in HTML View in the preceding screenshot, actually tell us it is a concurrency error. This is because, from WCF Test Client, we can't enter a row version as it is not a simple datatype parameter, thus we didn't pass in the original RowVersion for the object to be updated, and when updating the object in the database, the Entity Framework thinks this product has been updated by some other users.

WCF 4.5 Multi-Layer Services Development with Entity Framework Build SOA applications on Microsoft platforms with this hands-on guide book and ebook.
Published: December 2012
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

Testing concurrency with our own client

From an earlier section, we know that with WCF Test Client we cannot test the UpdateProduct method, nor can we test the concurrency support of the WCF service, because with WCF Test Client, we cannot update products. To test the concurrency support of the Entity Framework, we have to use our own test client.

Creating the test client

In this section, we will create a WinForm client to get the product details and update price for a product.

Follow these steps to create the test client:

  1. In the Solution Explorer, right-click on the solution item, and select Add | New Project....

  2. Select Visual C# | Windows Forms Application as the template and change the name to LINQNorthwindClient. Click on the OK button to add the new project.

  3. On the form designer, add the following five controls:

    • A label named lblProductID with the text Product ID

    • A textbox named txtProductID

    • A button named btnGetProductDetails with the text &Get Product Details

    • A label named lblProductDetails with the text Product Details

    • A textbox named txtProductDetails with the Multiline property set to True

    The layout of the form is as shown in the following screenshot:

  4. In the Solution Explorer, right-click on the LINQNorthwindClient project and select Add Service Reference....

  5. On the Add Service Reference window, click on the Discover button, wait a minute until the service is displayed, then change Namespace from ServiceReference1 to ProductServiceRef, and click on the OK button.

    The Add Service Reference window should be as shown in the following screenshot:

Implementing the GetProduct functionality

Now that we have the test client created, we will customize the client application to test the new WCF service.

First, we need to customize the test client to call the WCF service to get a product from the database so that we can test the GetProduct operation with LINQ to Entities.

We will call a WCF service through the proxy, so let's add the following using statements to the form class in the Form1.cs file:

using LINQNorthwindClient.ProductServiceRef; using System.ServiceModel;

Then on the Forms Designer, double-click on the btnGetProductDetails button and add an event handler for this button, as follows:

private void btnGetProductDetail_Click(object sender, EventArgs e) { var client = new ProductServiceClient(); string result = ""; try { int productID = Int32.Parse(txtProductID.Text); var product = client.GetProduct(productID); var sb = new StringBuilder(); sb.Append("ProductID:" + product.ProductID.ToString() + "\r\n"); sb.Append("ProductName:" + product.ProductName + "\r\n"); sb.Append("QuantityPerUnit:" + product.QuantityPerUnit + "\r\n"); sb.Append("UnitPrice:" + product.UnitPrice.ToString() + "\r\n"); sb.Append("Discontinued:" + product.Discontinued.ToString() + "\r\n"); sb.Append("RowVersion:"); foreach (var x in product.RowVersion.AsEnumerable()) { sb.Append(x.ToString()); sb.Append(" "); } result = sb.ToString(); } catch (TimeoutException ex) { result = "The service operation timed out. " + ex.Message; } catch (FaultException<ProductFault> ex) { result = "ProductFault returned: " + ex.Detail.FaultMessage; } catch (FaultException ex) { result = "Unknown Fault: " + ex.ToString(); } catch (CommunicationException ex) { result = "There was a communication problem. " + ex.Message + ex.StackTrace; } catch (Exception ex) { result = "Other exception: " + ex.Message + ex.StackTrace; } txtProductDetails.Text = result; }

Implementing the UpdateProduct functionality

Next, we need to modify the client program to call the UpdateProduct operation of the web service. This method is particularly important to us because we will use it to test the concurrent update control of LINQ to Entities.

First, we need to add some more controls to the form. We will modify the form UI as follows:

  1. Open the Form1.cs file from the LINQNorthwindClient project.

  2. Add a label named lblNewPrice with the text New Price.

  3. Add a textbox named txtNewPrice.

  4. Add a button named btnUpdatePrice with the text &Update Price.

  5. Add a label named lblUpdateResult with the text Update Result.

  6. Add a textbox control named txtUpdateResult with the Multiline property set to True and Scrollbars set to Both.

The form should now appear as shown in the following screenshot:

Now double-click on the Update Price button and add the following event handler method for this button:

private void btnUpdatePrice_Click(object sender, EventArgs e) { string result = ""; if (product != null) { try { // update its price product.UnitPrice = Decimal.Parse(txtNewPrice.Text); var client = new ProductServiceClient(); var sb = new StringBuilder(); string message = ""; sb.Append("Price updated to "); sb.Append(txtNewPrice.Text); sb.Append("\r\n"); sb.Append("Update result:"); sb.Append(client.UpdateProduct(ref product, ref message).ToString()); sb.Append("\r\n"); sb.Append("Update message: "); sb.Append(message); sb.Append("\r\n"); sb.Append("New RowVersion:"); foreach (var x in product.RowVersion.AsEnumerable()) { sb.Append(x.ToString()); sb.Append(" "); } result = sb.ToString(); } catch (TimeoutException ex) { result = "The service operation timed out. " + ex.Message; } catch (FaultException<ProductFault> ex) { result = "ProductFault returned: " + ex.Detail.FaultMessage; } catch (FaultException ex) { result = "Unknown Fault: " + ex.ToString(); } catch (CommunicationException ex) { result = "There was a communication problem. " + ex.Message + ex.StackTrace; } catch (Exception ex) { result = "Other exception: " + ex.Message + ex.StackTrace; } } else { result = "Get product details first"; } txtUpdateResult.Text = result; }

Note that inside the Update Price button's event handler listed previously, we don't get the product from the database first. Instead, we re-use the same product object from the btnGetProductDetails_Click method, which means we will update whatever product we get when we click on the Get Product Details button. In order to do this, we need to move the product variable outside of the private method btnGetProductDetail_Click, to be a class variable, as follows:

Product product;

Inside the btnGetProductDetail_Click method, we need not define another variable product, but use the class member product now. Now, the first few lines of code for the Form1 class should be as follows:

public partial class Form1 : Form { Product product; public Form1() { InitializeComponent(); } private void btnGetProductDetail_Click(object sender, EventArgs e) { var client = new ProductServiceClient(); string result = ""; try { int productID = Int32.Parse(txtProductID.Text); product = client.GetProduct(productID); // More code to follow

As you can see, we didn't do anything specific with the concurrent update control of the update, but later in the Testing concurrent update manually section within this article, we will learn how LINQ to Entities inside the WCF service handles this for us. We will also capture all kinds of exceptions and display appropriate messages for them.

Testing the GetProduct and UpdateProduct operations

We can build and run the program to test the GetProduct and UpdateProduct operations now. Because we are still using the WCF Service Host to host our service, we need to start it first.

  1. Make the LINQNorthwindClient project the startup project and press F5 to start it.

    The service application (WcfSvcHost) should be started automatically (controlled by the settings at LINQNorthwindService Project | Properties | WCF Options | Start WCF Service Host when debugging another project in the same solution). You can also first start the Service Host in non-debugging mode and then start this test client application. Or set the solution to start with multiple projects, with the service as the first project to start and the client application the second.

  2. On the Client form (UI), enter 10 as the product ID in the Product ID textbox and click on the Get Product Details button to get the product details. Note that Unit Price is now 31.0000 and RowVersion is 0 0 0 0 0 0 74 159, as shown in following screenshot (the price and row version may be different in your database):

  3. Now enter 32 as the product price in the New Price textbox and click on the Update Price button to update its price. Update result should be True. Note that RowVersion has been changed to 0 0 0 0 0 0 74 160:

  4. To verify the new price, click on the Get Product Details button again to get the product details for this product and you will see that UnitPrice has been updated to 32.0000.

Testing concurrent update manually

We can also test concurrent updates by using the client application LINQNorthwindClient.

In this section, we will start two clients (let's call them Client A and Client B) and update the same product from these two clients at the same time. We will create a conflict between the updates from these two clients so that we can test if this conflict is properly handled by LINQ to Entities.

The test sequence will be as follows:

  1. Client A starts.

  2. Client B starts.

  3. Client A reads the product information.

  4. Client B reads the same product information.

  5. Client B updates the product successfully.

  6. Client A tries to update the product but fails.

The last step is where the conflict occurs as the product has been updated in between the read and the update by Client A.

The steps are as follows:

  1. Stop the debugging process if it hasn't already been stopped.

  2. Start the WCF Service Host application in non-debugging mode, if you have stopped it (you have to set LINQNorthwindService as the startup project first). Remember that you have to leave the WCF Test Client running while we are performing the following tests.

  3. Start the client application in non-debugging mode by pressing Ctrl + F5 (you have to make LINQNorthwindClient the startup project). We will refer to this client as Client A. As we mentioned in the previous section, you have options such as starting the WCF service and the client applications at the same time.

  4. In the Client A application, enter 10 in the Product ID textbox and click on the Get Product Details button to get the product's details. Note that UnitPrice is 35.0000 and RowVersion is 0 0 0 0 0 0 98 14.

  5. Start another client application in non-debugging mode by pressing Ctrl + F5. We will refer to this client as Client B.

  6. In the Client B application, enter 10 in the Product ID textbox and click on the Get Product Details button to get the product's details. Note that the UnitPrice is still 35.0000 and RowVersion is 0 0 0 0 0 0 98 14. Client B's form window should be identical to Client A's form window.

  7. On Client B's form, enter 36 as the product price in the New Price textbox and click on the Update Price button to update its price.

  8. Client B's update is committed to the database and the Update result value is True. The price of this product has now been updated to 36 and RowVersion has been updated to a new value of 0 0 0 0 0 0 98 15.

  9. In the Client B application, click on the Get Product Details button to get the product details to verify the update. Note that UnitPrice is now 36.0000 and RowVersion is now 0 0 0 0 0 0 98 15.

  10. On Client A's form, enter 37 as the product price in the New Price textbox and click on the Update Price button to update its price.

  11. Client A's update fails with an error message, Entities may have been modified or deleted since entities were loaded.

  12. In the Client B application, click on the Get Product Details button again to get the product's details. You will see that the UnitPrice is still 36.0000 and RowVersion is still 0 0 0 0 0 0 98 15, which means that Client A's update didn't get committed to the database.

The following screenshot is for Client B. You can see Update result is True and the price after the update is 36:

The following screenshot is for Client A. You can see that the price before the update is 35.0000 and the update fails with an error message. This error message is caught as an unknown fault from the client side, because we didn't handle the concurrency exception in our service.

From the preceding test, we know that the concurrent update is controlled by LINQ to Entities. An optimistic locking mechanism is enforced and one client's update won't overwrite another client's update. The client that has a conflict will be notified by a fault message.

Concurrent update locking is applied at the record level in the database. If two clients try to update different records in the database, they will not interfere with each other. For example, if you repeat the previous steps to update product 10 in one client and product 11 in another client, there will be no problem at all.

Testing concurrent updates automatically

In the previous section, we tested the concurrent update control of LINQ to Entities, but as you can see, the timing of the update is fully controlled by our input. We know exactly when the conflict will happen. In a real production, a conflict may happen at any time, with no indication as to when and how it will happen. In this section, we will simulate a situation such that a conflict happens randomly. We will add a new functionality to update one product 100 times and let two clients compete with each other until one of the updates fails.

For this test, we will put the actual updates in a background worker thread so that the main UI thread won't be blocked.

  1. Open the Form1.cs file from the LINQNorthwindClient project.

  2. Add a new class member to the form class for the worker thread, as follows:

    BackgroundWorker bw;

  3. Go to the Form1.cs design mode.

  4. Add another button called btnAutoUpdate with the text &Auto Update.

  5. Add the following click event handler for this new button:

    private void btnAutoUpdate_Click(object sender, EventArgs e) { if (product != null) { btnAutoUpdate.Text = "Updating Price ..."; btnAutoUpdate.Enabled = false; bw = new BackgroundWorker(); bw.WorkerReportsProgress = true; bw.DoWork += AutoUpdatePrice; bw.ProgressChanged += PriceChanged; bw.RunWorkerCompleted += AutoUpdateEnd; bw.RunWorkerAsync(); } else { txtUpdateResult.Text = "Get product details first"; } }

  6. Add the following methods to track the status of the updates, as the updates may take a while:

    private void AutoUpdateEnd(object sender, RunWorkerCompletedEventArgs e) { btnAutoUpdate.Text = "&Auto Update"; btnAutoUpdate.Enabled = true; } private void PriceChanged(object sender, ProgressChangedEventArgs e) { txtUpdateResult.Text = e.UserState.ToString(); // Scroll to end of textbox txtUpdateResult.SelectionStart = txtUpdateResult.TextLength-4; txtUpdateResult.ScrollToCaret(); }

  7. And finally add the following method to do the actual update:

    private void AutoUpdatePrice(object sender, DoWorkEventArgs e) { var client = new ProductServiceClient(); string result = ""; try { // update its price for (int i = 0; i < 100; i++) { // refresh the product first product = client.GetProduct(product.ProductID); // update its price product.UnitPrice += 1.0m; var sb = new StringBuilder(); String message = ""; sb.Append("Price updated to "); sb.Append(product.UnitPrice.ToString()); sb.Append("\r\n"); sb.Append("Update result:"); bool updateResult = client.UpdateProduct(ref product, ref message); sb.Append(updateResult.ToString()); sb.Append("\r\n"); sb.Append("Update message: "); sb.Append(message); sb.Append("\r\n"); sb.Append("New RowVersion:"); foreach (var x in product.RowVersion.AsEnumerable()) { sb.Append(x.ToString()); sb.Append(" "); } sb.Append("\r\n"); sb.Append("Price updated "); sb.Append((i + 1).ToString()); sb.Append(" times\r\n\r\n"); result += sb.ToString(); // report progress bw.ReportProgress(i+1, result); // sleep a while var random = new Random(); int randomNumber = random.Next(0, 1000); System.Threading.Thread.Sleep(randomNumber); } } catch (TimeoutException ex) { result += "The service operation timed out. " + ex.Message; } catch (FaultException<ProductFault> ex) { result += "ProductFault returned: " + ex.Detail.FaultMessage; } catch (FaultException ex) { result += "Unknown Fault: " + ex.ToString(); } catch (CommunicationException ex) { result += "There was a communication problem. " + ex.Message + ex.StackTrace; } catch (Exception ex) { result += "Other exception: " + ex.Message + ex.StackTrace; } // report progress bw.ReportProgress(100, result); }

The concept here is that once this button is clicked, it will keep updating the price of the selected product 100 times, with a price increase of 1.00 in each iteration. If two clients—again let's call them Client A and Client B—are running and this button is clicked for both the clients, one of the updates will fail as the other client will also be updating the same record.

The sequence of the updates will be as follows:

  1. Client A reads the product's details, updates the product, and commits the changes back to the database.

  2. Client A sleeps for a while, then repeats the preceding step.

  3. Client B reads the product's details, updates the same product, and commits the changes back to the database.

  4. Client B sleeps for a while, then repeats the preceding step.

  5. At some point, these two sets of processes will cross; so the following events will happen:

    • Client A reads the product's details.

    • Client A processes the product in memory.

    • Client B reads the product's details.

    • Client A finishes processing and commits the changes back to the database.

    • Client B finishes processing and tries to commit the changes back to the database.

    • Client B update fails, because it finds that the product has been updated while it was still processing the product.

    • Client B stops.

    • Client A keeps updating the product until it has done so 100 times.

Now follow these steps to finish this test:

  1. Start the WCF Service Host application in non-debugging mode, if you had stopped it (you have to set LINQNorthwindService as the startup project first).

  2. Make LINQNorthwindClient the startup project and then run it twice in non-debugging mode by pressing Ctrl + F5. Two clients should be up and running.

  3. From each client, enter 3 in the Product ID textbox and click on Get Product Details to get the product details. Both clients should display the price as 10.0000.

  4. Click on the Auto Update button on each client.

You will see that one of the clients fails while the other one is keeping the updates to the end of 100 times.

The following screenshot shows the results in the successful client. As you can see, the initial price of the product was 10.0000, but after the updates, it has been changed to 207.0000. From the source code, we know that this client only updates the price 100 times with an increase of 1.00 each time, so we know that another client has updated this product 97 times.

The following screenshot shows the results in the failed client. As you can see, the initial price of the product was 10.000. After updating the price 97 times, when this client tries to update the price again, it fails with the error message Entities may have been modified or deleted since entities were loaded. From the results of the other client, we also know that this client has updated the product 97 times.

However, if you enter two different product IDs in each client, both client updates will be successful until all 100 updates have been made. This again proves that locking is applied on a record level of the database.

Summary

In this article, we used LINQ to Entities to communicate with the database in the data access layer rather than using the raw ADO.NET APIs. We have used only one LINQ statement to retrieve product information from the database and as you have seen, the updates with LINQ to Entities prove to be much easier than with the raw ADO.NET data adapters. Now, WCF and LINQ are combined together for our services, so we can take advantage of both technologies.

Resources for Article :


Further resources on this subject:


WCF 4.5 Multi-Layer Services Development with Entity Framework Build SOA applications on Microsoft platforms with this hands-on guide book and ebook.
Published: December 2012
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

About the Author :


Mike Liu

Mike Liu studied Mathematics and Software Engineering at Nanjing University and Brandeis University, graduated with a bachelor’s degree and a master's degree respectively. He is a Sun Certified Java Programmer (SCJP), Microsoft Certified Solution Developer for Visual Studio 6.0 and Microsoft Certified Solution Developer for .NET. He has been working as a software Engineer/architect on various platforms (DOS, Unix, and Windows) using C/C++, Java, VB/VB.NET, and C#.

Mike started using C# for production development back in 2001 when C# was still in beta stage and he is now working as a senior software engineer for an investment management firm in Boston, Mass.

Mike had his first book,MITT: Multi-user Integrated Table-processing Tool Under Unix,published in 1993, and a second book,Advanced C# Programming,published in 2003. The previous two versions of this book,WCF Multi-tier Services Development with LINQ and WCF 4.0 Multi-tier Services Development with LINQ to Entities,were published in 2008 and 2010.

Books From Packt


 Entity Framework Tutorial
Entity Framework Tutorial

 Entity Framework 4.1: Expert’s Cookbook
Entity Framework 4.1: Expert’s Cookbook

 LINQ Quickly
LINQ Quickly

 SOA Patterns with BizTalk Server 2009
SOA Patterns with BizTalk Server 2009

Microsoft Visual Studio LightSwitch Business Application Development
Microsoft Visual Studio LightSwitch Business Application Development

Microsoft Windows Server AppFabric Cookbook
Microsoft Windows Server AppFabric Cookbook

MVVM WCF Multi-tier Services Development with LINQ
WCF Multi-tier Services Development with LINQ

 WCF 4.0 Multi-tier Services Development with LINQ to Entities
WCF 4.0 Multi-tier Services Development with LINQ to Entities


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
p
S
g
E
2
H
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software