Martin Fowler’s Design Pattern Transaction Script in C# (With UI)

Daneesha Bartholomeusz
4 min readNov 20, 2021

--

Most of the example code in Martin Fowler’s book Patterns of Enterprise Application Architecture are in Java. Here I’m trying to write the equivalent code in C#. I will also provide a simple user interface.

The examples are not a complete application. They’re stripped down to explain the principle. Here I have kept them as thus.

I have made some modifications to the code where I thought there were bugs. This is a running application and has been tested on Visual Studio 2019 (version 16.10.3).

I wanted this to be an experience such as going through a tutorial. But I haven’t explained some of the basics like how to start a new project in Visual Studio, how to add components to the user interface etc. I’m assuming that you already know some of these things. (If you don’t have a look at the web to learn what you have to learn before you start this tutorial).

This shouldn’t be taken as a guide to organizing a software project. There’re best practices I have neglected. Such as naming the components with meaningful names. I wanted only to transfer the knowledge about the pattern.

I have not described the design pattern at all here. For that you’ll have to go to the book.

The database table structure is as given in the book.

Here’s what the UI looks like.

Here’s the code for the Form (in Form1.cs class). btnSaveRevenue and btnCalculate are the two buttons on the UI.

public partial class Form1 : Form
{
RecognitionService recognitionService = new RecognitionService();
public Form1()
{
InitializeComponent();
}
private void btnSaveRevenue_Click(object sender, EventArgs e)
{
try
{
recognitionService.CalculateRevenueRecognitions(
Convert.ToInt64(txtContractNumber1.Text));
MessageBox.Show("Successfully saved the revenue to database");
}
catch (Exception)
{
MessageBox.Show("Could not save the revenue to database");
}
}
private void btnCalculate_Click(object sender, EventArgs e)
{
try
{
long contractNumber =Convert.ToInt64(
txtContractNumber2.Text);
DateTime date = dtpAsOf.Value;
lblRevenue.Text = recognitionService.RecognizedRevenue(
contractNumber, date).ToString();
}
catch (Exception)
{
MessageBox.Show("Could not retrive revenue");
}
}
}

Here’s the code for the RecognitionService class.

public class RecognitionService
{
public decimal RecognizedRevenue(long contractNumber, DateTime asof)
{
decimal result = 0.0M;
Gateway gateway = new Gateway();
try
{
List<ResultSetRevenueRecognitions> resultSet = gateway.FindRecognitionsFor(contractNumber, asof);
for (int i = 0; i < resultSet.Count; i++)
{
result += resultSet[i].Amount;
}
return result;
}
catch (SqlException)
{
throw;
}
}
public void CalculateRevenueRecognitions(long contractNumber)
{
try
{
Gateway gateway = new Gateway();
ResultSetContracts contract = gateway.FindContract(contractNumber);

decimal totalRevenue = contract.Revenue;
DateTime recognitionDate = contract.DateSigned;
int type = contract.Product;
if(type == 1)
{
gateway.InsertRecognition(contractNumber, totalRevenue, recognitionDate);
}
else if (type == 2)
{
decimal allocation = totalRevenue/3;
gateway.InsertRecognition(contractNumber, allocation, recognitionDate);
gateway.InsertRecognition(contractNumber, allocation, recognitionDate.AddDays(30));
gateway.InsertRecognition(contractNumber, allocation, recognitionDate.AddDays(60));
}
else if (type == 3)
{
decimal allocation = totalRevenue/3;
gateway.InsertRecognition(contractNumber, allocation, recognitionDate);
gateway.InsertRecognition(contractNumber, allocation, recognitionDate.AddDays(60));
gateway.InsertRecognition(contractNumber, allocation, recognitionDate.AddDays(90));
}

}
catch (SqlException)
{
throw;
}
}
}

Here’s the code for the Gateway class.

public class Gateway
{
private SqlConnection conn;
private string connectionString;
private static readonly string findRecognitionStatement =
"SELECT Amount " +
"FROM RevenueRecognitions " +
"WHERE Contract = @Contract AND RecognizedOn <= @RecognizedDate";
private static readonly string findContractStatement =
"SELECT * " +
"FROM Contracts c, Products p " +
"WHERE c.ID = @ID AND c.Product = p.ID";
private static readonly string insertRecognitionStatement =
"INSERT INTO RevenueRecognitions VALUES (@Contract, @Amount, @RecognizedOn)";
public List<ResultSetRevenueRecognitions> FindRecognitionsFor(long contractID, DateTime asof)
{
try
{
connectionString = @"Data Source=LAPTOP-8LO4E9GU; Initial Catalog=RevenueRecognition; Integrated Security=True;";
conn = new SqlConnection(connectionString);
conn.Open();
SqlCommand command;
SqlDataReader dataReader;
command = new SqlCommand(findRecognitionStatement, conn);
List<ResultSetRevenueRecognitions> resultSet = new List<ResultSetRevenueRecognitions>();
SqlParameter param1 = new SqlParameter();
param1.ParameterName = "@Contract";
param1.Value = contractID;
SqlParameter param2 = new SqlParameter();
param2.ParameterName = "@RecognizedDate";
param2.Value = asof;
command.Parameters.Add(param1);
command.Parameters.Add(param2);
dataReader = command.ExecuteReader();
while (dataReader.Read())
{
ResultSetRevenueRecognitions recognition = new ResultSetRevenueRecognitions();
recognition.Amount = (decimal)dataReader.GetValue(0);
resultSet.Add(recognition);
}
dataReader.Close();
command.Dispose();
conn.Close();
return resultSet;
}
catch (SqlException)
{
throw;
}
}
public ResultSetContracts FindContract(long contractID)
{
try
{
connectionString = @"Data Source=LAPTOP-8LO4E9GU; Initial Catalog=RevenueRecognition; Integrated Security=True;";
conn = new SqlConnection(connectionString);
conn.Open();
SqlCommand command;
SqlDataReader dataReader;
command = new SqlCommand(findContractStatement, conn);
ResultSetContracts resultSet = new ResultSetContracts();
SqlParameter param1 = new SqlParameter();
param1.ParameterName = "@ID";
param1.Value = contractID;
command.Parameters.Add(param1);dataReader = command.ExecuteReader();
while (dataReader.Read())
{
resultSet.ID = (int)dataReader.GetValue(0);
resultSet.Product = (int)dataReader.GetValue(1);
resultSet.Revenue = (decimal)dataReader.GetValue(2);
resultSet.DateSigned = (DateTime)dataReader.GetValue(3);
resultSet.ProductID = (int)dataReader.GetValue(4);
resultSet.Name = (string)dataReader.GetValue(5);
resultSet.Type = (int)dataReader.GetValue(6);
}
dataReader.Close();
command.Dispose();
conn.Close();
return resultSet;
}
catch (SqlException)
{
throw;
}
}
public void InsertRecognition(long contractID, decimal amount, DateTime asof)
{
try
{
connectionString = @"Data Source=LAPTOP-8LO4E9GU; Initial Catalog=RevenueRecognition; Integrated Security=True;";
conn = new SqlConnection(connectionString);
conn.Open();
SqlCommand command;
SqlDataAdapter adapter = new SqlDataAdapter();
command = new SqlCommand(insertRecognitionStatement, conn);

SqlParameter param1 = new SqlParameter();
param1.ParameterName = "@Contract";
param1.Value = contractID;
SqlParameter param2 = new SqlParameter();
param2.ParameterName = "@Amount";
param2.Value = amount;
SqlParameter param3 = new SqlParameter();
param3.ParameterName = "@RecognizedOn";
param3.Value = asof;
command.Parameters.Add(param1);
command.Parameters.Add(param2);
command.Parameters.Add(param3);
adapter.InsertCommand = command;
adapter.InsertCommand.ExecuteNonQuery();

command.Dispose();
conn.Close();
}
catch (SqlException)
{
throw;
}
}
}

In the Java example in the book a resultSet is used to pass data from the Gataway to the Service class. I have used Data Transfer Objects here. You can also use Datasets.

Here’s the data transfer object class for Contract.

public class ResultSetContracts
{
public int ID { get; set; }
public int Product { get; set; }
public decimal Revenue { get; set; }
public DateTime DateSigned { get; set; }
public int ProductID { get; set; }
public string Name { get; set; }
public int Type { get; set; }
}

Here’s the data transfer object class for Revenue Recognitions.

public class ResultSetRevenueRecognitions
{
public int Contract { get; set; }
public decimal Amount { get; set; }
public DateTime RecognizedOn { get; set; }
}

I hope this helps someone who’s studying patterns and looking for the code in C#.

--

--

No responses yet