Tutorial - Unit of Work (Repository) Pattern with CodeTrigger
CodeTrigger provides support for the UnitOfWork pattern, by decorating its classes with the appropriate interfaces. However you still need to provide an implementation for those interfaces, corresponding to your requirements. Here is a sample that shows one way of doing that, while also using the Repository pattern.
This guide has been updated for version 5.1.0.0 (released June 10th 2017)
|
|
Microsoft Northwind Sample db
|
STEP 1 - Ensure you have a relevant test database
The CodeTrigger sample applications are designed to work with your existing database
schema to ensure that the samples are as relevant as possible. Ensure that you have
a TEST SQL Server/Oracle/MYSQL database handy. CodeTrigger and its associated supplier
companies do not accept any liability for data loss whilst using the CodeTrigger
product. It is important that you use a test database until you are familiar with
the functionality of the generated code. This code sample assumes MSQL DB Server/NORTHWIND test database,
but should work with your MYSQL/ORACLE db server and your test database of choice.
|
|
Step 2 - Create a new CodeTrigger project
Create a new Multi-tier application using the wizard. As a guide you can use the 'DB to Microservice' wizard. Name your project UnitOfWorkRepoSample
and generate your application as usual, using CodeTrigger.
Confirm that your project builds and runs successfully.
|
|
|
Step 3 - Modify the CodeTrigger project
Choose the following settings in the Advanced options tab:
Generate interface definitions: Ticked
Generate using repository pattern: Ticked
Enable for Unit of Work pattern: Ticked
If you are using Northwind sample test database, select 'Employees' and 'Customers' from the Schema Objects tab and 'BOEmployees' and 'BOCustomers' in the Business Objects tab,
(If you are not using the Northwind sample test database, select suitable objects from those listed in your Business Objects tab)
and click Generate Code to generate the files.
|
|
Step 4 - Define your IUnitOfWork interface and Implementation
Define your UnitOfWork interface and implementation to utilize the codetrigger interfaces 'IUnitOfWorkEntity'.
An example implementation is listed here, either use as is, or modify to suit your requirements
Add the IUnitOfWorkRepo.cs to the BusinessObjects/Interfaces folder, and the UnitOfWorkRepoImp.cs to the BusinessObjects/Implementation folder.
/*IUnitOfWork Interface definition - IUnitOfWorkRepo.cs*/
using System;
using UnitOfWorkRepoSample.BusinessObjects.Repository.Interfaces;
namespace UnitOfWorkRepoSample.BusinessObjects.Interfaces
{
public interface IUnitOfWorkRepo
{
void Create(IUnitOfWorkEntity busObj);
void Update(IUnitOfWorkEntity busObj);
void Delete(IUnitOfWorkEntity busObj);
bool Commit(IRepositoryConnection[] iRepos, out string message);
}
}
|
|
/*UnitOfWork Implementation - UnitOfWorkRepoImp.cs*/
using System;
using System.Collections.Generic;
using UnitOfWorkRepoSample.BusinessObjects.Interfaces;
using UnitOfWorkRepoSample.BusinessObjects.Repository.Interfaces;
namespace UnitOfWorkRepoSample.BusinessObjects
{
public class UnitOfWorkRepoImp : zUnitOfWorkRepoSampleConn_BaseBusiness, IUnitOfWorkRepo
{
private enum OpType
{ Create, Update, Delete };
List<Tuple<IUnitOfWorkEntity, OpType>> _opList = new List<Tuple<IUnitOfWorkEntity, OpType>>();
public void Create(IUnitOfWorkEntity busObj)
{
_opList.Add(new Tuple<IUnitOfWorkEntity, OpType>(busObj, OpType.Create));
}
public void Update(IUnitOfWorkEntity busObj)
{
_opList.Add(new Tuple<IUnitOfWorkEntity, OpType>(busObj, OpType.Update));
}
public void Delete(IUnitOfWorkEntity busObj)
{
_opList.Add(new Tuple<IUnitOfWorkEntity, OpType>(busObj, OpType.Delete));
}
public bool Commit(IRepositoryConnection[] iRepos, out string message)
{
message = "SUCCESS";
string transName = GetTransactionName();
this.BeginTransactions(iRepos, transName);
try
{
foreach (var op in _opList)
{
switch(op.Item2)
{
case OpType.Create:
op.Item1.SaveNew();
break;
case OpType.Update:
op.Item1.Update();
break;
case OpType.Delete:
op.Item1.Delete();
break;
}
}
CommitTransactions(iRepos);
return true;
}
catch(Exception ex)
{
base.RollbackTransactions(iRepos, transName);
message = ex.Message;
return false;
}
}
private string GetTransactionName()
{
return "tr-" + DateTime.Now.TimeOfDay.Ticks.ToString();
}
}
}
Build your project to make sure it builds successfully and fix any namespace issues.
|
|
Step 5 - Write some code to test our your UnitOfWork Implementation
You are ready to test out your UnitOfWork implementation. Here is some code that (using the Northwind sample database) shows an update to a customer record and an update to an employee record wrapped as a single unit of work. The update to the customer record occurs first, and when the update to the employee record fails, the entire unit of work is rolled back to the original state.
Add the relevant namespaces to your generated UnitOfWorkRepoSample.Svc.Service.cs class, and add the following code to the 'Start()' method so it looks like this:
using System;
using UnitOfWorkRepoSample.BusinessObjects;
using UnitOfWorkRepoSample.BusinessObjects.Repository;
using UnitOfWorkRepoSample.BusinessObjects.Repository.Interfaces;
namespace UnitOfWorkRepoSample.Svc
{
public class Service
{
public bool Start()
{
Console.WriteLine("Service started...");
RF repoFactory = RF.New();
string originalcontactName = ""; //Maria Anders
string updatedContactName = "Updated despite exceptions";
int invalidReportingLine = -1;
BOCustomers boCustomer = new BOCustomers();
boCustomer.Repository = repoFactory.CustomersRepository;
boCustomer.Init("ALFKI");
originalcontactName = boCustomer.ContactName;
BOEmployees boEmployee = new BOEmployees();
boEmployee.Repository = repoFactory.EmployeesRepository;
boEmployee.Init(1);
Console.WriteLine("");
boCustomer.ContactName = updatedContactName;
boEmployee.ReportsTo = invalidReportingLine;
Console.Write("Try saving the 2 object instances to the db. We expect the employee save to fail with ");
Console.Write("invalid foreign key, thereby rolling back the unit of work");
Console.WriteLine("");
Console.WriteLine("");
UnitOfWorkRepoImp unitOfWork = new UnitOfWorkRepoImp();
unitOfWork.Update(boCustomer);
unitOfWork.Update(boEmployee);
string message;
if (!unitOfWork.Commit(new IRepositoryConnection[] {(IRepositoryConnection)boCustomer.Repository,
(IRepositoryConnection)boEmployee.Repository}, out message))
{
Console.WriteLine("As expected, error thrown:" + message);
Console.WriteLine("");
Console.WriteLine("However, we can check that the customer instance update was also rolled back");
BOCustomers boCustomerCheck = new BOCustomers();
boCustomerCheck.Repository = new CustomersRepository();
boCustomerCheck.Init("ALFKI");
Console.WriteLine("Customer contact name: '" + boCustomerCheck.ContactName + "'. Original contact name: '"
+ originalcontactName + "' is the same, so customer name change was rolled back"
+ ", when subsequent employee update failed.");
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Unit of Work rollback "
+ (boCustomerCheck.ContactName == originalcontactName ? "successful." : "failed."));
}
else
{
Console.WriteLine("---SHOULD NOT GET HERE [If you do, check your constraint values for reportinglineid in your db ");
Console.WriteLine("and use an invalid one instead]");
}
Console.WriteLine("Press any key to continue..");
Console.ReadLine();
return true;
}
public bool Stop()
{
return true;
}
public bool Pause()
{
Console.WriteLine("Service paused...");
return true;
}
public bool Continue()
{
Console.WriteLine("Service continued...");
return true;
}
}
}
|
|