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

WCF(Windows Communication Foundation)-Transaction Support

5.00/5 (2 votes)
11 Dec 2016CPOL3 min read 5K  
Transaction support in WCF

In this post, we will cover transaction support in WCF. Let’s see how we can implement the same, we will make sure that we will be wsHttpBinding as it supports Transaction.

Introduction

We will create a library service, where we will see the actual transaction feature working. We will use entity framework as our data access to interact with DB. You may think that entity framework already supports transaction as well as ADO.NET, but in case we want multiple methods, we want to expose to the client, and give control to client on how to call methods and in what sequence.

Let’s create the service.

  1. Let’s add an empty Solution and name it as “WCFTransactionService”, under empty solution, right click and add a new project, and name it as “WCFTransactionService”.

    service

  2. We will add a few methods and configure them to use transaction. After adding this project, we will get service1 already present in our project. Let’s remove all classes and interfaces from project.
  3. Add a new interface called “ILibraryService”, and add the following methods:
    1. AddBooks
    2. AddRemoveQuantity
    3. AddStudent
    4. GetBooks
    5. GetStudent
    6. ProvideBookToStudent
      C#
      using Library.Model;
      using System.Collections.Generic;
      using System.ServiceModel;
      using System.Threading.Tasks;
      
      namespace WCFTransactionService
      {
       [ServiceContract]
       public interface ILibraryService
       {
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.NotAllowed)]
       Task<List<Book>> GetBooks();
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.NotAllowed)]
       Task<List<Student>> GetStudent();
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.Mandatory)]
       Task<int> AddBooks(Book book);
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.Mandatory)]
       Task<int> AddStudent(Student student);
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.Mandatory)]
       Task<int> ProvideBookToStudent(StudentBookDetails details);
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.Mandatory)]
       Task<bool> AddRemoveQuantity(Book book);
       }
      }
  4. Now let’s add our models, add a new class project and name it as Library.Model. We will use the same class as datacontract and same class as our tables for Entity Framework. Never do that in real time project, have 2 different classes to avoid conflicts as we have requirement of some properties which are not actually required in table, and running migration for such changes is overhead. We will have 3 models.
    1. Book
    2. Student
    3. StudentBookDetails
      C#
      using System.Runtime.Serialization;
      
      namespace Library.Model
      {
          [DataContract]
          public class StudentBookDetails
          {
              [DataMember]
              public int Id { get; set; }
              [DataMember]
              public int StudentID { get; set; }
              [DataMember]
              public int BookId { get; set; }
              [DataMember]
              public virtual Book Book { get; set; }
              [DataMember]
              public virtual Student Student { get; set; }
          }
      }
      C#
      using System;
      using System.Collections.Generic;
      using System.Runtime.Serialization;
      
      namespace Library.Model
      {
          [DataContract]
          public class Student
          {
              [DataMember]
              public int StudentID { get; set; }
              [DataMember]
              public string FristName { get; set; }
              [DataMember]
              public string LastName { get; set; }
              [DataMember]
              public DateTime DateOfBirth { get; set; }
              [DataMember]
              public DateTime CreatedDate { get; set; }
              [DataMember]
              public DateTime? ModeifiedDate { get; set; }
              [DataMember]
              public virtual ICollection&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;StudentBookDetails&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; StudentBookDetails { get; set; }
          }
      }
      C#
      using System;
      using System.Collections.Generic;
      using System.Runtime.Serialization;
      
      namespace Library.Model
      {
          [DataContract]
          public class Book
          {
              [DataMember]
              public int BookId { get; set; }
              [DataMember]
              public string Name { get; set; }
              [DataMember]
              public string Author { get; set; }
              [DataMember]
              public int Quantity { get; set; }
              [DataMember]
              public DateTime PublishDate { get; set; }
              [DataMember]
              public string Section { get; set; }
              [DataMember]
              public DateTime CreatedDate { get; set; }
              [DataMember]
              public DateTime? ModeifiedDate { get; set; }
              [DataMember]
              public virtual ICollection&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;
              lt;StudentBookDetails&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; 
              StudentBookDetails { get; set; }
          }
      }
  5. Next, add a new class project and name it as Library.Repository, install NuGet package for entity framework, once installed, go ahead and add an interface ILibraryRepository. Also add reference of Library.Model in this project.
    C#
    using Library.Model;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    
    namespace Library.Repository
    {
     public interface ILibraryRepository
     {
     Task<List<Book>> GetBooks();
     Task<List<Student>> GetStudent();
     Task<int> AddBooks(Book book);
     Task<int> AddStudent(Student student);
     Task<int> ProvideBookToStudent(StudentBookDetails details);
     Task<bool> AddRemoveQuantity(Book book);
     }
    }
  6. Next, let’s add a new class and name it as LibraryContext.
    C#
    using Library.Model;
    using System.Data.Entity;
    using System.Data.Entity.ModelConfiguration.Conventions;
    
    namespace Library.Repository
    {
     public class LibraryContext : DbContext
     {
     public LibraryContext() : base("DefaultConnection") { }
     public DbSet<Student> Student { get; set; }
     public DbSet<Book> Book { get; set; }
     public DbSet<StudentBookDetails> StudentBookDetails { get; set; }
     protected override void OnModelCreating(DbModelBuilder modelBuilder)
     {
     base.OnModelCreating(modelBuilder);
    
     modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
     }
     }
    }
  7. Now, add a new class LibraryRepository, and implement the interface.
    C#
    using Library.Model;
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Threading.Tasks;
    
    namespace Library.Repository
    {
     public class LibraryRepository : ILibraryRepository
     {
     private LibraryContext _context = new LibraryContext();
     public LibraryRepository(LibraryContext context)
     {
     this._context = context;
     }
    
     public async Task<int> AddBooks(Book book)
     {
     book.CreatedDate = DateTime.Now;
     _context.Book.Add(book);
     int x = await _context.SaveChangesAsync();
     return x;
     }
    
     public async Task<bool> AddRemoveQuantity(Book book)
     {
     Book bupdate = await _context.Book.FindAsync(book.BookId);
     bupdate.Quantity = bupdate.Quantity - 1;
     int x = await _context.SaveChangesAsync();
     return x == 0 ? false : true;
     }
    
     public async Task<int> AddStudent(Student student)
     {
     student.CreatedDate = DateTime.Now;
     _context.Student.Add(student);
     int x = await _context.SaveChangesAsync();
     return x;
     }
    
     public async Task<List<Book>> GetBooks()
     {
     return await _context.Book.ToListAsync();
     }
    
     public async Task<List<Student>> GetStudent()
     {
     return await _context.Student.ToListAsync();
     }
    
     public async Task<int> ProvideBookToStudent(StudentBookDetails details)
     {
     _context.StudentBookDetails.Add(details);
     int x = await _context.SaveChangesAsync();
     return x;
     }
     }
    }
  8. Repository is done, now compile this project and check if we are getting any error.
  9. Now let’s get back to our WCFTransactionService service, open app.config to set our service to use the transaction feature, to use that, we need use “wsHttpBinding”, so go to binding and change it and in binding, add transactionFlow=”true”.
    XML
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
     <configSections>
     <!-- For more information on Entity Framework configuration, 
     visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
     <section name="entityFramework" 
     type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, 
     EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
     requirePermission="false" />
     </configSections>
     <appSettings>
     <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
     </appSettings>
     <system.web>
     <compilation debug="true" />
     </system.web>
     <!-- When deploying the service library project, 
     the content of the config file must be added to the host's 
     app.config file. System.Configuration does not support config files for libraries. -->
     <system.serviceModel>
     <services>
     <service name="WCFTransactionService.LibraryService">
     <endpoint address="" binding="wsHttpBinding" 
     contract="WCFTransactionService.ILibraryService">
     <identity>
     <dns value="localhost" />
     </identity>
     </endpoint>
     <host>
     <baseAddresses>
     <add baseAddress=
      "http://localhost:8733/Design_Time_Addresses/WCFTransactionService/LibraryService/" />
     </baseAddresses>
     </host>
     </service>
     </services>
     <behaviors>
     <serviceBehaviors>
     <behavior>
     <!-- To avoid disclosing metadata information, 
     set the values below to false before deployment -->
     <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
     <!-- To receive exception details in faults for debugging purposes, 
     set the value below to true. Set to false before deployment 
     to avoid disclosing exception information -->
     <serviceDebug includeExceptionDetailInFaults="true" />
     </behavior>
     </serviceBehaviors>
     </behaviors>
     <bindings>
     <wsHttpBinding>
     <binding transactionFlow="true" />
     </wsHttpBinding>
     </bindings>
     </system.serviceModel>
     <entityFramework>
     <defaultConnectionFactory 
     type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
     <providers>
     <provider invariantName="System.Data.SqlClient" 
     type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
     </providers>
     </entityFramework>
    </configuration>
  10. Now add a class as we already have interface we need to implement the same, name the class as “LibraryService”.
  11. To configure our service, we need to add some attributes If you noticed the interface, we have an attribute called TransactionFlow - this attribute is required to configure which method will take part in transaction and which will not.
  12. Now let's come to our class which will implement the interface .
    C#
    using Library.Model;
    using Library.Repository;
    using System.Collections.Generic;
    using System.ServiceModel;
    using System.Threading.Tasks;
    using System.Transactions;
    
    namespace WCFTransactionService
    {
     [ServiceBehavior(TransactionIsolationLevel = 
     IsolationLevel.Serializable, TransactionTimeout = " 00:00:30")]
     public class LibraryService : ILibraryService
     {
    
     private ILibraryRepository _repo;
     private LibraryContext _context = new LibraryContext();
     public LibraryService()
     {
     _repo = new LibraryRepository(_context);
     }
     [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
     public async Task<int> AddBooks(Book book)
     {
     return await _repo.AddBooks(book);
     }
    
     [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
     public async Task<bool> AddRemoveQuantity(Book book)
     {
     return await _repo.AddRemoveQuantity(book);
     }
    
     [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
     public async Task<int> AddStudent(Student student)
     {
     return await _repo.AddStudent(student);
     }
    
     public async Task<List<Book>> GetBooks()
     {
     return await _repo.GetBooks();
     }
    
     public async Task<List<Student>> GetStudent()
     {
     return await _repo.GetStudent();
     }
    
     [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
     public async Task<int> ProvideBookToStudent(StudentBookDetails details)
     {
     return await _repo.ProvideBookToStudent(details);
     }
     }
    }
  13. If you notice, there is ServiceBehavior attribute where we can set multiple attributes, we are using TransactionIsolationLevel. This is similar to the isolation level in SQL, this is set to IsolationLevel.Serializable which is default, it will give only committed rows, you can change it to ReadCommited, ReadUnCommitted and many more, choose which you think is required in your scenario.
  14. Next attribute us OperationBehavior, where we are using 2 properties TransactionScopeRequired and TransactionAutoComplete. We can use TransactionScopeRequired to set which methods will be used in TransactionScope and TransactionAutoComplete can be set to true or false.
  15. Now, add a console project and name it as LibraryClient, add a service reference, click on discover to get the service.

    Image 2

  16. Go to program.cs and add the below code, if you notice on PublishDate = Convert.ToDateTime(“3423”). This is an intentional error generated to show if the transaction works or not.
    C#
    using LibraryClient.LibraryServiceRef;
    using System;
    using System.ServiceModel;
    using System.Transactions;
    
    namespace LibraryClient
    {
     class Program
     {
     static void Main(string[] args)
     {
     try
     {
     using (var transScope = new TransactionScope())
     {
     using (LibraryServiceClient client = new LibraryServiceClient())
     {
     Student s = new Student()
     { FristName = "Santosh", LastName = "Yadav", 
     DateOfBirth = Convert.ToDateTime("13-Nov-1986") };
     int student=client.AddStudent(s);
    
     Console.WriteLine("Number of student:" + student);
    
     Book b = new Book() { PublishDate = Convert.ToDateTime("3423"), 
     Author = "Santosh", Name = "WCF", Quantity = 10, Section = "Test" };
     client.AddBooks(b);
    
     transScope.Complete();
     }
     }
     }
     catch (FaultException ex)
     {
     Console.Write(ex);
     }
     }
     }
    }
  17. If you see the output, we will get the “Number of student:1”, post which error will be generated. Now run it again. We will get the count as 1 again, which shows that transaction is working fine.

Conclusion

The transaction feature is one of the powerful features when you want to give more controls to client on how and in which sequence to call the methods. One of the real-time examples is ecommerce, where you can call PlaceOrder after Payment or Payment after PlaceOrder in transaction.

You can reach out to me at santosh.yadav198613@gmail.com for any queries.

For code, refer to this link.

In the next post, we will continue with Angular.

License

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