Update
An updated version of this article for .NET 4.5 can be found at Implementing a WCF Service with Entity Framework (v4.5).
Introduction
This is another article I've written on WCF/LINQ. In the first three articles on CodeProject.com, I'll explain the fundamentals of Windows Communication Foundation (WCF), including:
In the other articles, I'll explain LINQ, LINQ to SQL, Entity Framework, and LINQ to Entities. Followings are the articles I wrote for LINQ, LINQ to SQL, and LINQ to Entities:
Overview
In a previous article (Implementing a Basic Hello World WCF Service), we have learned how to create a basic WCF Service manually from scratch. In that article, we also manually created a host application, generated the proxy and configuration files, and created a test client to test the WCF Service. You can review that article to learn what a WCF Service is really like under the hood.
Now in this article, we will implement a simple WCF Service using built-in Visual Studio WCF templates. This simple WCF Service will have only one operation, GetProduct
. This operation will accept an integer input as the product ID, and connect to a backend database to retrieve the product details for the specified product ID using LINQ to Entities. The Microsoft sample database Northwind will be used for the backend database, and Visual Studio 2010 will be used as the IDE.
In a future article (Concurrency Control of a WCF Service with Entity Framework), we will implement another operation, UpdateProduct
, to update a product in the database through the Entity Framework. We will cover concurrency control in that article, and we will create a WPF test client in that article to test the UpdateProduct
operation with concurrency support.
In this article, we will build the solution in the following order:
- Create the WCF Service
- Create a new solution and project using WCF templates
- Create the service interface
- Create the operation contract
- Create the data contract
- Implement the service interface
- Modify the app.config file
- Test the service using the WCF Test Client
- Apply LINQ to Entities to the WCF Service
- Prepare the database
- Model the Northwind database
- Rename the EF Product class
- Retrieve product details from the database using EF
- Translate the ProductEntity object to a Product object
- Test the WCF Service with EF
- Test exceptions
Creating the WCF Service
In the first half of this article, we will create the simple WCF Service. This WCF Service will contain the GetProduct
operation, which will return a product object to the client. The product detail is hard coded in this WCF Service, except the ID will be the input from the client. Later in this article, we will enhance this WCF Service to use LINQ to Entities to retrieve the real product detail from a real backend database.
Creating a new solution and project using WCF templates
We need to create a new solution for this article, and add a new WCF project to this solution. There are a few built-in WCF service templates within Visual Studio 2010. In this article, we will use the service library template to create the WCF Service.
Follow these steps to create the WCF and EF solution and the project using the service library template:
- Start Visual Studio 2010, select the menu option File | New | Project..., and you will see the New Project dialog box.
- In the New Project window, specify Visual C# | WCF | WCF Service Library as the project template, WCFandEFService as the (project) name, and WCFandEFSolution as the solution name. Make sure that the Create directory for solution checkbox is selected.
- Click the OK button, and the solution is created with a WCF project inside it. The project already has a IService1.cs file to define a service interface, and Service1.cs to implement the service. It also has an app.config file, which we will cover shortly.
Now we have created the WCF Service project using the C# WCF Service Library template. This project is actually an application containing a sample WCF Service, a hosting application (WcfSvcHost), and a WCF Test Client. This means now we can use the built-in WCF Test Client to invoke the sample WCF Service.
To try it, just press Ctrl+F5, and you will see the WCF Test Client is up and running! Double click the GetData
operation in the left panel, enter a number in the Value textbox in the right panel, then click the button Invoke, and you will see the WCF Service has been invoked: the response of the WCF Service is now displayed in the bottom part of the right panel.
Note: in order to run the WCF Test Client, you have to log in to your machine as a local administrator. You may also need to run Visual Studio as an administrator. We will discuss more about the WCF Test Client later.
Creating the service interface
In the previous section, we created a WCF project using the WCF Service Library template. In this section, we will create the service interface contracts.
Because two sample files have already been created for us, we will try to re-use them as much as possible. Then, we will start customizing these two files to create the service contracts.
Creating the operation contract
To create the service operation contract, we need to open the IService1.cs file and do the following:
- Change the interface name from
IService1
to IProductService
. Don't be worried if you see the warning message before the interface definition line, as we will change the web.config file in one of the following steps. - Change the first operation contract definition from this line:
string GetData(int value);
to this line:
Product GetProduct(int id);
- Change the file's name from IService1.cs to IProductService.cs.
- Delete the operation GetDataUsingDataContract
With these changes, we have defined a service contract. It is used to get the product details for a specific product ID. The product type, which we used to define the service contract, is still not defined. We will define it right after this section.
The content of the service interface for IProductService
should look like this now:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCFandEFService
{
[ServiceContract]
public interface IProductService
{
[OperationContract]
Product GetProduct(int id);
}
}
Note: this is not the whole content of the IProductService.cs file. The bottom part of this file now should still have the class CompositeType
, which we will change to our Product
type in the next section.
Creating the data contract
Another important aspect of SOA design is that you shouldn't assume that the consuming application supports a complex object model. A part of the service boundary definition is the data contract definition for the complex types that will be passed as operation parameters or return values.
For maximum interoperability and alignment with SOA principles, you should not pass any .NET specific types such as DataSet
or Exception
s across the service boundary. You should stick to fairly simple data structure objects such as classes with properties, and backing member fields. You can pass objects that have nested complex types such as 'Customer with an Order collection'. However, you shouldn't make any assumption about the consumer being able to support object-oriented constructs such as inheritance, or base-classes for interoperable Web Services.
In our example, we will create a complex data type to represent a product object. This data contract will have five properties: ProductID
, ProductName
, QuantityPerUnit
, UnitPrice
, and Discontinued
. These will be used to communicate with client applications. For example, a supplier may call the Web Service to update the price of a particular product, or to mark a product for discontinuation.
It is preferable to put data contracts in separate files within a separate assembly, but to simplify our example, we will put the DataContract
within the same file as the service contract. So, we will modify the file IProductService.cs as follows:
bool boolValue = true;
string stringValue = "Hello ";
[DataMember]
public int ProductID { get; set; }
- Change the
DataContract
name from CompositeType
to Product
. - Remove the following two lines of code:
- Delete the old
BoolValue
, and StringValue
DataMember
properties. Then, for each product property, add a DataMember
property. For example, for ProductID
, we will have this DataMember
property:
The data contract part of the finished service contract file IProductService.cs should now look like this:
[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; }
}
The final content of the file IProductService.cs should be like this now:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCFandEFService
{
[ServiceContract]
public interface IProductService
{
[OperationContract]
Product GetProduct(int id);
}
[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; }
}
}
Implementing the service interface
To implement the service interface that we defined in the previous section, open the Service1.cs file and do the following:
public class ProductService : IProductService
public Product GetProduct(int id)
{
Product product = new Product();
product.ProductID = id;
product.ProductName = "fake product name";
product.UnitPrice = (decimal)10.0;
return product;
}
In this method, we created a fake product and returned it to the client. Later, we will remove the hard-coded product from this method and retrieve the real product info from the database using EF.
The contents of the ProductService.cs file should be like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCFandEFService
{
public class ProductService : IProductService
{
public Product GetProduct(int id)
{
Product product = new Product();
product.ProductID = id;
product.ProductName = "fake product name";
product.UnitPrice = (decimal)10.0;
return product;
}
}
}
- Change the class name from
Service1
to ProductService
. Make it inherit from the IProductService
interface, instead of IService1
. The class definition line should be like this: - Delete the
GetData
and GetDataUsingDataContract
methods. - Add the following method, to get a product:
- Change the file's name from Service1.cs to ProductService.cs.
Modifying the app.config file
Because we have changed the service name, we have to make the appropriate changes to the configuration file. Note: when you rename the service, if you have used the refactor feature of Visual Studio, some of the following tasks may have been done by Visual Studio.
Follow these steps to change the configuration file:
- Open the app.config file from Solution Explorer.
- Change all instances of
Service1
to ProductService
.
The content of the app.config file should now look like this:
="1.0"="utf-8"
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<system.serviceModel>
<services>
<service name="WCFandEFService.ProductService">
<host>
<baseAddresses>
<add baseAddress = "http://localhost:8732/
Design_Time_Addresses/WCFandEFService/ProductService/" />
</baseAddresses>
</host>
<endpoint address ="" binding="wsHttpBinding"
contract = "WCFandEFService.IProductService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding"
contract = "IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Testing the service using the WCF Test Client
Because we are using the WCF Service Library template in this example, we are now ready to test this Web Service. As we pointed out when creating this project, this service will be hosted in the Visual Studio 2010 WCF Service Host environment.
To start the service, press F5 or Ctrl+F5. WcfSvcHost will be started and the WCF Test Client is also started. This is a Visual Studio 2010 built-in test client for WCF Service Library projects.
Note: in order to run the WCF Test Client, you have to log in to your machine as a local administrator. You also have to start Visual Studio as an administrator if you have changed the port from the default value 8732 to some other port, like 8080. Otherwise, you may get an "Access is denied" error.
Now, from this WCF Test Client, we can test our simple GetProduct
operation.
- In the left panel of the client, double-click on the
GetProduct
operation; the GetProduct
Request will be shown on the right-side panel. - In this Request panel, specify an integer for the product ID, and click the Invoke button to let the client call the service. You may get a dialog box to warn you about the security of sending information over the network. Click the OK button to acknowledge this warning (you can check the 'In the future, do not show this message' option, so that it won't be displayed again).
Now, the message Invoking Service... will be displayed in the status bar, as the client is trying to connect to the server. It may take a while for this initial connection to be made, as several things need to be done in the background. Once the connection has been established, a channel will be created and the client will call the service to perform the requested operation. Once the operation has completed on the server side, the response package will be sent back to the client, and the WCF Test Client will display this response in the right bottom panel.
If you have started the test client in debugging mode (by pressing F5), you can set a breakpoint at a line inside the GetProduct
method in the ProductService.cs file, and when the Invoke button is clicked, the breakpoint will be hit so that you can debug the service as you usually do with any other .NET application.
Note that the response is always the same, no matter what product ID you use to retrieve the product (except the product ID field). Specifically, the product name is hard-coded, as shown in the diagram. Moreover, from the client response panel, we can see that several properties of the Product
object have been assigned default values.
Also, because the product ID is an integer value from the WCF Test Client, you can only enter an integer for it. If a non-integer value is entered, when you click the Invoke button, you will get an error message box to warn you that you have entered a value with the wrong type.
The Request/Response packages are displayed in grids by default, but you have the option of displaying them in XML format. Just select the XML tab from the bottom of the right-hand side panel, and you will see the XML formatted Request/Response packages. From these XML strings, you will discover that they are SOAP messages.
Besides testing operations, you can also look at the configuration settings of the Web Service. Just double-click on Config File from the left-side panel and the configuration file will be displayed in the right-side panel. This will show you the bindings for the service, the addresses of the service, and the contract for the service.
Note: what you see here for the configuration file is not an exact image of the actual configuration file. It hides some information, such as debugging mode and service behavior, and includes some additional information on reliable sessions and compression mode.
If you are satisfied with the test results, just close the WCF Test Client, and you will go back to the Visual Studio IDE. Note that as soon as you close the client, the WCF Service Host is stopped. This is different from hosting a service inside the ASP.NET Development Server, where after you close the client, the ASP.NET Development Server still stays active.
Applying LINQ to Entities to the WCF Service
In previous sections, we have created a simple WCF Service to get production details for an input product ID. The production details are hard coded in the service implementation.
In the following sections, we will apply LINQ to Entities to the WCF Service. We will connect to a database through Entity Framework, and retrieve the real product information from the database.
Preparing the database
In this article, we will use the Microsoft sample database, Northwind, as the backend database. This database is not installed by default in SQL Server 2005 or SQL Server 2008, so first, we need to install it to our database server.
- Download the database package. Just search for "Northwind Sample Databases download" on the Internet, or go to this page: http://www.microsoft.com/downloads/details.aspx?FamilyId=06616212-0356-46A0-8DA2-EEBC53A68034&displaylang=en and download the file SQL2000SampleDb.msi. Note: this sample database was designed for SQL Server 2000, but it can also be used in SQL Server 2005 and SQL Server 2008.
- Install (extract) it to: C:\SQL Server 2000 Sample Databases.
- Change the security of both Northwnd.mdf and Northwnd.ldf to be read/write-able to your SQL Server service account user (or just give Everyone full access).
- Open SQL Server 2005/2008 Management Studio.
- Connect to your database engine.
- Right click on the Databases node, and select Attach... from the context menu, as shown in the SQL Server Management Studio diagram below:
- In the pop-up Attach Databases dialog box, click Add, browse to the file C:\SQL Server 2000 Sample Databases\NORTHWND.MDF, click OK, and you now have the Northwind database attached to your SQL Server 2005 or 2008 engine.
Modeling the Northwind database
Now we have the database ready, we can model this database using Entity Framework. After we have the database modeled, we will use LINQ to Entities to retrieve the real product information from the database.
You can follow these steps to add an Entity data model to the project.
This will generate a file called Northwind.designer.cs which contains the object context for the Northwind database. This file also contains the Product
entity class.
- In Solution Explorer, right-click on the project item WCFandEFService, select menu option Add | New Item..., and then choose Visual C# Items | ADO.NET Entity Data Model as the Template, and enter Northwind.edmx as the name.
- Clicking the Add button will bring you to the Entity Data Model Wizard. In this wizard, select "Generate from database", click the Next button, and the connection dialog window will pop up. Enter your database server name, like localhost, specify the logon details, and choose Northwind as the database, then click the OK button to close this window.
- From the Entity Data Model Wizard, click button Next to go to the "Choose Your Database Objects" screen. Select the Products table, and click the Finish button to close the wizard.
Renaming the EF Product class
If you compile the project now, you will see an error "Missing partial modifier on declaration of type 'WCFandEFService.Product'; another partial declaration of this type exists". This is because earlier in this article, we have created a Product
class for the service data contract, but now another Product
class was created by the Entity Framework. Both are within the same namespace, and one is a partial class, while another is not.
There are two ways to solve this problem. First, we can remove our own Product
class definition, and expose the EF Product
class as the WCF service data contract class. In this way, we can work on only one Product
entity class through the whole project, and avoid some duplication of entity definitions. However, it is not a best practice to expose an EF entity class outside of a WCF Service. You can just search for "expose EF entities as data contracts" on Google and get an idea of what people are talking about this.
Another way is to separate our own Product
class and the EF Product
class. Our own Product
class will serve as the WCF data contract class, and the EF Product
class will serve as the ORM data entity class. Now we have a clear separation of concerns, and the outside world will never know what kind of ORM we are using underneath. I like this idea, so in this article, we will go this way.
However, keeping our own Product
class and the EF Product
class inside the same project causes a problem, as we have noted in the beginning of this session. To solve this problem, we have a few options. First, we can add a data access layer to the WCF Service solution and put the EF classes in the data access layer. Actually, this is the preferred way to develop an enterprise WCF Service, just as I discussed in my WCF and LINQ to Entities book (you can find more information about this book at the end of this article). But in this article, we will have only one layer for the WCF Service to keep it easy and simple, so we won't take this approach. A second way is to put the EF entity classes in a separate namespace, so two Product
classes can co-exist without a problem. But having two Product
classes in the same project is confusing, even if they are in two different namespaces. So we will go the third way, that is, rename the EF Product
entity class to ProductEntity
.
To rename the EF Product
class, open the file Northwind.edmx, click on the class name, and rename it. The renamed model should be like this:
After you rename the EF Product
class, if you re-build the service, you should see no errors.
Retrieving product details from the database using EF
Now, inside the GetProduct
method, we can use the following statements to get the product details from the database using LINQ to Entities:
NorthwindEntities context = new NorthwindEntities();
var productEntity = (from p
in context.ProductEntities
where p.ProductID == id
select p).FirstOrDefault();
Here, we first created a ObjectContext
object for the EF model, then we used the LINQ to Entities statement to get the product entities from the database for the given ID. Because the return result of the LINQ to Entities statement is IQueryable
, we called the FirstOrDefault
method to get only the first record. This method also gives a null result if the query doesn't yield any result from the database.
Translating the ProductEntity object to a Product object
However, we cannot return this product object back to the caller, because this product is of type ProductEntity
, which is not the type that the caller is expecting. The caller is expecting a return value of type Product
, which is a data contract defined within the service interface. We need to translate this ProductEntity
object to a Product
object. To do this, we add the following new method to the ProductService
class:
private Product TranslateProductEntityToProduct(
ProductEntity productEntity)
{
Product product = new Product();
product.ProductID = productEntity.ProductID;
product.ProductName = productEntity.ProductName;
product.QuantityPerUnit = productEntity.QuantityPerUnit;
product.UnitPrice = (decimal)productEntity.UnitPrice;
product.Discontinued = productEntity.Discontinued;
return product;
}
Inside this translation method, we copy all of the properties from the ProductEntity
object to the service contract data object, but not the last three properties - UnitsInStock
, UnitsOnOrder
, and ReorderLevel
. We assume these three properties are used only inside the service implementations. Outside callers cannot see them at all.
The GetProduct
method should now look like this:
public Product GetProduct(int id)
{
NorthwindEntities context = new NorthwindEntities();
var productEntity = (from p
in context.ProductEntities
where p.ProductID == id
select p).FirstOrDefault();
if (productEntity != null)
return TranslateProductEntityToProduct(productEntity);
else
throw new Exception("Invalid product id");
}
Note: inside the GetProduct
method, after we retrieve the product details from the database using EF, we first test the object to see if it is null
. If so, we know the input ID is not a valid product ID in our database. We then throw an exception to tell the client. However, in a real WCF Service, you shouldn't throw any exceptions to the client; instead, you should define and throw a Fault to the client. Again, more information about Faults can be found in my WCF and LINQ to Entities book.
The final contents of the ProductService.cs file should be like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCFandEFService
{
public class ProductService : IProductService
{
public Product GetProduct(int id)
{
NorthwindEntities context = new NorthwindEntities();
var productEntity = (from p
in context.ProductEntities
where p.ProductID == id
select p).FirstOrDefault();
if (productEntity != null)
return TranslateProductEntityToProduct(productEntity);
else
throw new Exception("Invalid product id");
}
private Product TranslateProductEntityToProduct(
ProductEntity productEntity)
{
Product product = new Product();
product.ProductID = productEntity.ProductID;
product.ProductName = productEntity.ProductName;
product.QuantityPerUnit = productEntity.QuantityPerUnit;
product.UnitPrice = (decimal)productEntity.UnitPrice;
product.Discontinued = productEntity.Discontinued;
return product;
}
}
}
Testing the WCF Service with EF
We can now compile and test the new service with EF support. We will still use the WCF Test Client to simplify the process.
- Start the WCF Service Host application and WCF Service Test Client, by pressing F5 or Ctrl+F5.
- In the WCF Service Test Client, double-click on the
GetProduct
operation, to bring up the GetProduct test screen. - Enter a value of 56 for the ID field, and then click the Invoke button.
You will see that this time the product is returned from the real database, instead of hard coded. Also, note that the UnitsOnOrder
property is not displayed as it is not part of the service contract data type.
Testing exceptions
Now, enter an invalid product ID, like 0, and you will get this error message:
This is because inside the WCF Service, we couldn't find the product in the Northwind database with ID 0, so we threw an exception for it. Because we haven't turned on the includeExceptionDetailInFaults
flag in the app.config, the client application couldn't get the exception details.
To display the exception details, you can open the app.config file, change the value of includeExceptionDetailInFaults
from False to True, and try again. This time, you will get this error message:
This time, the exception detail is returned back to the client application, as you can see; however, this is not recommended. The reason is, not all clients can understand a .NET exception. It will be treated as an unknown fault by certain types of clients. To make it understandable by all kinds of clients, we need to define and throw a fault when we can't get the product from the database for the given ID.
Another drawback of throwing a .NET exception instead of a fault is, once an exception is thrown, the communication channel is no longer valid (the channel is in faulted state). It can't be used for subsequent service calls.
To test this, first enter an invalid product ID, like 0, to trigger the exception, then enter another valid product ID, like 56, and you will get an error message like this:
From the error message, we know the communication channel is now faulted. You have to restart the WCF Test Client to create a new communication channel. Alternatively, you can force the test client to use a new proxy for the service call by checking "Start a new proxy", but this is not a recommended way in production for performance reasons.
Summary
In this article, we have created a simple WCF Service that retrieves product details from a sample database using Entity Framework. The key points in this article include:
- WCF Services should have explicit boundaries
- The WCF Service Library template can be used to create WCF Services that will be hosted by the WCF Service Host, and these can be tested using the WCF Service Test Client
- Entity Framework can be used to model backend databases
- Entity Framework entities represent the internal data of the service and they should not be exposed to clients
- LINQ to Entities can be used to query databases using EF
- Fault contracts should be used when there are exceptions
Note: this article is based on chapter 4 and chapter 9 of my book "WCF 4.0 Multi-tier Services Development with LINQ to Entities" (ISBN 1849681147). This book is a hands-on guide to learn how to build SOA applications on the Microsoft platform using WCF and LINQ to Entities. It is updated for VS2010 from my previous book: WCF Multi-tier Services Development with LINQ.
With this book, you can learn how to master WCF and LINQ to Entities concepts by completing practical examples and applying them to your real-world assignments. This is the first and only book to combine WCF and LINQ to Entities in a multi-tier real-world WCF Service. It is ideal for beginners who want to learn how to build scalable, powerful, easy-to-maintain WCF Services. This book is rich with example code, clear explanations, interesting examples, and practical advice. It is a truly hands-on book for C++ and C# developers.
You don't need to have any experience of WCF or LINQ to Entities to read this book. Detailed instructions and precise screenshots will guide you through the whole process of exploring the new worlds of WCF and LINQ to Entities. This book is distinguished from other WCF and LINQ to Entities books by that, this book focuses 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 Entities. Once you have finished this book, you will be proud that you have been working with WCF and LINQ to Entities in the most straightforward way.
You can buy this book from Amazon, or from the publisher's website at https://www.packtpub.com/wcf-4-0-multi-tier-services-development-with-linq-to-entities/book.
You can also read a sample chapter from this book on the publisher's website at http://www.packtpub.com/article/implementing-wcf-service-real-world.
Update
The latest version of my book (for Visual Studio 2013 / Windows 7 and 8.1) is just published. You can get it directly from the publisher's website at this address
https://www.packtpub.com/application-development/wcf-multi-layer-services-development-entity-framework-4th-edition
or from Amazon at this address:
http://www.amazon.com/Multi-Layer-Services-Development-Entity-Framework/dp/1784391042