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.
- 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
”.
- 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. - Add a new interface called “
ILibraryService
”, and add the following methods:
AddBooks
AddRemoveQuantity
AddStudent
GetBooks
GetStudent
ProvideBookToStudent
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);
}
}
- 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.
Book
Student
StudentBookDetails
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; }
}
}
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;lt;StudentBookDetails&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; StudentBookDetails { get; set; }
}
}
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;
lt;StudentBookDetails&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
StudentBookDetails { get; set; }
}
}
- 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.
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);
}
}
- Next, let’s add a new class and name it as
LibraryContext
.
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>();
}
}
}
- Now, add a new class
LibraryRepository
, and implement the interface.
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;
}
}
}
- Repository is done, now compile this project and check if we are getting any error.
- 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”
.
="1.0"="utf-8"
<configuration>
<configSections>
<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>
<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>
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
<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>
- Now add a class as we already have interface we need to implement the same, name the class as “
LibraryService
”. - 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. - Now let's come to our class which will implement the interface .
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);
}
}
}
- 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. - 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
. - Now, add a console project and name it as
LibraryClient
, add a service reference, click on discover to get the service.
- 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.
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);
}
}
}
}
- 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.