My team and I were going to start working on a big project. It was about a Web Application for a huge company. You can’t imagine tempted I am to reveal its name, but unfortunately, I can’t.
The client had some specific requirements and some of them were actually technical ones. I know this is not a normal thing, but, this client already had their own team of technical people so that they could cope with our team.
Long story short, let me skip the too-long list of requirements and jump to the ones I am actually interested in for this article.
As I said, our client had some specific requirements and some of them were technical ones. So, let me walk you through some of these interesting requirements and I am sure you would fall in love with them.
The application needed to have a backend to store all the data processed. And, Once I say Backend, you say… Repository.
The most interesting part of the requirements was related to the Repository to be implemented. Therefore, let me tell you about these ones.
Scheduled Automatic Health Check on Repositories
APIs Throttling Restrictions on Queries
We would start with a very simple entity representing an employee. However, since our design in general would need some high-level abstraction, we need to have an abstract Entity that would be inherited by all our system entities.
Entity
namespace BetterRepository.Entities
{
public abstract class Entity
{
}
}
For our solution, we will keep it simple and don’t provide any common members for the abstract Entity
class. However, for your own solution, you might need to do so.
Employee
using System;
using System.Text;
namespace BetterRepository.Entities
{
public class Employee : Entity
{
public int Id { get; }
public string Name { get; }
public Employee(int id, string name)
{
Id = id;
Name = name;
}
public Employee(Employee other, int id)
{
if (other == null) throw new ArgumentException("Other cannot be null");
Id = id;
Name = other.Name;
}
// This code is added for demonstration purposes only.
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendLine($"Id: {Id}, Name: {Name}");
return builder.ToString();
}
}
}
What we can notice here:
Employee
class which inherits the abstract Entity
class.Id
and Name
.ToString
method to be used for demonstration purposes only. This is not a best practice to follow.If you wish to understand the paging code, I recommend that you first go and check that article.
I would include the paging code here for brevity and as a quick reference.
using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace BetterRepository.Models
{
public class PagingDescriptor
{
public int ActualPageSize { get; private set; }
public int NumberOfPages { get; private set; }
public PageBoundry[] PagesBoundries { get; private set; }
public PagingDescriptor(
int actualPageSize,
int numberOfPages,
PageBoundry[] pagesBoundries)
{
ActualPageSize = actualPageSize;
NumberOfPages = numberOfPages;
PagesBoundries = pagesBoundries;
}
// This code is added for demonstration purposes only.
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendLine($"ActualPageSize: {ActualPageSize}");
builder.AppendLine($"NumberOfPages: {NumberOfPages}");
builder.AppendLine("");
builder.AppendLine($"PagesBoundries:");
builder.AppendLine($"----------------");
foreach (var boundry in PagesBoundries)
{
builder.Append(boundry.ToString());
}
return builder.ToString();
}
}
public class PageBoundry
{
public int FirstItemZeroIndex { get; private set; }
public int LastItemZeroIndex { get; private set; }
public PageBoundry(int firstItemZeroIndex, int lastItemZeroIndex)
{
FirstItemZeroIndex = firstItemZeroIndex;
LastItemZeroIndex = lastItemZeroIndex;
}
// This code is added for demonstration purposes only.
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendLine($"FirstItemZeroIndex: {FirstItemZeroIndex}, LastItemZeroIndex: {LastItemZeroIndex}");
return builder.ToString();
}
}
public static class ListExtensionMethods
{
public static PagingDescriptor Page(this IList list, int pageSize)
{
var actualPageSize = pageSize;
if (actualPageSize <= 0)
{
actualPageSize = list.Count;
}
var maxNumberOfPages =
(int)Math.Round(Math.Max(1, Math.Ceiling(((float)list.Count) / ((float)actualPageSize))));
return new PagingDescriptor(
actualPageSize,
maxNumberOfPages,
Enumerable
.Range(0, maxNumberOfPages)
.Select(pageZeroIndex => new PageBoundry(
pageZeroIndex * actualPageSize,
Math.Min((pageZeroIndex * actualPageSize) + (actualPageSize - 1), list.Count - 1)
)).ToArray()
);
}
}
}
What we can notice here:
ToString
method.This leads us to the unified object which is returned from the Get repository calls which is in our case called QueryResult
.
using System.Collections.Generic;
using System.Text;
using BetterRepository.Entities;
namespace BetterRepository.Models
{
public interface IQueryResult
{
PagingDescriptor PagingDescriptor { get; }
int ActualPageZeroIndex { get; }
IEnumerable<Entity> Results { get; }
}
public interface IQueryResult<out TEntity> : IQueryResult where TEntity : Entity
{
new IEnumerable<TEntity> Results { get; }
}
public class QueryResult<TEntity> : IQueryResult<TEntity> where TEntity : Entity
{
public QueryResult(
PagingDescriptor pagingDescriptor,
int actualPageZeroIndex,
IEnumerable<TEntity> results)
{
PagingDescriptor = pagingDescriptor;
ActualPageZeroIndex = actualPageZeroIndex;
Results = results;
}
public PagingDescriptor PagingDescriptor { get; }
public int ActualPageZeroIndex { get; }
public IEnumerable<TEntity> Results { get; }
IEnumerable<Entity> IQueryResult.Results => Results;
// This code is added for demonstration purposes only.
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendLine($"ActualPageZeroIndex: {ActualPageZeroIndex}");
builder.AppendLine("");
builder.AppendLine($"PagingDescriptor:");
builder.AppendLine($"----------------");
builder.AppendLine(PagingDescriptor.ToString());
builder.AppendLine($"Results:");
builder.AppendLine($"----------------");
foreach (var entity in Results)
{
builder.Append(entity.ToString());
}
return builder.ToString();
}
}
}
What we can notice here:
IQueryResult
interface which represents the object to be returned from the Get repository calls.PagingDescriptor
and ActualPageZeroIndex
properties so that the caller would know the exact paging details of the data retrieved by his call.Results
property which is an IEnumerable
of the abstract base Entity
results.IQueryResult<out TEntity>
generic interface so that we can have strong typed language support for our specific repositories.IEnumerable<Entity> IQueryResult.Results
property and replacing it with a new typed one.QueryResult<TEntity>
class which implements the IQueryResult<TEntity>
interface.ToString
method to be used for demonstration purposes only. This is not a best practice to follow.We would support different methods on our repositories including AddOrUpdate
methods. This would make the caller life easier whenever needed.
For that, we need to define a unified object structure to be returned from the AddOrUpdate
method.
namespace BetterRepository.Models
{
public enum AddOrUpdate
{
Add,
Update
}
public interface IAddOrUpdateDescriptor
{
AddOrUpdate ActionType { get; }
int Id { get; }
}
public class AddOrUpdateDescriptor : IAddOrUpdateDescriptor
{
public AddOrUpdate ActionType { get; }
public int Id { get; }
public AddOrUpdateDescriptor(AddOrUpdate actionType, int id)
{
ActionType = actionType;
Id = id;
}
}
}
What we can notice here:
IAddOrUpdateDescriptor
interface.AddOrUpdateDescriptor
class which implements IAddOrUpdateDescriptor
and it is immutable.AddOrUpdate
.Now we move to our repository definitions. We would split our repository methods into two parts; Queries and Commands. I can hear you saying Command and Query Responsibility Segregation (CQRS).
Yes, our design would embrace the same concept of CQRS but it is not fully implemented here. So, please don’t judge this design as an incomplete CQRS as it was not intended to be in the first place.
IQueryRepository
public interface IQueryRepository
{
IQueryResult<Entity> GetAll();
Entity Get(int id);
IQueryResult<Entity> Get(int pageSize, int pageIndex);
}
What we can notice here:
IQueryRepository
interface which represents any non-generic Query Repository.Entity Get(int id)
method to get an entity by Id.IQueryResult<Entity> GetAll()
method to get all the entities inside the implementing repository. Please keep in mind that throttling restrictions should be applied and that’s why we are not just returning a list of entities, we are returning IQueryResult
.IQueryResult<Entity> Get(int pageSize, int pageIndex)
method to get entities when divided into certain page size. Please keep in mind that throttling restrictions should be applied here as well. Therefore, if the throttling threshold is somehow set to 5, and the caller is requesting to get entities in pages of 8 entities per page, an adaptation would be applied and the caller would be aware of it by using the returned IQueryResult
object.
IQueryRepository<TEntity>
public interface IQueryRepository<TEntity> : IQueryRepository where TEntity : Entity
{
new IQueryResult<TEntity> GetAll();
new TEntity Get(int id);
new IQueryResult<TEntity> Get(int pageSize, int pageIndex);
IQueryResult<TEntity> GetByExpression(Expression<Func<TEntity, bool>> predicate);
IQueryResult<TEntity> GetByExpression(Expression<Func<TEntity, bool>> predicate, int pageSize, int pageIndex);
}
We defined IQueryResult<TEntity> GetByExpression(Expression<Func<TEntity, bool>> predicate)
method to get entities after filtering them using the passed in predicate. Please keep in mind that throttling restrictions should be applied here as well.
Additionally, we defined IQueryResult<TEntity> GetByExpression(Expression<Func<TEntity, bool>> predicate, int pageSize, int pageIndex)
method to apply paging after filtering the entities using the passed in predicate. Please keep in mind that throttling restrictions should be applied here as well.
QueryRepository<TEntity>
public abstract class QueryRepository<TEntity> : IQueryRepository<TEntity> where TEntity : Entity
{
public abstract IQueryResult<TEntity> GetAll();
public abstract IQueryResult<TEntity> Get(int pageSize, int pageIndex);
public abstract TEntity Get(int id);
public abstract IQueryResult<TEntity> GetByExpression(Expression<Func<TEntity, bool>> predicate);
public abstract IQueryResult<TEntity> GetByExpression(Expression<Func<TEntity, bool>> predicate, int pageSize, int pageIndex);
IQueryResult<Entity> IQueryRepository.GetAll()
{
return GetAll();
}
Entity IQueryRepository.Get(int id)
{
return Get(id);
}
IQueryResult<Entity> IQueryRepository.Get(int pageSize, int pageIndex)
{
return Get(pageSize, pageIndex);
}
}
What we can notice here:
IQueryRepository<TEntity>
.IQueryRepository<TEntity>
interface would be delegated to the child classes inheriting from QueryRepository<TEntity>
.IQueryResult<Entity> IQueryRepository.GetAll()
, it is defaulted to call the other IQueryResult<TEntity> GetAll()
which would be implemented by child classes.Entity IQueryRepository.Get(int id)
, it is defaulted to call the other TEntity Get(int id)
which would be implemented by child classes.IQueryResult<Entity> IQueryRepository.Get(int pageSize, int pageIndex)
, it is defaulted to call the other IQueryResult<TEntity> Get(int pageSize, int pageIndex)
which would be implemented by child classes.Here are the definitions related to any Command Repository.
ICommandRepository
public interface ICommandRepository
{
int Add(Entity entity);
IEnumerable<int> Add(IEnumerable<Entity> entities);
void Update(Entity entity);
void Update(IEnumerable<Entity> entities);
IAddOrUpdateDescriptor AddOrUpdate(Entity entity);
IEnumerable<IAddOrUpdateDescriptor> AddOrUpdate(IEnumerable<Entity> entities);
bool Delete(int id);
bool Delete(Entity entity);
IDictionary<int, bool> Delete(IEnumerable<Entity> entities);
}
What we can notice here:
ICommandRepository
interface which represents any non-generic Command Repository.Add
, Update
, AddOrUpdate
, and Delete
.
ICommandRepository<in TEntity>
public interface ICommandRepository<in TEntity> : ICommandRepository where TEntity : Entity
{
int Add(TEntity entity);
IEnumerable<int> Add(IEnumerable<TEntity> entities);
void Update(TEntity entity);
void Update(IEnumerable<TEntity> entities);
IAddOrUpdateDescriptor AddOrUpdate(TEntity entity);
IEnumerable<IAddOrUpdateDescriptor> AddOrUpdate(IEnumerable<TEntity> entities);
bool Delete(TEntity entity);
IDictionary<int, bool> Delete(IEnumerable<TEntity> entities);
abstract int ICommandRepository.Add(Entity entity);
abstract IEnumerable<int> ICommandRepository.Add(IEnumerable<Entity> entities);
abstract void ICommandRepository.Update(Entity entity);
abstract void ICommandRepository.Update(IEnumerable<Entity> entities);
abstract IAddOrUpdateDescriptor ICommandRepository.AddOrUpdate(Entity entity);
abstract IEnumerable<IAddOrUpdateDescriptor> ICommandRepository.AddOrUpdate(IEnumerable<Entity> entities);
abstract bool ICommandRepository.Delete(Entity entity);
abstract IDictionary<int, bool> ICommandRepository.Delete(IEnumerable<Entity> entities);
}
What worth mentioning here is that starting from C#8.0, you can add this to an interface.
abstract int ICommandRepository.Add(Entity entity);
In our case, this means that although ICommandRepository<in TEntity>
extends ICommandRepository
, any class implementing ICommandRepository<in TEntity>
interface would not expose int Add(Entity entity)
method unless it is casted -implicitly or explicitly- into the non-generic ICommandRepository
interface.
CommandRepository<TEntity>
public abstract class CommandRepository<TEntity> : ICommandRepository<TEntity> where TEntity : Entity
{
public abstract bool Delete(int id);
public abstract int Add(TEntity entity);
public abstract IEnumerable<int> Add(IEnumerable<TEntity> entities);
public abstract void Update(TEntity entity);
public abstract void Update(IEnumerable<TEntity> entities);
public abstract IAddOrUpdateDescriptor AddOrUpdate(TEntity entity);
public abstract IEnumerable<IAddOrUpdateDescriptor> AddOrUpdate(IEnumerable<TEntity> entities);
public abstract bool Delete(TEntity entity);
public abstract IDictionary<int, bool> Delete(IEnumerable<TEntity> entities);
int ICommandRepository.Add(Entity entity)
{
if (entity.GetType() == typeof(TEntity))
{
return Add(entity as TEntity);
}
throw new ArgumentException(
$"The type \"{entity.GetType()}\" does not match the type \"{typeof(TEntity)}\"");
}
IEnumerable<int> ICommandRepository.Add(IEnumerable<Entity> entities)
{
if (entities is IEnumerable<TEntity>)
{
return Add(entities.Select(e => e as TEntity));
}
throw new ArgumentException(
$"The type \"{entities.GetType()}\" does not match the type \"{typeof(IEnumerable<TEntity>)}\"");
}
void ICommandRepository.Update(Entity entity)
{
if (entity.GetType() == typeof(TEntity))
{
Update(entity as TEntity);
}
else
{
throw new ArgumentException(
$"The type \"{entity.GetType()}\" does not match the type \"{typeof(TEntity)}\"");
}
}
void ICommandRepository.Update(IEnumerable<Entity> entities)
{
if (entities is IEnumerable<TEntity>)
{
Update(entities.Select(e => e as TEntity));
}
else
{
throw new ArgumentException(
$"The type \"{entities.GetType()}\" does not match the type \"{typeof(IEnumerable<TEntity>)}\"");
}
}
IAddOrUpdateDescriptor ICommandRepository.AddOrUpdate(Entity entity)
{
if (entity.GetType() == typeof(TEntity))
{
return AddOrUpdate(entity as TEntity);
}
throw new ArgumentException(
$"The type \"{entity.GetType()}\" does not match the type \"{typeof(TEntity)}\"");
}
IEnumerable<IAddOrUpdateDescriptor> ICommandRepository.AddOrUpdate(IEnumerable<Entity> entities)
{
if (entities is IEnumerable<TEntity>)
{
return AddOrUpdate(entities.Select(e => e as TEntity));
}
throw new ArgumentException(
$"The type \"{entities.GetType()}\" does not match the type \"{typeof(IEnumerable<TEntity>)}\"");
}
bool ICommandRepository.Delete(Entity entity)
{
if (entity.GetType() == typeof(TEntity))
{
return Delete(entity as TEntity);
}
throw new ArgumentException(
$"The type \"{entity.GetType()}\" does not match the type \"{typeof(TEntity)}\"");
}
IDictionary<int, bool> ICommandRepository.Delete(IEnumerable<Entity> entities)
{
if (entities is IEnumerable<TEntity>)
{
return Delete(entities.Select(e => e as TEntity));
}
throw new ArgumentException(
$"The type \"{entities.GetType()}\" does not match the type \"{typeof(IEnumerable<TEntity>)}\"");
}
}
What we can notice here:
ICommandRepository<TEntity>
.ICommandRepository<TEntity>
interface would be delegated to the child classes inheriting from CommandRepository<TEntity>
.int ICommandRepository.Add(Entity entity)
, it is defaulted to call the other int Add(TEntity entity)
which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.IEnumerable<int> ICommandRepository.Add(IEnumerable<Entity> entities)
, it is defaulted to call the other IEnumerable<int> Add(IEnumerable<TEntity> entities)
which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.void ICommandRepository.Update(Entity entity)
, it is defaulted to call the other void Update(TEntity entity)
which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.void ICommandRepository.Update(IEnumerable<Entity> entities)
, it is defaulted to call the other void Update(IEnumerable<TEntity> entities)
which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.IAddOrUpdateDescriptor ICommandRepository.AddOrUpdate(Entity entity)
, it is defaulted to call the other IAddOrUpdateDescriptor AddOrUpdate(TEntity entity)
which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.IEnumerable<IAddOrUpdateDescriptor> ICommandRepository.AddOrUpdate(IEnumerable<Entity> entities)
, it is defaulted to call the other IEnumerable<IAddOrUpdateDescriptor> AddOrUpdate(IEnumerable<TEntity> entities)
which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.bool ICommandRepository.Delete(Entity entity)
, it is defaulted to call the other bool Delete(TEntity entity)
which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.IDictionary<int, bool> ICommandRepository.Delete(IEnumerable<Entity> entities)
, it is defaulted to call the other IDictionary<int, bool> Delete(IEnumerable<TEntity> entities)
which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.Now, this is the time to implement our Employee Query and Command repositories. This is where we start inheriting from our base abstract CommandRepository<TEntity>
and QueryRepository<TEntity>
classes.
However, first, let’s clarify that for our example here we are not going to use a real database for storing our Employees data. What we are going to do instead, is to create a static list.
EmployeePersistence
internal static class EmployeePersistence
{
public static readonly List<Employee> Employees = new();
static EmployeePersistence()
{
Reset();
}
public static void Reset()
{
Employees.Clear();
Employees.Add(new Employee(0, "Ahmed"));
Employees.Add(new Employee(1, "Tarek"));
Employees.Add(new Employee(2, "Patrick"));
Employees.Add(new Employee(3, "Mohamed"));
Employees.Add(new Employee(4, "Sara"));
Employees.Add(new Employee(5, "Ali"));
}
}
It is as simple as a list of employees.
EmployeeQueryRepository
public class EmployeeQueryRepository : QueryRepository<Employee>
{
private static int MaxResultsCountPerPage = 5;
public override IQueryResult<Employee> GetAll()
{
return Get(emp => true, null, null);
}
public override Employee Get(int id)
{
return EmployeePersistence.Employees.FirstOrDefault(e => e.Id == id);
}
public override IQueryResult<Employee> Get(int pageSize, int pageIndex)
{
return Get(emp => true, pageSize, pageIndex);
}
public override IQueryResult<Employee> GetByExpression(Expression<Func<Employee, bool>> predicate)
{
return Get(predicate, null, null);
}
public override IQueryResult<Employee> GetByExpression(Expression<Func<Employee, bool>> predicate, int pageSize, int pageIndex)
{
return Get(predicate, pageSize, pageIndex);
}
private static IQueryResult<Employee> Get(Func<Employee, bool> predicate, int? pageSize, int? pageIndex)
{
var filteredItems =
predicate != null ?
EmployeePersistence.Employees.AsQueryable().Where(predicate).ToList() :
EmployeePersistence.Employees;
var finalPageSize = Math.Min(MaxResultsCountPerPage, filteredItems.Count);
var finalPageIndex = 0;
if (pageSize != null)
{
if (pageSize <= MaxResultsCountPerPage)
{
finalPageSize = pageSize.Value;
finalPageIndex = pageIndex ?? 0;
}
else
{
finalPageSize = MaxResultsCountPerPage;
if (pageIndex != null)
{
var oldPagingDescriptor = filteredItems.Page(pageSize.Value);
var oldPageBoundries = oldPagingDescriptor.PagesBoundries[pageIndex.Value];
var targetedItemZeroIndex = oldPageBoundries.FirstItemZeroIndex;
var newPagingDescriptor = filteredItems.Page(finalPageSize);
finalPageIndex =
newPagingDescriptor
.PagesBoundries
.ToList()
.FindIndex(i => i.FirstItemZeroIndex <= targetedItemZeroIndex && i.LastItemZeroIndex >= targetedItemZeroIndex);
}
}
}
var pagingDescriptor = filteredItems.Page(finalPageSize);
var pageBoundries = pagingDescriptor.PagesBoundries[finalPageIndex];
var from = pageBoundries.FirstItemZeroIndex;
var to = pageBoundries.LastItemZeroIndex;
return new QueryResult<Employee>(pagingDescriptor, finalPageIndex, filteredItems.Skip(from).Take(to - from + 1));
}
}
What we can notice here:
EmployeeQueryRepository
class is inheriting from QueryRepository<Employee>
.IQueryRepository
interface as they have been implemented in the abstract base QueryRepository<TEntity>
class.IQueryResult<Employee> Get(Func<Employee, bool> predicate, int? pageSize, int? pageIndex)
method where the magic happens.MaxResultsCountPerPage
and the Page
extension method we implemented on the Paging section.
EmployeeCommandRepository
public class EmployeeCommandRepository : CommandRepository<Employee>
{
public override int Add(Employee entity)
{
var newEmployee = new Employee(entity, EmployeePersistence.Employees.Count);
EmployeePersistence.Employees.Add(newEmployee);
return newEmployee.Id;
}
public override IEnumerable<int> Add(IEnumerable<Employee> entities)
{
return entities.Select(Add).ToList();
}
public override void Update(Employee entity)
{
var foundIndex = EmployeePersistence.Employees.FindIndex(e => e.Id == entity.Id);
if (foundIndex == -1)
{
throw new InvalidOperationException($"Employee with Id \"{entity.Id}\" does not exist.");
}
var foundEmployee = EmployeePersistence.Employees[foundIndex];
var newEmployee = new Employee(entity, foundEmployee.Id);
EmployeePersistence.Employees.RemoveAt(foundIndex);
EmployeePersistence.Employees.Insert(foundIndex, newEmployee);
}
public override void Update(IEnumerable<Employee> entities)
{
foreach (var employee in entities)
{
Update(employee);
}
}
public override IAddOrUpdateDescriptor AddOrUpdate(Employee entity)
{
var foundIndex = EmployeePersistence.Employees.FindIndex(e => e.Id == entity.Id);
if (foundIndex != -1)
{
Update(entity);
return new AddOrUpdateDescriptor(Models.AddOrUpdate.Update, entity.Id);
}
else
{
return new AddOrUpdateDescriptor(Models.AddOrUpdate.Add, Add(entity));
}
}
public override IEnumerable<IAddOrUpdateDescriptor> AddOrUpdate(IEnumerable<Employee> entities)
{
return entities.Select(AddOrUpdate).ToList();
}
public override bool Delete(int id)
{
var foundIndex = EmployeePersistence.Employees.FindIndex(e => e.Id == id);
if (foundIndex == -1) return false;
EmployeePersistence.Employees.RemoveAt(foundIndex);
return true;
}
public override bool Delete(Employee entity)
{
return Delete(entity.Id);
}
public override IDictionary<int, bool> Delete(IEnumerable<Employee> entities)
{
return entities
.ToDictionary(emp => emp.Id, Delete);
}
}
What we can notice here:
EmployeeCommandRepository
class is inheriting from CommandRepository<Employee>
.ICommandRepository
interface as they have been implemented in the abstract base CommandRepository<TEntity>
class.
Now, it is time to test our design. I created a Console Application for quick testing.
Testing would be divided into three parts:
Therefore, we will end up with a Program
class as follows:
using System;
using BetterRepository.Entities;
using BetterRepository.Models;
using BetterRepository.Repositories;
namespace BetterRepository
{
internal class Program
{
private static readonly EmployeeQueryRepository m_EmployeeQueryRepository = new();
private static readonly EmployeeCommandRepository m_EmployeeCommandRepository = new();
static void Main(string[] args)
{
DemonstratingBasicOperation();
DemonstratingWrongCastingChecks();
DemonstratingDealingWithAbstractions();
Console.ReadLine();
}
}
public class Student : Entity
{
}
}
We also defined a Student
class which inherits from Entity and we are going to use it for testing at some point.
Now, we move to the implementation of the DemonstratingBasicOperation()
, DemonstratingWrongCastingChecks()
, and DemonstratingDealingWithAbstractions()
one by one.
public static void DemonstratingBasicOperation()
{
Console.WriteLine("Started DemonstratingBasicOperation");
var result = m_EmployeeQueryRepository.GetAll();
Console.WriteLine(result);
/*
ActualPageZeroIndex: 0
PagingDescriptor:
----------------
ActualPageSize: 5
NumberOfPages: 2
PagesBoundries:
----------------
FirstItemZeroIndex: 0, LastItemZeroIndex: 4
FirstItemZeroIndex: 5, LastItemZeroIndex: 5
Results:
----------------
Id: 0, Name: Ahmed
Id: 1, Name: Tarek
Id: 2, Name: Patrick
Id: 3, Name: Mohamed
Id: 4, Name: Sara
*/
result = m_EmployeeQueryRepository.Get(result.PagingDescriptor.ActualPageSize,
result.ActualPageZeroIndex + 1);
Console.WriteLine(result);
/*
ActualPageZeroIndex: 1
PagingDescriptor:
----------------
ActualPageSize: 5
NumberOfPages: 2
PagesBoundries:
----------------
FirstItemZeroIndex: 0, LastItemZeroIndex: 4
FirstItemZeroIndex: 5, LastItemZeroIndex: 5
Results:
----------------
Id: 5, Name: Ali
*/
result = m_EmployeeQueryRepository.Get(6, 0);
Console.WriteLine(result);
/*
ActualPageZeroIndex: 0
PagingDescriptor:
----------------
ActualPageSize: 5
NumberOfPages: 2
PagesBoundries:
----------------
FirstItemZeroIndex: 0, LastItemZeroIndex: 4
FirstItemZeroIndex: 5, LastItemZeroIndex: 5
Results:
----------------
Id: 0, Name: Ahmed
Id: 1, Name: Tarek
Id: 2, Name: Patrick
Id: 3, Name: Mohamed
Id: 4, Name: Sara
*/
var tarek = m_EmployeeQueryRepository.Get(2);
Console.WriteLine(tarek);
/*
Id: 1, Name: Tarek
*/
result = m_EmployeeQueryRepository.GetByExpression(emp => emp.Name.ToLower().Contains("t"));
Console.WriteLine(result);
/*
ActualPageZeroIndex: 0
PagingDescriptor:
----------------
ActualPageSize: 2
NumberOfPages: 1
PagesBoundries:
----------------
FirstItemZeroIndex: 0, LastItemZeroIndex: 1
Results:
----------------
Id: 1, Name: Tarek
Id: 2, Name: Patrick
*/
var erikId = m_EmployeeCommandRepository.Add(new Employee(0, "Erik"));
Console.WriteLine(erikId);
/*
6
*/
var added = m_EmployeeCommandRepository.Add(new Employee[]
{
new Employee(0, "Hasan"),
new Employee(0, "Mai"),
new Employee(0, "John")
});
Console.WriteLine("");
Console.WriteLine(String.Join("\r\n", added));
/*
7
8
9
*/
m_EmployeeCommandRepository.Update(new Employee(1, "Tarek - Updated"));
var tarekUpdated = m_EmployeeQueryRepository.Get(1);
Console.WriteLine("");
Console.WriteLine(tarekUpdated);
/*
Id: 1, Name: Tarek - Updated
*/
m_EmployeeCommandRepository.AddOrUpdate(new Employee(1, "Tarek - Updated - Updated"));
var tarekUpdatedUpdated = m_EmployeeQueryRepository.Get(1);
Console.WriteLine("");
Console.WriteLine(tarekUpdatedUpdated);
/*
Id: 1, Name: Tarek - Updated - Updated
*/
var deletedTarek = m_EmployeeCommandRepository.Delete(1);
Console.WriteLine("");
Console.WriteLine(deletedTarek);
/*
True
*/
var checkTarek = m_EmployeeQueryRepository.Get(1);
Console.WriteLine("");
Console.WriteLine(checkTarek != null);
/*
False
*/
Console.WriteLine("Finished DemonstratingBasicOperation");
}
We are calling var result = m_EmployeeQueryRepository.GetAll();
followed by Console.WriteLine(result);
and the result is:
ActualPageZeroIndex: 0
PagingDescriptor:
— — — — — — — —
ActualPageSize: 5
NumberOfPages: 2PagesBoundries:
— — — — — — — —
FirstItemZeroIndex: 0, LastItemZeroIndex: 4
FirstItemZeroIndex: 5, LastItemZeroIndex: 5Results:
— — — — — — — —
Id: 0, Name: Ahmed
Id: 1, Name: Tarek
Id: 2, Name: Patrick
Id: 3, Name: Mohamed
Id: 4, Name: Sara
So, the Get All is working fine.
Then we are calling result = m_EmployeeQueryRepository.Get(result.PagingDescriptor.ActualPageSize, result.ActualPageZeroIndex + 1);
followed by Console.WriteLine(result);
and the result is:
ActualPageZeroIndex: 1
PagingDescriptor:
— — — — — — — —
ActualPageSize: 5
NumberOfPages: 2PagesBoundries:
— — — — — — — —
FirstItemZeroIndex: 0, LastItemZeroIndex: 4
FirstItemZeroIndex: 5, LastItemZeroIndex: 5Results:
— — — — — — — —
Id: 5, Name: Ali
So, the Get With Paging is working fine.
Then we are calling result = m_EmployeeQueryRepository.Get(6, 0);
followed by Console.WriteLine(result);
and the result is:
ActualPageZeroIndex: 0
PagingDescriptor:
— — — — — — — —
ActualPageSize: 5
NumberOfPages: 2PagesBoundries:
— — — — — — — —
FirstItemZeroIndex: 0, LastItemZeroIndex: 4
FirstItemZeroIndex: 5, LastItemZeroIndex: 5Results:
— — — — — — — —
Id: 0, Name: Ahmed
Id: 1, Name: Tarek
Id: 2, Name: Patrick
Id: 3, Name: Mohamed
Id: 4, Name: Sara
Here we need to keep in mind the applied throttling restriction of 5 items. Analyzing this, we would get convinced that the Get With Paging is working fine.
Then we are calling var tarek = m_EmployeeQueryRepository.Get(2);
followed by Console.WriteLine(tarek);
and the result is:
Id: 1, Name: Tarek
So, the Get By Id is working fine.
Then we are calling result = m_EmployeeQueryRepository.Get(emp => emp.Name.ToLower().Contains(“t”));
followed by Console.WriteLine(result);
and the result is:
ActualPageZeroIndex: 0
PagingDescriptor:
— — — — — — — —
ActualPageSize: 2
NumberOfPages: 1PagesBoundries:
— — — — — — — —
FirstItemZeroIndex: 0, LastItemZeroIndex: 1Results:
— — — — — — — —
Id: 1, Name: Tarek
Id: 2, Name: Patrick
So, the Get With Predicate Filter is working fine.
Then we are calling var erikId = m_EmployeeCommandRepository.Add(new Employee(0, “Erik”));
followed by Console.WriteLine(erikId);
and the result is:
6
So, the Add is working fine.
Then we are calling
var added = m_EmployeeCommandRepository.Add(new []
{ new Employee(0, “Hasan”), new Employee(0, “Mai”), new Employee(0, “John”) });
followed by Console.WriteLine(String.Join(“\r\n”, added));
and the result is:
7
8
9
So, the Add Collection is working fine.
Then we are calling
m_EmployeeCommandRepository.Update(new Employee(1, “Tarek — Updated”));
var tarekUpdated = m_EmployeeQueryRepository.Get(1);
followed by Console.WriteLine(tarekUpdated);
and the result is:
Id: 1, Name: Tarek — Updated
So, the Update is working fine.
Then we are calling
m_EmployeeCommandRepository.AddOrUpdate(new Employee(1, “Tarek — Updated — Updated”));
var tarekUpdatedUpdated = m_EmployeeQueryRepository.Get(1);
followed by Console.WriteLine(tarekUpdatedUpdated);
and the result is:
Id: 1, Name: Tarek — Updated — Updated
So, the Add Or Update is working fine.
Then we are calling var deletedTarek = m_EmployeeCommandRepository.Delete(1);
followed by Console.WriteLine(deletedTarek);
and the result is:
True
And calling var checkTarek = m_EmployeeQueryRepository.Get(1);
followed by Console.WriteLine(checkTarek != null);
and the result is:
False
So, the Delete is working fine.
public static void DemonstratingWrongCastingChecks()
{
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Started DemonstratingWrongCastingChecks");
var queryRepository = m_EmployeeQueryRepository as IQueryRepository;
var commandRepository = m_EmployeeCommandRepository as ICommandRepository;
try
{
commandRepository.Add(new Student());
}
catch (Exception e)
{
Console.WriteLine(e.Message);
/*
System.ArgumentException: 'The type "BetterRepository.Student" does not match the type
"BetterRepository.Entities.Employee"'
*/
}
try
{
commandRepository.Add(new Student[]
{
new Student(),
new Student()
});
}
catch (Exception e)
{
Console.WriteLine(e.Message);
/*
System.ArgumentException: The type "BetterRepository.Student[]" does not match the type
"System.Collections.Generic.IEnumerable`1[BetterRepository.Entities.Employee]"
*/
}
try
{
bool FilterStudents(object obj)
{
return true;
}
// Compiler would not allow it as Func<Entity, bool> predicate is contravariant
// This means that for the predicate parameter, you can only provide Func<Entity, bool>
// or Func<Parent of Entity, bool>. In our case, the only available parent of Entity is
// the Object class.
//queryRepository.Get(FilterStudents);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.WriteLine("Finished DemonstratingWrongCastingChecks");
}
We are defining the local variable queryRepository
which is of type IQueryRepository
by casting m_EmployeeQueryRepository
.
var queryRepository = m_EmployeeQueryRepository as IQueryRepository;
And defining the local variable commandRepository
which is of type ICommandRepository
by casting m_EmployeeCommandRepository
.
var commandRepository = m_EmployeeCommandRepository as ICommandRepository;
Then when trying to execute commandRepository.Add(new Student());
it fails with the exception System.ArgumentException: ‘The type “BetterRepository.Student” does not match the type “BetterRepository.Entities.Employee”’
.
Then when trying to execute commandRepository.Add(new Student[] { new Student(), new Student() });
it fails with the exception System.ArgumentException: The type “BetterRepository.Student[]” does not match the type “System.Collections.Generic.IEnumerable`1[BetterRepository.Entities.Employee]”
.
So, the Wrong Casting Check is working fine.
public static void DemonstratingDealingWithAbstractions()
{
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Started DemonstratingDealingWithAbstractions");
// Resetting the employees collection
EmployeePersistence.Reset();
var queryRepository = m_EmployeeQueryRepository as IQueryRepository;
var commandRepository = m_EmployeeCommandRepository as ICommandRepository;
// Getting first two Employees when we actually don't know their type
// and we don't care about their type
var firstTwoItems = queryRepository.Get(2, 0);
Console.WriteLine(firstTwoItems);
/*
Id: 0, Name: Ahmed
Id: 1, Name: Tarek
*/
// Now we are deleting the first two items blindly when we don't know their type
// and we don't care about their type
commandRepository.Delete(firstTwoItems.Results);
// Now we get the first two Employees again to check if it worked
firstTwoItems = queryRepository.Get(2, 0);
Console.WriteLine(firstTwoItems);
/*
Id: 2, Name: Patrick
Id: 3, Name: Mohamed
*/
Console.WriteLine("Finished DemonstratingDealingWithAbstractions");
}
First, we are resetting the Employees list EmployeePersistence.Reset();
.
Then, we are defining the local variable queryRepository
which is of type IQueryRepository
by casting m_EmployeeQueryRepository
.
var queryRepository = m_EmployeeQueryRepository as IQueryRepository;
And defining the local variable commandRepository
which is of type ICommandRepository
by casting m_EmployeeCommandRepository
.
var commandRepository = m_EmployeeCommandRepository as ICommandRepository;
Then we execute:
/ Getting first two Employees when we actually don’t know their type
// and we don’t care about their type
var firstTwoItems = queryRepository.Get(2, 0);
Console.WriteLine(firstTwoItems);
And the result is:
Id: 0, Name: Ahmed
Id: 1, Name: Tarek
And then we execute:
// Now we are deleting the first two items blindly when we don’t know their type
// and we don’t care about their type
commandRepository.Delete(firstTwoItems.Results);// Now we get the first two Employees again to check if it worked
firstTwoItems = queryRepository.Get(2, 0);
Console.WriteLine(firstTwoItems);
And the result is:
Id: 2, Name: Patrick
Id: 3, Name: Mohamed
So, Dealing With Abstractions is working fine.
Wow, a long trip. Now, let’s take a break, relax, and take a step back to see the big picture.
What we implemented here is not rocket science. The concepts are not that hard, it is only a matter of having steady hands while implementing them.
That’s it, hope you found reading this article as interesting as I found writing it.
Also Published Here