Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Improvements and Enhancements of a Data Access Web Service Using WCF and Entity Framework

5.00/5 (15 votes)
27 Mar 2014CPOL15 min read 47.6K   1.3K  
An article with sample application showing how and what we can do to improve and enhance a WCF and EF data access web service
Many areas of a WCF and EF data access web service could be improved or enhanced to make the application more robust and valuable. In this article, you will see a sample application showing how and what can be done to achieve this.

Introduction

SOAP based web services are a reliable, scalable, and secure form of wrapping the data access layer (DAL) and business logic layer (BLL) in a multi-tiered business data applications, particularly in an enterprise environment. Negative arguments for this data access architecture are mostly focused on performance and complexity. Those disadvantages have decreased with the advances of WCF and Entity Framework. On the other hand, improvements and enhancements of structures and code can further increase the service performance and maintainability, and reduce the complexity. This article will describe how to set up the sample application first and then the details on the below highlighted topics:

Setting Up Sample Application

The StoreDB sample database can be created in the SQL Server 2012 Express or LocalDB, and then populate the tables by executing the included script file, StoreDB.sql, using the Visual Studio or the SQL Server Management Studio (SSMS). You can download the SQL Server Express with LocalDB and the SSMS here.

The WCF web service is set to the basicHttpBinding mode and hosted on an ASP.NET website. An EF data model with the database-first approach is set in a class library project. You can directly compile the downloaded source easily with the Visual Studio 2010 with the .NET Framework 4.0 or Visual Studio 2012/2013 with the .NET Framework 4.5. All referenced assemblies other than the .NET Framework are included in the Referenced.Lib folder under the sample application source root. Projects in the Visual Studio solution are shown in the below screenshot:

Image 1

You need to change the data source value "(localdb)\v11.0" for the connectionString in the SM.Store.Service.web.config file to your SQL Server instance if you do not use the SQL Server 2012 LocalDB.

XML
<add name="StoreDataEntities" 
connectionString="metadata=res://*/StoreDataModel.csdl|res://*/StoreDataModel.ssdl|
res://*/StoreDataModel.msl;provider=System.Data.SqlClient;
provider connection string=&quot;data source=(localdb)\v11.0;
initial catalog=StoreDB;integrated security=True;multipleactiveresultsets=True;
App=EntityFramework&quot;" providerName="System.Data.EntityClient" />

Moving Entity Classes to a Standalone Assembly

New models created using the EF designer generates a derived DbContext and POCO type entity classes that are in the same DAL project. For better structures and component usability, we can move the model template file and all entity class files to a separate project, or assembly when compiled. To automatically refresh entity class contents by using Run Custom Tool context-menu command after any update with the model, we need to manually edit some code lines in both data context and data model template files to make the synchronization work. Here are examples as in the sample application.

In the StoreDataModel.Context.tt file from the SM.Store.Service.DAL project:

Image 2

In the StoreDataModel.tt file from the SM.Store.Service.Entities project.

Image 3

Better Structured Repositories and Unit of Work in DAL

In an optimized implementation, the Unit of Work (UOW) object should initiate the data context object and be passed to repositories to coordinate the work of multiple repositories that share the single database context class. Since the UOW is on the top of the database context, the database connection string can be passed from the caller to the UOW class instead of directly injecting to the data context class.

C#
public class StoreDataModelUnitOfWork : IUnitOfWork
{
    private StoreDataEntities context;
    public StoreDataModelUnitOfWork(string connectionString)
    {
        this.context = new StoreDataEntities(connectionString);
    }
    //Other code...
}

To make this connection string injection complete, we need to edit the context template StoreDataModel.Context.tt file for the data context class constructor. Each time when saving the file or running the custom tool for the template, the connection string injection code will be kept there.

Image 4

The optimized structures of repositories should include a generic base repository class for the basic CRUD operations and other individual repositories inheriting the generic repository for the particular entity types. If operations for an entity are no more than those already in the generic repository, the derived repository for this entity may not be needed. The generic repository can directly be called from the BLL (see the example in the BLL section below).

In the generic repository class:

C#
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
    public IUnitOfWork UnitOfWork { get; set; }
    public GenericRepository(IUnitOfWork unitOfWork)
    {
        this.UnitOfWork = unitOfWork;
    }
    //Other code...
}

In the individually typed repository class for the Product entity:

C#
public class ProductRepository : 
GenericRepository<Entities.Product>, IProductRepository
{
    public ProductRepository(IUnitOfWork unitOfWork)
        : base(unitOfWork)
    { 
    }
    //Other code...
}

Calling Multiple Repositories from Business Logic Layer (BLL)

Although the primary purpose for the BLL is to enforce business rules, we can also use this layer to manage operations using multiple repositories, for example, saving data to multiple entities in cascading and transaction styles. This will keep the DAL in a clear single entity related structure. In a BLL class, the instances of multiple repositories are passed into the constructor for working with the dependency injection pattern (described in the below section).

C#
public class CategoryBS : ICategoryBS
{
    private IGenericRepository<Entities.Category> _categoryRepository;
    private IProductRepository _productRepository;
    public CategoryBS(IGenericRepository<Entities.Category> 
    cateoryRepository, IProductRepository productRepository)
    {
        this._categoryRepository = cateoryRepository;
        this._productRepository = productRepository;
    }
    //Other code...
}

Configuring Dependency Injection (DI) with Unity

Using Microsoft Unity libraries, we can easily implement the dependency injection pattern for decoupling components of the service yet maintaining maximum co-adherence in these components. One of the key tasks to use the DI with Unity is to configure the container either at design time or runtime. There is no performance difference between the two configuring approaches, but the design time configuration is more flexible and maintainable. If an application has similar components targeting for different data sources or service layers, using the design time configuration can switch between desired assemblies from the XML config file without recompiling and deploying the application projects.

Since the database connection string is passed as the parameter in the constructor of the UOW object, code in runtime configuration can directly retrieve the connection string value from the normal place in the web.config file. However, the design time configuration cannot get the connection string using the same way. The alternative is to use a placeholder for the Value attribute in the Unity.config file. In the runtime, the code can then get the real connection string value from the web.config and replace the placeholder in the constructor of the SM.Store.Service.DAL.StoreDataModelUnitOfWork class.

The sample application demonstrates the use of both configuration options in the code shown below.

The runtime configuration code in the DIRegister.cs file:

C#
public static void RegisterTypes(UnityContainer Container)
{
    //Register UnitOfWork with db ConnectionString injection
    string connString = ConfigurationManager.ConnectionStrings
                        ["StoreDataEntities"].ConnectionString;
    Container.RegisterType<DAL.IUnitOfWork, 
    DAL.StoreDataModelUnitOfWork>(new PerResolveLifetimeManager(), 
                                  new InjectionConstructor(connString));
    
    //Register BLL and DAL objects
    Container.RegisterType<BLL.IProductBS, BLL.ProductBS>();
    Container.RegisterType<DAL.IProductRepository, DAL.ProductRepository>();
    Container.RegisterType<BLL.ICategoryBS, BLL.CategoryBS>();
    Container.RegisterType<DAL.IGenericRepository<Entities.Category>, 
    DAL.GenericRepository<Entities.Category>>();
}

The method is called from the Global.asax.cs when the service starts.

C#
protected void Application_Start(object sender, EventArgs e)
{
    DIRegister.RegisterTypes(DIFactoryForRuntime.Container);
}

The design time configuration code in the Unity.config file:

XML
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
  <assembly name="SM.Store.Service.DAL"/>
  <assembly name="SM.Store.Service.BLL"/>
  <alias alias="Category" 
  type="SM.Store.Service.Entities.Category, SM.Store.Service.Entities" />
  <container>
    <register type="SM.Store.Service.DAL.IUnitOfWork" 
    mapTo="SM.Store.Service.DAL.StoreDataModelUnitOfWork">
      <lifetime type="singleton" />
      <constructor>
        <!--Set placeholder for value attribute and replace it at runtime-->
        <param name="connectionString" value="{connectionString}" />
      </constructor>
    </register>   
    <register type="SM.Store.Service.BLL.IProductBS" 
    mapTo="SM.Store.Service.BLL.ProductBS"/>
    <register type="SM.Store.Service.DAL.IProductRepository" 
    mapTo="SM.Store.Service.DAL.ProductRepository"/>   
    <register type="SM.Store.Service.BLL.ICategoryBS" 
    mapTo="SM.Store.Service.BLL.CategoryBS"/>
    <register type="SM.Store.Service.DAL.IGenericRepository[Category]" 
    mapTo="SM.Store.Service.DAL.GenericRepository[Category]"/>   
  </container> 
</unity>

The container is registered from the web.config file when the service starts.

XML
<configuration>
  <configSections>
    <section name="unity" 
    type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, 
    Microsoft.Practices.Unity.Configuration"/>
  </configSections>
  <unity configSource="Unity.config"/>
   . . .
</configuration>

The code for replacing the placeholder with the real connection string in the SM.Store.Service.DAL.StoreDataModelUnitOfWork class:

C#
public StoreDataModelUnitOfWork(string connectionString)
{
    //Used for design time DI configuration
    if (connectionString == "{connectionString}")
        connectionString = ConfigurationManager.ConnectionStrings
                           ["StoreDataEntities"].ConnectionString;
    
    this.context = new StoreDataEntities(connectionString);
}

For illustrations in the sample application, some service methods call the Unity container for component instances using the runtime configurations whereas others using the design time configurations. In the real world, of course, you may only use one kind of configurations within the same application.

Disabling Lazy Loading

By default, the Lazy Loading for the data context is enabled. This may not be the optimal setting for the data access tier wrapped in the web service since there are multiple hits to the database server during the entity-contract type conversions before the serializations for contract objects. It could slow the type conversion processes. We need to disable the lazy loading for the data context by right-clicking any blank area of the EF Designer, select the Properties, and change the Lazy Loading Enabled to False, and then save the EDMX file. We can then use following approaches in the code.

  1. Eager loading that includes the child entities if desired. The GetProductList() and GetProductByCategoryId() methods in the SM.Store.Service.DAL.ProductRepository class use the eager loading as examples.
  2. Joining linked entities in the LINQ to Entity query and returning data with a custom type. The GetFullProducts() method in the SM.Store.Service.DAL.ProductRepository class shows this approach.
  3. Calling stored procedures and returning data with an EF complex type. The sample application includes a more advanced EF stored procedure mappings for returning multiple result sets.

These alternative approaches will also be discussed with related topics in the following sections.

Getting Paginated and Sorted Data Sets

Implementing the EF server-side paging and sorting needs constructions of appropriate LINQ queries. The sorting is a must when doing the paging.

The most concerned issue on the sorting is how to return records sorted by a child entity property. This can be done by searching expression tree nodes to find the property of the child entity and then build the OrderBy statement of the query. Below is the method in the SM.Store.Service.Common.GenericSorterPager class for building this part of the query.

C#
public static IOrderedQueryable<T> AsSortedQueryable<T, TSortByType>
       (IQueryable<T> source, string sortBy, SortDirection sortDirection)
{
    //Initiate expression tree
    var param = Expression.Parameter(typeof(T), "item");
    Expression parent = param;

    //Separate child entity name and property
    string[] props = sortBy.Split(".".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

    //Reset expression tree if there is entity name prefix
    foreach (string prop in props)
    {
        parent = Expression.Property(parent, prop);
    }

    //Build the sort expression
    var sortExpression = Expression.Lambda<Func<T, TSortByType>>(parent, param);

    //Handle ascending and descending
    switch (sortDirection)
    {
        case SortDirection.Descending:
            return source.OrderByDescending<T, TSortByType>(sortExpression);
        default:
            return source.OrderBy<T, TSortByType>(sortExpression);
    }
}

The most concerned issue on the paging is how to consolidate calls for data sets and a total count value. The EF paging usually needs two calls for the requested data, one for data sets and the other for a total count value which is needed for populating the grid or table display structures in the client UI application. Two calls for returning paged and count data may have an impact on the performance for large databases. This issue can be resolved using the code in the GetSortedPagedList() method in the SM.Store.Service.Common.GenericSorterPager class.

C#
//Build one-call query from created regular query
var pagedGroup = from sel in sortedPagedQuery
                 select new PagedResultSet<T>()
                 {
                     PagedData = sel,
                     TotalCount = source.Count()
                 };
//Get the complete resultset from db.
List<PagedResultSet<T>> pagedResultSet = pagedGroup.AsParallel().ToList();
//Get data and total count from the resultset
IEnumerable<T> pagedList = new List<T>();

if (pagedResultSet.Count() > 0)
{
    totalCount = pagedResultSet.First().TotalCount;
    pagedList = pagedResultSet.Select(s => s.PagedData);
}
//Remove the wrapper reference
pagedResultSet = null;

return pagedList.ToList();

The SQL query generated from the EF behind the scene by checking the <code>pagedGroup variable value is shown below. It actually uses the Cross Join to get the total count value and attach it to each row. Although this is a little redundant, it’s not the issue since there is only limited number of rows returned in the paged data sets (10 rows in our example).

SQL
SELECT TOP (10) 
[Join3].[ProductID] AS [ProductID], 
[Join3].[C1] AS [C1], 
[Join3].[ProductName] AS [ProductName], 
[Join3].[CategoryID] AS [CategoryID], 
[Join3].[UnitPrice] AS [UnitPrice], 
[Join3].[StatusCode] AS [StatusCode], 
[Join3].[Description] AS [Description], 
[Join3].[A1] AS [C2]
FROM ( SELECT [Distinct1].[ProductID] AS [ProductID], _
[Distinct1].[ProductName] AS [ProductName], _
[Distinct1].[CategoryID] AS [CategoryID], [Distinct1].[UnitPrice] AS [UnitPrice], _
[Distinct1].[StatusCode] AS [StatusCode], [Distinct1].[Description] AS [Description], _
[Distinct1].[C1] AS [C1], [GroupBy1].[A1] AS [A1], row_number() _
OVER (ORDER BY [Distinct1].[ProductName] ASC) AS [row_number]
    FROM   (SELECT DISTINCT 
        [Extent1].[ProductID] AS [ProductID], 
        [Extent1].[ProductName] AS [ProductName], 
        [Extent1].[CategoryID] AS [CategoryID], 
        [Extent1].[UnitPrice] AS [UnitPrice], 
        [Extent1].[StatusCode] AS [StatusCode], 
        [Extent2].[Description] AS [Description], 
        1 AS [C1]
        FROM  [dbo].[Product] AS [Extent1]
        INNER JOIN [dbo].[ProductStatusType] AS [Extent2] _
        ON [Extent1].[StatusCode] = [Extent2].[StatusCode] ) AS [Distinct1]
    CROSS JOIN  (SELECT 
        COUNT(1) AS [A1]
        FROM ( SELECT DISTINCT 
            [Extent3].[ProductID] AS [ProductID], 
            [Extent3].[ProductName] AS [ProductName], 
            [Extent3].[CategoryID] AS [CategoryID], 
            [Extent3].[UnitPrice] AS [UnitPrice], 
            [Extent3].[StatusCode] AS [StatusCode], 
            [Extent4].[Description] AS [Description]
            FROM  [dbo].[Product] AS [Extent3]
            INNER JOIN [dbo].[ProductStatusType] AS [Extent4] _
            ON [Extent3].[StatusCode] = [Extent4].[StatusCode]
        )  AS [Distinct2] ) AS [GroupBy1]
)  AS [Join3]
WHERE [Join3].[row_number] > 0
ORDER BY [Join3].[ProductName] ASC

Note that the pagedGroup query doesn’t return the data from any child entity when using the eager loading for the base source sortedPagedQuery passed in the generic IQueriable<T> type. It works for the lazy loading, but additional calls are also needed to get the data from child entities. Since we disable the lazy loading for the data context, we need to make a separate call to obtain the total count value if an eager loading is used. The code for the GetSortedPagedList() method handles calls from queries with both eager loading and no loading of child entities.

C#
public static IList<T> GetSortedPagedList<T>(IQueryable<T> source, 
       PaginationRequest paging, out int totalCount, ChildLoad childLoad = ChildLoad.None)
{
    . . .
    //Call db for total count when child entity mode is eager loading
    if (childLoad == ChildLoad.Include)
    {
        totalCount = source.Count();
    }
    //Call to build sorted paged query
    IQueryable<T> sortedPagedQuery = GetSortedPagedQuerable<T>(source, paging);
    //Call db one time to get data rows and count together
    if (childLoad == ChildLoad.None)
    {
        //Build one-call query from created regular query
        [Code of one-call query shown before]
    }
    //Call db second time to get paged data rows when using eager loading
    else
    {
        return sortedPagedQuery.ToList();
    }
}

Object Type Conversion and EF Query Scenarios

The SOAP based web services communicate with the service consumers through serialization and deserialization processes for contract objects. However, the entity type objects are used in all BLL and DAL. The entity-contract type conversions, and vice versa, are conducted during each request and response between services and clients. These conversions also add overheads to the service data operations, especially in these situations:

  • Retrieving a data set containing child entities when the lazy loading is enabled. Calls to the database for child entity data occur during the mapping processes in this case, which should be avoided in a data access web service.
  • Using eager loading for a data set including child entities when the large number of rows is returned and multiple, or even multiple levels of, child entities are included. Converters need extra work to search and map those child entity properties.

Options on mapping properties between object types can be auto, semi-auto, and manual mappings. We can use the AutoMapper library to do the auto and the semi-auto mappings in a generic style whereas the manual conversion needs one-to-one property value assignment between source and destination objects. The data type conversion approaches, although performed in the service endpoint methods, are also related to EF queries used in the DAL. Generally, using auto mapping eases the development and maintenance tasks but reduces the performance. Here are several mapping scenarios shown in the sample application.

C#
//Obtain data as an entity object
Entities.Product ent = ProductBs.GetProductById(request.Id);

//Use simple auto mapping
IBaseConverter<Entities.Product, DataContracts.Product> 
convtResult = new AutoMapConverter<Entities.Product, DataContracts.Product>();

//Convert entity to contract object
resp.Product = convtResult.ConvertObject(ent);

This scenario is also used for all cases converting data contract objects back to entity objects for Insert and Update operations which are all single entity based.

C#
//Use custom configured auto mapping
ProductAutoMapConverter convtResult = new ProductAutoMapConverter();
List<DataContracts.ProductCM> dcList = convtResult.ConvertObjectCollection(productList);

The code in the ProductAutoMapConverter class manually maps the child entity property ProductStatusType.Description to the StatusDescription property of the contract object so that the data value of StatusDescription field can be shown in the returned data list. Full auto mapping processes are used for all other properties.

C#
public class ProductAutoMapConverter : AutoMapConverter<Entities.Product, 
       DataContracts.ProductCM>
{
    public ProductAutoMapConverter()
    {
        //Mapping child entity property to contract property
        AutoMapper.Mapper.CreateMap<Entities.Product, DataContracts.ProductCM>()
         .ForMember(dest => dest.StatusDescription, _
         opt => opt.MapFrom(src => src.ProductStatusType.Description)); 
    }
}
C#
public override DataContracts.ProductCM ConvertObject(Entities.Product ent)
{
    //Manual one-to-one mapping
    return new DataContracts.ProductCM()
    {
        ProductID = ent.ProductID,
        ProductName = ent.ProductName,
        CategoryID = ent.CategoryID != null ? ent.CategoryID.Value : 0,
        UnitPrice = ent.UnitPrice,
        StatusCode = ent.StatusCode,
        StatusDescription = ent.ProductStatusType != null ? 
                            ent.ProductStatusType.Description : string.Empty
    };
}
C#
//Query to join parent and child entities and return custom object type
IQueryable<Entities.ProductCM> query = this.UnitOfWork.Context.Products
    .Join(this.UnitOfWork.Context.ProductStatusTypes,
     p => p.StatusCode, s => s.StatusCode,
    (p, s) => new Entities.ProductCM
    {
        ProductID = p.ProductID,
        ProductName = p.ProductName,
        CategoryID = p.CategoryID,
        UnitPrice = p.UnitPrice,
        StatusCode = p.StatusCode,
        StatusDescription = s.Description
    });

The EF query takes care of joining the underlying parent and child tables in the database and returns data with all fields from parent and child tables as properties of the custom type. We can then just use the simple auto mapping for the ProductCM to its contract type counterpart.

C#
//Use simple auto mapping
IBaseConverter<Entities.ProductCM, DataContracts.ProductCM> convtResult = 
               new AutoMapConverter<Entities.ProductCM, DataContracts.ProductCM>();

Based on my experiences, this is the best scenario for EF queries and data type conversions in a data access WCF web service since the DAL only returns data in a collection with just a simple object type, not including any other child or linked object. We then avoid digging into the child entities to map members during the entity-contract type conversions. In this scenario, we can also ignore whether the lazy or eager loading is used or not.

  1. Using simple auto mapping for returned data containing no child entity. The GetProductById() method in the DAL makes a simple query call to get an entity data record without any property from child entities.
  2. Using the semi-auto or custom configured mapping for returned data including any property from child entities (eager loading in our case). This approach is demonstrated in the GetProductList() service method.
  3. Using pure manual mapping. The GetProductByCategoryId() service method uses the pure manual mapping approach. It calls the ProductManualConverter class which does the assignments for each property returned.
  4. Using simple auto mapping with optimized EF queries. This needs a custom POCO object similar to the ViewModel object that includes all needed properties from child entities. In our case, it’s the ProductCM class in the SM.Store.Service.Entities project. Inside the ProductReporsitory.GetFullProducts() method in the DAL, the EF query is defined as shown blow.

Returning Multiple Result Sets from Database Stored Procedures

The sample app shows how to use this feature with EF designer Function Import mappings. This part is only included in the source code for VS 2012/2013 since it is only supported by .NET Framework 4.5. The implementation details, particularly for manually editing function import mappings, are described in my previous article.

For the equivalent returning complex types in the entity side, the response data contract object and collections are added into the web service project.

C#
//Contract object to hold collections for multiple result sets
[DataContract(Namespace = Constants.NAMESPACE)]
public class CategoriesProductsResponse
{
    [DataMember(IsRequired = false, Order = 0)]
    public CategoriesCM Categories { get; set; }

    [DataMember(IsRequired = false, Order = 1)]
    public ProductsCM Products { get; set; }
}

//Collection for Category complex type
[CollectionDataContract(Namespace = Constants.NAMESPACE, 
Name = "CategoriesCM", ItemName = "CategoryCM")]
public class CategoriesCM : List<DataContracts.CategoryCM> { }

//Collection for Product complex type
[CollectionDataContract(Namespace = Constants.NAMESPACE, 
Name = "ProductsCM", ItemName = "ProductCM")]
public class ProductsCM : List<DataContracts.ProductCM> { }

The service will then return the multiple result sets after the type conversions. Since the complex types already include all wanted fields and no field mapping from any child data structure is needed, we can directly use the AutoMapper for simple mappings to achieve the optimal performance and maintainability.

C#
public DataContracts.CategoriesProductsResponse 
   GetCategoriesAndProducts(DataContracts.QueryByStringRequest request)
{
    DataContracts.CategoriesProductsResponse resp = 
                  new DataContracts.CategoriesProductsResponse();
    resp.Categories = new DataContracts.CategoriesCM();
    resp.Products = new DataContracts.ProductsCM();

    //Use design time configuration for Unity DI
    ICategoryBS categoryBS = DIFactoryForDesigntime.GetInstance<ICategoryBS>();

    //Get data by calling method in BLL.
    Entities.CategoriesProducts ent = categoryBS.GetCategoriesAndProducts();

    //Use simple auto mapping without custom configurations.
    IBaseConverter<Entities.Category_Result, DataContracts.CategoryCM> convtResult1 = 
         new AutoMapConverter<Entities.Category_Result, DataContracts.CategoryCM>();
    List<DataContracts.CategoryCM> dcList1 = 
             convtResult1.ConvertObjectCollection(ent.Categories);
    IBaseConverter<Entities.Product_Result, DataContracts.ProductCM> convtResult2 = 
         new AutoMapConverter<Entities.Product_Result, DataContracts.ProductCM>();
    List<DataContracts.ProductCM> dcList2 = convtResult2.ConvertObjectCollection(ent.Products);
    
    //Add multiple collections to resp object and return it.
    resp.Categories.AddRange(dcList1);
    resp.Products.AddRange(dcList2);
    return resp;
}

Running and Testing Service from Clients

In the local development environment, the sample web service runs from the development server with Visual Studio 2010 and IIS Express with Visual Studio 2012/2013. To test the service locally from any external client application, you firstly need to start the web service by running the browser pointing to a service endpoint URL. The easiest way to do this is to execute the context-menu command View in Browser on the endpoint file (*.svc) from the Visual Studio Solution Explorer. You can then test the service using the client application or tools, such as WcfTestClient.exe (in C:\Program Files (x86)\Microsoft Visual Studio [version_ number]\Common7\IDE) or SoapUI. If opening the WcfTestClient, adding the "http://localhost:23066/Services/ProductService.svc" endpoint URL into it, then selecting and invoking the GetProductByCategoryId() service method, the resulted screen should be like this:

Image 5

The sample application also includes a unit test project in the client side. It sets the service references items CategorySvcRef and ProductSvcRef as the client proxies pointing to the service endpoints. The project has many advantages as a test client for the service:

  • Automatic service starting. When setting the test project as the starting project and running it in the same Visual Studio solution, you don’t have to manually start the service from a browser before running the test since the service will automatically be running there.
  • Conducting manual or automated unit tests.
  • Used for easy debugging. From the code lines executed in the test class, we can step into the code of any project in the service solution.
  • Checking details of returned data. When placing a breakpoint on a line after returning the data, we can see all data details from the client side in the Tooltip, Autos, or Locals window.

Calling Service with Task-based Asynchronous Operation

The Task-based Asynchronous Pattern (TAP) is the recommended asynchronous design pattern for new development. With .NET Framework 4.5, the structure and syntax are simplified to implement the TAP. To call a service method asynchronously, we don’t have to do anything in the server side. All we need is to enable the Allow generation of asynchronous operations and Generate task-based operations on the Service Reference Settings dialog when adding new, or updating/configuring existing, service references for the client applications. The corresponding methods with suffix Async and returning awaitable Task type for the TAP operations are then automatically added into the client proxy.

The TestClientConsole project in the sample application contains code to call the GetFullProductsAsync() service method wrapped by the GetFullProductsTaskAsync() method with the async keyword which opens another thread to process the data retrieval asynchronously. The Console.Write() code line is physically located after calling the GetFullProductsTaskAsync() in the main thread but it continues to run and displays the text in the Console window before the data retrieval completes and displays.

C#
static void Main(string[] args)
{
    //Call async method
    GetFullProductsTaskAsync();

    //Previous call in another thread gets waiting and this line executed before it.
    Console.Write("This code line after async call in main thread 
                   \r\nbut was executed before returning async data\r\n");
    Console.ReadKey();
}
private async static void GetFullProductsTaskAsync()
{
    //Initiate client proxy object and construct input request
     . . .
    //Processing data retrieval in another thread
    Task<ProductListResponse> taskResp = client.GetFullProductsAsync(inputItem);
    var resp = await taskResp;

    //Display data
    Console.WriteLine("");
    Console.WriteLine("Products: (total {0}, listed {1})", 
                       resp.TotalCount.ToString(), resp.Products.Count.ToString());
    foreach (var item in resp.Products)
    {
        Console.WriteLine("");
        Console.WriteLine("Name: {0}", item.ProductName);
        Console.WriteLine("Price: ${0}", item.UnitPrice.ToString());
    }
}

Image 6

Summary

Many areas of a WCF and EF data access web service could be improved or enhanced to make the application more robust and valuable. I hope that the article is helpful to developers who are interested in, or working, on the data tier service applications.

History

  • 21st November, 2013: Initial version

License

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