Conventions

In this section you will learn about the conventions in Gazel. Keep in mind that Gazel heavily relies on Convention over Configuration design paradigm.

Dependency Injection

Gazel uses Castle.Windsor for Dependency Injection. Using Convention over Configuration capabilities of Castle.Windsor, you don't have to register your classes one by one. Every public class in module projects are automatically registered to the kernel. In this document you will find ways on how to make use of this feature.

Scopes

There are 3 types of scope;

  1. Transient: For every resolution, Windsor creates a new instance.
  2. Request: For every request there is only one instance. After the request ends, object is disposed.
  3. Singleton: There is only one instance until application is shut down.

Manager and Query classes are singleton by convention and the rest are transient.

Manager classes

For a class to be a manager class, it should have Manager suffix.

public class ProductManager
{
...
}

Manager classes are singleton by convention. You can make use of Manager classes to provide general domain logic of a module, batch operations or complex query services.

Injecting a Dependency

Every public class is registered to Castle.Windsor by their own type so that they can be injected to other classes.

public class ProductManager
{
private readonly UserManager _userManager;
public ProductManager(UserManager userManager)
{
_userManager = userManager;
}
...
}
public class UserManager { ... }

Public Constructors

Every public class should contain a public constructor, in order for Castle.Windsor to register and configure them.

public class ProductManager
{
...
public ProductManager()
{
...
}
...
}

Circular Dependencies

Avoid creating circular dependencies as shown below;

public class ProductManager
{
...
public ProductManager(UserManager userManager)
{
...
}
...
}
public class UserManager
{
...
public UserManager(ProductManager userManager)
{
...
}
...
}

For such cases;

  1. Consider refactoring your code. Two classes shouldn't depend on each other.
  2. If you think you have no other choice, then you can refactor code as shown below;
public class UserManager
{
private readonly IModuleContext _context;
public UserManager(IModuleContext context)
{
_context = context;
}
private ProductManager ProductManager
{
get
{
return _context.Resolve(typeof(ProductManager), Scope.Any);
}
}
...
}

IModuleContext interface

IModuleContext is basically an abstraction for your domain objects to be isolated from the 3rd party libraries.

For singleton objects, mainly Manager objects, we encourage you to inject them through constructor. On the other hand, if you want to create a transient object, you can make use of IModuleContext.New<T>().

public class ProductManager
{
private readonly IModuleContext _context;
public ProductManager(IModuleContext context)
{
_context = context;
}
public void DoSomethingHeavy()
{
_context.New<BatchUserOperation>().DoSomethingHeavy();
}
...
}
public class BatchUserOperation
{
private readonly UserManager _userManager;
public BatchUserOperation(UserManager userManager)
{
_userManager = userManager;
}
internal void DoSomethingHeavy() { ... }
}

Injecting Interfaces

Public classes can also be injected using their interfaces

public class ProductManager
{
private readonly IUserManager _userManager;
public ProductManager(IUserManager userManager)
{
_userManager = userManager;
}
...
}
public interface IUserManager { ... }
public class UserManager : IUserManager { ... }

Injecting Multiple Implementations

If an interface has more than one implementations then you can inject all of them at once by using IList<T>.

public class ProductManager
{
private IList<INotifier> _notifiers;
public ProductManager(IList<INotifier> notifiers)
{
_notifiers = notifiers;
}
...
}
public interface INotifier { ... }
public class SmsNotifier : INotifier { ... }
public class MailNotifier : INotifier { ... }
public class PushNotifier : INotifier { ... }

Business Services

Gazel configures Routine to expose every public method to be a business service. A business service is a public method that you can call using HTTP.

Public Methods

public methods are directly exposed as business services.

public class Company
{
...
public virtual List<Branch> GetBranches()
{
return _context.Query<Branches>().ByCompany(this);
}
...
}

Persistent Objects as Parameters

You can use a persistent object directly in your business methods. Gazel will automatically lookup for a record with given id. If record is not found, it will automatically throw ObjectNotFoundException.

public class Company
{
...
public virtual Branch AddBranch(string name, District district)
{
return _context.New<Branch>().With(this, name, district);
}
...
}

Therefore, with the above code, you ensured that district is either null or an existing record in database.

Method Overloads

Method overloads are considered as one business service in service layer. If you create method overloads like below, then you will see only one business service with all of parameters.

public class Company
{
...
public virtual Branch AddBranch(string name, District district) => AddBranch(name, district, null);
public virtual Branch AddBranch(string name, District district, Company ownerCompany)
{
...
}
...
}

If you don't send an ownerCompany then the first overload will be called, if you fulfill all parameters, then the second overload will be called.

Overload selection is automatic, and Gazel will try to pass as many parameters as it can. Assume that you send name and ownerCompany to above AddBranch service. Then first overload matches only name parameter, but second overload matches both of them. So invocation will be on the second one and district parameter will be null.

If return type of overloads are not the same, the first overload will be a business service, but the second one will be ignored.

public class Company
{
...
// First overload returns void
public virtual void AddBranch(string name, District district) { ... }
// Second overload returns Branch, this will not be a business service.
public virtual Branch AddBranch(string name, District district, Company ownerCompany) { ... }
...
}

Optional parameters

Gazel supports optional parameters in business services. You can use optional parameters instead of overloads.

public class Company
{
...
public virtual Branch AddBranch(string name, District district,
Company ownerCompany = null
)
{
...
}
...
}

List and array parameters and return types

You can use List class for both parameters and return types as long as type parameter T for lists is a supported type.

public class Company
{
...
public virtual List<Branch> GetBranches() { ... }
public virtual Employee[] GetEmployees() { ... }
...
}

No Dictionary parameters and return types

Gazel does not support Dictionary in business services by design. So any service with Dictionary parameter or return type will not be exposed as a business service. The reason behind this design decision is to favor strongly-typed classes over generic dictionaries.

Data Transfer Objects (DTOs)

If you need a type other than persistent class or a primitive for input or output, then you can make use of DTOs.

public class Company
{
...
public virtual void AddBranch(string name, string address, City city, Country country) { ... }
...
}

Example usage of a DTO with business service;

public class Company
{
...
public virtual void AddBranches(List<BranchInfo> branches) { ... }
...
}

By definition DTOs are expected to be only used for data transfer with no business logic and for this reason Gazel uses record.

You can define a record with using positional syntax. With this syntax, compiler generates your record as a class with init only public properties and a public constructor.

public record BranchInfo(string Name, string Address, City City, Country Country);

Constructor parameters of record DTOs are just like parameters of business services. You can use

  • primitives and other value types,
  • persistent classes,
  • other DTOs as parameters of constructor.

You can use constructor overloads if you need some construction logic or validation when creating your DTOs.

Example usage of a record with a constructor overloads.

public record BranchInfo(string Name, string Address, City City, Country Country)
{
public Geoloc Location { get; init; }
public BranchInfo(string name, Geoloc location)
: this(name, default, default, default)
{
Location = location;
}
}

You can use optional or calculated properties in records, without adding a constructor overload.

public record BranchInfo(string Name, City City, Country Country, string Address = default)
{
...
public string FullAddress => $"{Name} - {Address} {City} {Country}";
....
}

If you need to keep some properties or methods only for internal business logic, you can use internal access modifier or [Internal] attribute.

public record BranchInfo(string Name, [property: Internal] string Address,...)
{
// this constructor will not be rendered in client APIs,
// it will only be available for your business services
internal BranchInfo(Branch branch)
: this(branch.Name, branch.Address, branch.City, branch.Country)
{ }
public string FullAddress => $"{Name} - {Address} {City} {Country}";
}

Example usage of record with internal constructor with business service;

public class Company
{
...
public virtual List<BranchInfo> GetBranches()
{
return _context
.Query<Branches>()
.ByCompany(this)
.Select(b => new BranchInfo(b))
.ToList()
;
}
...
}

Legacy

struct DTOs are currently supported as legacy for backward compatibility and may not be available on future releases. We recommend you to use record over struct in your new projects.

Below is a struct implementation of BranchInfo record.

public record BranchInfo(string Name, string Address, City City, Country Country);
public struct BranchInfo
{
public string Name { get; private set; }
public string Address { get; private set; }
public City City { get; private set; }
public Country Country { get; private set; }
public BranchInfo(string name, string address, City city, Country country)
: this()
{
Name = name;
Address = address;
City = city;
Country = country;
}
}

Hiding Public Methods and Properties

In some cases you might need to have a public method, but you might not want it to be exposed as a business service. In this kind of cases, you can mark a business service by adding an Internal attribute on top of a class, constructor, method or property like below;

[Internal]
public class InternalManager
{
public PublicData ShouldNotBeRegisteredAsAService()
{
return new PublicData("public internal", "public");
}
}
public class PublicManager
{
private readonly InternalManager _internalManager;
public PublicManager(InternalManager internalManager)
{
_internalManager = internalManager;
}
public PublicData PublicService()
{
return _internalManager.ShouldNotBeRegisteredAsAService();
}
[Internal]
public void InternalService()
{
}
}
public record PublicData([property: Internal] string InternalProperty, string PublicProperty);

For the above example;

  • InternalManager class and all of its members will be hidden in service layer.
  • PublicManager.InternalService method will be hidden in service layer.
  • For PublicData class;
    • The constructor will be hidden,
    • InternalProperty will be hidden,
    • But PublicProperty will be available in service layer

You can think of this attribute as an additional access modifier to public, private, internal, protected and protected internal access modifier which we sometimes refer as public internal.

Data Persistence

Gazel configures NHibernate with the help of Fluent NHibernate to provide a data persistence layer.

Persistent Class Conventions

An object is Persistent if its class injects its own repository. This convention ensures that there is only one class to deal with one table in the database.

public class Company
{
private readonly IRepository<Company> _repository;
protected Company() { }
public Company(IRepository<Company> repository)
{
_repository = repository;
}
...
}

This injection tells Gazel to configure this class to be a persistent class which means that there will be an ORM configuration for this class.

protected Task() { } constructor is there for NHibernate to be able to create proxies on persistent classes for lazy loading.

Query classes

When you have a persistent class, this means that you will need to read persistent objects from corresponding database table. That's why Gazel requires you to create a query class for every persistent class.

public class Company
{
...
}
public class Companies : Query<Company>
{
...
}

It's better to put persistent and query classes of a table into one source file.

In this document we will explain persistent classes rather than query classes. You can find detailed explanation for queries in this page .

Id property

Every persistent class should have an identifier property of type int and of name Id.

public class Company
{
...
public virtual int Id { get; protected set; }
...
}

This property will be used by NHibernate to be the primary key and first level caching.

Id properties are automatically configured to be an identity column which means that database is responsible for assigning Id values.

virtual keyword

Every member in persistent classes (methods and properties) should be virtual in order NHibernate to be able to create proxies for lazy loading.

public class Company
{
...
public virtual int Id { get; protected set; }
public virtual string Name { get; protected set; }
public virtual string Address { get; protected set; }
...
}

In persistent classes private access modifier causes null reference exceptions. Use protected virtual instead of private to workaround this problem.

protected setters

We encourage you to use protected setters so that you can make sure that no other class than the class itself is able to modify the values of its properties.

It could be private setters but NHibernate wouldn't be able to create proxies for lazy loading.

For a more detailed explanation please have a look at this tutorial page .

Inserting a New Record

There is an Insert method in IRepository<T> which does what it says. The convention for insert operations is as follows;

public class Company
{
...
protected internal virtual Company With(string name, string address)
{
Name = name;
Address = address;
_repository.Insert(this);
return this;
}
...
}

With methods are part of the creation of a persistent object. We use builder pattern for this operation. For a more detailed explanation about With methods please have a look at this tutorial page .

With methods are;

  • protected and virtual because of NHibernate's lazy loading requirements,
  • internal to be able to use from other classes in its module.

Below you can see an example of an insert operation;

public class CompanyManager
{
...
public void CreateCompany(string name, string address)
{
_context.New<Company>().With(name, address);
}
...
}

Updating a Record

When a persistent object is loaded, its state is managed by NHibernate. This feature of NHibernate enables services to automatically update a record upon commit.

public class Company
{
...
public virtual void Update(string name, string address)
{
Name = name;
Address = address;
}
...
}

Above method is enough for the Company object to update itself. At this point object is marked as dirty by NHibernate. Whenever NHibernate session is flushed, an update statement will be executed in the database. There are 3 reasons for NHibernate session to be flushed;

  1. When current transaction is committed NHibernate flushes current session, hence record gets updated.
  2. When a select query is sent to Company table, either directly or via table joins.
  3. You make an explicit call to IRepository<T>.Flush() which calls NHibernate's session flush directly.

IRepository<T>.Flush() causes all dirty objects to be flushed, not just the persistent object it is called, nor instances of <T>. This is how NHibernate implements session flush.

Batch Updates

public class Company
{
...
public virtual bool Active { get; protected set; }
...
public virtual void Deactivate()
{
Active = false;
}
}
public class CompanyManager
{
...
public virtual void DeactivateCompanies(string name)
{
foreach(var company in _context.Query<Companies>().ByName(name))
{
company.Deactivate();
}
}
...
}

DeactivateCompanies method iterates through a list of Company objects. Changing a property value does not cause an immediate update. Upon NHibernate session flush, above updates will be executed as batch.

If you execute a query to Company table inside Deactivate method, this will cause NHibernate to flush on every iteration and cause an update execution one by one. This can create a performance flaw in your code. If you need to check something before such an update (e.g. checking if new username exists before updating it), and if you want the operation to be a batch update, then you need to optimize your query accordingly. For "unique username" example, you might consider checking uniqueness at once.

public class User
{
...
public virtual bool Username { get; protected set; }
...
public virtual void ChangeUsername(string username) { ChangeUsername(username, false); }
protected internal virtual void ChangeUsername(string username, bool batch)
{
if(!batch)
{
if(_context.Query<Users>().CountByUsername(username) > 0)
{
throw new ArgumentException("username");
}
}
Username = username;
}
...
}
public class UserManager
{
...
public virtual void ChangeUsernames(List<UsernameChange> changes)
{
if(_context.Query<Users>().CountByUsernames(changes.Select(c => c.To)) > 0)
{
throw new ArgumentException("changes");
}
var users = _context.Query<Users>().ByUsernames(changes.Select(c => c.From));
foreach(var user in users)
{
users.ChangeUsername(change.To, true);
}
}
...
}

Force Update

If a persistent class implements IAuditable, this means that with every update to objects of that class, Gazel will automatically update values of AuditInfo properties. A force update is handy when you only want to update AuditInfo properties (e.g. ModifyDate). You can force an object to be updated using ForceUpdate method of IRepository<T>.

public class Company : IAuditable
{
...
public virtual AuditInfo AuditInfo { get; protected set; }
...
public virtual void AddEmployee(string name)
{
_context.New<Employee>().With(this, name);
_repository.ForceUpdate(this);
}
...
}
public class Companies : Query<Company> { ... }

In the above example, we want a company record to be updated whenever an employee is added to it. Since adding an employee does not cause an update to a company record, we force company objects to be updated upon adding an employee. This ForceUpdate call will cause a company object's AuditInfo.ModifyDate column to be refreshed with AddEmployee operation.

If given persistent class does not implement IAuditable interface, an InvalidOperationException will be thrown.

If a persistent object is already dirty and forced to be updated, there will be only one UPDATE statement to update changes to database.

Deleting a Record

Deleting is done by Delete method in IRepository<T>.

public class Company
{
...
public virtual void Delete()
{
_repository.Delete(this);
}
...
}

Batch Deletes

Behaviour of delete operations are similar to update operations. If you do it like above, delete statements will be executed when NHibernate session is flushed. If you execute a query before deleting, this would prevent NHibernate from performing a batch operation. For detailed explanation have a look at Batch Updates section above in this document.

Relations

Gazel configures NHibernate so that you can define relations intiutively.

Many-to-One Relation

This relation is the most used type among all and the simplest one.

public class Company
{
...
public virtual int Id { get; protected set; }
public virtual string Name { get; protected set; }
...
}
public class Employee
{
...
public virtual int Id { get; protected set; }
public virtual Company Company { get; protected set; }
public virtual string Name { get; protected set; }
...
}

Above code is enough to set a many-to-one relation between Employee and Company classes. In this example, Employee table will require a column named CompanyId that is mapped to Company property as a many to one relation.

One-to-Many Relations

Creating List<T> properties on persistent classes will NOT cause a one-to-many relation. To have this relation, simply create a method that queries child records from the parent record;

public class Company
{
...
public virtual List<Employee> GetEmployees()
{
return _context.Query<Employees>().ByCompany(this);
}
...
}
public class Employees : Query<Employee>
{
...
internal List<Employee> ByCompany(Company company)
{
return By(e => e.Company == company);
}
...
}

Eager-Fetching and Lazy-Loading

Gazel configures NHibernate to eager fetch persistent object with one level. This means that when you query a list of persistent objects, their parents will be fetched eagerly using an inner join. But their grandparents will be lazy-loaded.

public class Company
{
...
public virtual string Name { get; protected set; }
...
}
public class Department
{
...
public virtual string Name { get; protected set; }
public virtual Company Company { get; protected set; }
...
}
public class Employee
{
...
public virtual string Name { get; protected set; }
public virtual Department Department { get; protected set; }
...
}

When you query employees with something like _context.Query<Employees>().ByName("mike"), the resulting Employee objects will have Department objects eagerly fetched. However, when you try to access a property of their Company object (other than Id property), a query will be executed using that Company object's primary key.

public class CompanyManager
{
...
public void SomeEmployeeOperation(string name)
{
var employees = _context.Query<Employees>().ByName(name);
foreach(var employee in employees)
{
var departmentName = employee.Department.Name; //No query is executed, department is already loaded
var companyName = employee.Department.Company.Name; //A query is executed to load company object from database
}
}
...
}

About N + 1 select problem

Above code causes N+1 select problem, which can be a performance issue. If you encounter this problem, you need to consider selecting all grandparents (Company) of the children (Employee) with an additional query so that there will be only 2 queries in total.

public class CompanyManager
{
...
public void SomeEmployeeOperation(string name)
{
var employees = _context.Query<Employees>().ByName(name);
var companies = _context.Query<Companies>().ByIds(employees.Select(e => e.Department.Company.Id));
foreach(var employee in employees)
{
var departmentName = employee.Department.Name; //No query is executed, department is already loaded
var companyName = employee.Department.Company.Name; //No query is executed, company is already loaded with the second query.
}
}
...
}

One-to-Any Relation

This relation type enables you to map your properties to interfaces, which we refer to as Interface Mapping or Polymorphic Mapping.

public class Order
{
...
public virtual ICustomer Customer { get; protected set; }
...
}

For NHibernate to map this property to a table it needs two columns;

  1. CustomerId: The id value of related record, like in a Many-to-One relation,
  2. CustomerType: Type of related record.

NHibernate uses type column to know which table to select. This type information is retrieved from an enum that corresponds to IOrderProcessor interface.

public interface ICustomer
{
}
public enum CustomerType
{
RealPerson = 1,
LegalEntity = 2
}
public class RealPerson : ICustomer { ... }
public class LegalEntity : ICustomer { ... }

Conventions for one-to-any mapping are;

  • For an interface named I[Name] (e.g. ICustomer)
  • There should be an enum named [Name]Type (e.g. CustomerType)
  • Each enum member must be a persistent class and implement I[Name] interface (e.g. RealPerson and LegalEntity).
  • When I[Name] is mapped to a persistent class with a property named [Property],
    • [Property]Id (CustomerId) column stores the id value of the related record
    • [Property]Type (CustomerType) column store enum member values (1 or 2), not member names (RealPerson or LegalEntity)

In a query method, you can filter by object like this;

public class Orders : Query<Order>
{
...
internal List<Order> ByCustomer(ICustomer customer)
{
return By(wa => wa.Customer == customer); //uses both CustomerId and CustomerType columns
}
...
}

Or you can filter by type like this;

public class Orders : Query<Order>
{
...
internal List<Order> ByCustomerType<T>() where T : ICustomer
{
return By(wa => wa.Customer is T); //uses only Customer column
}
...
}

Unfortunately interface mappings cannot be fetched eagerly. So beware of N+1 select problems if you use this feature.

Queries

For persistence classes, there needs to be corresponding query class to read records from database. This section focuses on how you can organize your queries in your projects.

Query Class Conventions

The main purpose of query classes is to organize all of the queries of a table together into one place. Query classes are named after their corresponding persistent class. They are in plural form like below;

public class Company { ... }
public class Companies : Query<Company>
{
...
}

You can access pluralization service through IModuleContext.Pluralizer property.

If pluralization service does not provide what you want, you can make use of [Name]s convention. For instance, there is a persistent class named Xyz, when you name its query class as Xyzs, it will work as well.

Query classes are singleton by convention and its usage is as follows;

public class CompanyManager
{
...
public virtual void DeactivateCompanies(string name)
{
foreach(var company in _context.Query<Companies>().ByName(name))
{
company.Deactivate();
}
}
...
}

Query classes are singleton by convention, so you can inject query classes as well;

public class CompanyManager
{
private Companies _companies;
public CompanyManager(Companies companies) => _companies = companies;
public virtual void DeactivateCompanies(string name)
{
foreach(var company in _companies.ByName(name))
{
company.Deactivate();
}
}
}

Query classes should extend Query<T> which is an abstract class with helper functionalities to make it simple to implement query methods. To provide these functionalities it requires IModuleContext to be injected.

public class Company { ... }
public class Companies : Query<Company>
{
public Companies(IModuleContext context) : base(context) { }
...
}

If you want to inject other dependencies, you are free to do it like in any other class.

By Methods

By methods are type of queries that return list of persistent objects.

public class Company { ... }
public class Companies : Query<Company>
{
...
public List<Company> By(string name, City city) =>
By(c => c.Name.Contains(name) && c.City == city);
...
}

By method is declared in Query<T> base class to help you create query methods quickly.

Query<T>.By method accepts an expression that is converted to SQL. This expression never runs in your .NET application. Because of this reason you are not supposed to call methods of persistent classes within these expressions. For example; By(c => c.GetEmployees().Count > 0) will not work.

Do not use Id properties for filtering;

public class Company { ... }
public class Companies : Query<Company>
{
...
public List<Company> By(string name, City city) =>
By(c => c.Name.Contains(name) && c.City.Id == city.Id);
...
}

This will cause a NullReferenceException when city is null. Prefer c.City == city expression, which will handle both cases in one shot.

A by method can also be implemented like below;

public class Company { ... }
public class Companies : Query<Company>
{
...
public List<Company> By(string name, City city) =>
Lookup.List(true).Where(c => c.Name.Contains(name) && c.City == city).ToList();
...
}

Lookup property is declared in Query<T> base class and is of type ILookup<T>. This interface acts as a gateway to NHibernate. ILookup<T>.List method returns an IQueryable<T> instance. You may use this instance when By method is not enough.

Single Parameter Convention

When there is only one parameter in query methods, we suggest you to include parameter name in method name like below;

public class Transaction { ... }
public class Transactions : Query<Transaction>
{
...
public List<Transaction> ByFrom(Account from) => By(t => t.From == from);
...
}

Consider you have two different queries on Transaction table, first one filters using From column, second one filters using To column;

public class Transaction { ... }
public class Transactions : Query<Transaction>
{
...
public List<Transaction> By(Account from) => By(t => t.From == from);
public List<Transaction> By(Account to) => By(t => t.To == to);
...
}

Above code will not compile because there are two methods with exactly the same signature. To make it compile, you need to rename one of them. We prefer renaming both to provide consistency in naming;

public class Transaction { ... }
public class Transactions : Query<Transaction>
{
...
public List<Transaction> ByFrom(Account from) => By(t => t.From == from);
public List<Transaction> ByTo(Account to) => By(t => t.To == to);
...
}

Take and Skip

You can use skip and take extensions methods. They are optional parameters. take parameter extracts the first n elements from the beginning of the target sequence. Here is how you can do it;

public class Company { ... }
public class Companies : Query<Company>
{
...
public List<Company> By(string name, City city, int take) =>
By(c => c.Name.Contains(name) && c.City == city, take: take);
public List<Company> All(string name, City city, int take) =>
All(c => c.Name.Contains(name) && c.City == city, take: take);
...
}

The skip parameter moves pass the first n elements from the beginning of the target sequence, returning the remainder;

public class Company { ... }
public class Companies : Query<Company>
{
...
public List<Company> By(string name, City city, int skip) =>
By(c => c.Name.Contains(name) && c.City == city, skip: skip);
public List<Company> All(string name, City city, int skip) =>
All(c => c.Name.Contains(name) && c.City == city, skip: skip);
...
}

You can apply pagination by using skip and take optional parameters. Here is how you can do it;

public class Company { ... }
public class Companies : Query<Company>
{
...
public List<Company> By(string name, int skip, int take) =>
By(c => c.Name.Contains(name), skip: skip, take:take);
public List<Company> All(string name, int skip, int take) =>
All(c => c.Name.Contains(name), skip: skip, take:take);
...
}

Alternatively, you can also be implemented by using ILookup<T>.List as shown below. It actually returns an IQueryable<T> instance.

public class Company { ... }
public class Companies : Query<Company>
{
...
public List<Company> By(string name, City city, int take) =>
Lookup
.List(true)
.Where(c => c.Name.Contains(name) && c.City == city)
.Take(take)
.ToList();;
public List<Company> By(string name, City city, int skip) =>
Lookup
.List(true)
.Where(c => c.Name.Contains(name) && c.City == city)
.Skip(skip)
.ToList();;
public List<Company> By(string name, City city, int take, int skip) =>
Lookup
.List(true)
.Where(c => c.Name.Contains(name) && c.City == city)
.OrderByDescending(c => c.City)
.Skip(skip)
.Take(take)
.ToList();
...
}

OrderBy and OrderByDescending

You can use orderBy and orderByDescending extensions methods. They are optional parameters.

You can use orderBy and orderByDescending parameters in By, All, FirstBy, methods.

The orderby parameter sorts the elements of a sequence in ascending order according to a key.

public class Company { ... }
public class Companies : Query<Company>
{
...
public List<Company> ByName(string name) =>
By(c => c.Name.StartsWith(name), orderBy: c => c.Name);
public List<Company> All() =>
All(orderBy: c => c.Name);
public List<Company> FirstByName(string name) =>
FirstBy(c => c.Name.StartsWith(name), orderBy: c => c.Name);
...
}

The orderByDescending parameter sorts the elements of a sequence in descending order according to a key.

public class Company { ... }
public class Companies : Query<Company>
{
...
public List<Company> ByName(string name) =>
By(c => c.Name.StartsWith(name), orderByDescending: c => c.Name);
public List<Company> All(string name) =>
All(orderByDescending: c => c.Name);
public List<Company> FirstByName(string name) =>
FirstBy(c => c.Name.StartsWith(name), orderByDescending: c => c.Name);
...
}

FirstBy and SingleBy

FirstBy and SingleBy methods are like By methods but they return only one record. FirstBy returns the first record matching the given conditions, whereas SingleBy throws an exception when there are more than one records matching the given conditions.

public class User { ... }
public class Users : Query<User>
{
...
public User SingleByEmail(Email email) =>
SingleBy(u => u.Email == email);
public User FirstByRegistrationDate(Date registrationDate) =>
FirstBy(u => u.RegistrationDate == registrationDate);
...
}

Single Parameter Convention applies to all query methods. That's why above methods are named as SingleByEmail and FirstByRegistrationDate instead of SingleBy and FirstBy. When you have more than one parameter you may exclude parameter name from method name (e.g. public User SingleBy(Email email, Password password)).

SingleBy method will return null when there are no matching records. LINQ extension methods uses a different convention. Single methods expects to return one record, if there are none or more than one they will throw an exception. SingleBy methods acts like SingleOrDefault method.

CountBy

As the name implies, CountBy methods executes a count query and returns an int.

public class User { ... }
public class Users : Query<User>
{
...
public int CountByRegistrationDate(Date registrationDate) =>
CountBy(u => u.RegistrationDate == registrationDate);
public int CountBy(Gender gender, Date birthDate) =>
CountBy(u => u.Gender == gender && u.BirthDate == birthDate);
...
}

AnyBy

As the name implies, AnyBy method determines whether all elements of a sequence satisfy a condition and returns a bool.

public class User { ... }
public class Users : Query<User>
{
...
public bool AnyByRegistrationDate(Date registrationDate) =>
AnyBy(u => u.RegistrationDate == registrationDate);
public bool AnyBy(Gender gender, Date birthDate) =>
AnyBy(u => u.Gender == gender && u.BirthDate == birthDate);
...
}

MinBy and MaxBy

These aggregate functions take two expressions.

  1. An expression of the property on which aggregate function is applied
  2. An expression for where clause
public class Transaction { ... }
public class Transactions : Query<Transaction>
{
...
public Money MinAmountBy(DateRange transactionDateRange, CurrencyCode currency) =>
MinBy(
//Property expression
t => t.Amount.Value,
//Where clause expression
t => t.TransactionDate >= transactionDateRange.Start &&
t.TransactionDate < transactionDateRange.End &&
t.Amount.Currency == currency
).ToMoney(currency);
public Money MaxAmountBy(DateRange transactionDateRange, CurrencyCode currency) =>
MaxBy(
//Property expression
t => t.Amount.Value,
//Where clause expression
t => t.TransactionDate >= transactionDateRange.Start &&
t.TransactionDate < transactionDateRange.End &&
t.Amount.Currency == currency
).ToMoney(currency);
...
}

Optional Where Clauses

In a query class, if a condition needs to be included in a query depending on the state of a given parameter, an optional where clause can be created as shown below;

public List<Company> By(City city, string name = default, Vkn taxNo = default)
{
return By(c => c.City == city,
When(name).IsNot(default).ThenAnd(c => c.Name.StartsWith(name)),
When(taxNo).IsNot(default).ThenAnd(c => c.TaxNo == taxNo)
);
}

This way you can create reusable query services/methods;

companies.By(city);
companies.By(city, taxNo: taxNo);
companies.By(city, name: name, taxNo: taxNo);

You can use optional where clauses in By, FirstBy, SingleBy, MinBy, MaxBy, CountBy methods.

An optional where clause is built in 3 steps:

  • When: In this step you specify the parameter on which you will check a condition.
  • Is/Not: Is method expects the given condition to be true while IsNot method expects the given condition to be false.
  • ThenAnd: This is the final step. In this step you provide the where clause.
When(name).IsNot(default).ThenAnd(c => c.Name.StartsWith(name))

Together, in the above statement, you stated that there is an optional filter which should be included when name is not default.

The alternative, you can also use as named optional parameters. They must be the last ones in method arguments list.

There are available 2 different ways.

  • optional: In this way, you specify the single condition.
  • optionals: In this way, you specify the multiple conditions.

You can pass the parameter according to the name, as shown below;

...
public List<Company> By(City city, string name = default)
{
return By(c => c.City == city,
optional: When(name).IsNot(default).ThenAnd(c => c.Name.StartsWith(name))
);
}
public List<Company> By(City city, string name = default, Vkn taxNo = default)
{
return By(c => c.City == city,
optionals: new[] {
When(name).IsNot(default).ThenAnd(c => c.Name.StartsWith(name)),
When(taxNo).IsNot(default).ThenAnd(c => c.TaxNo == taxNo)
}
);
}
...

How to use Is

//if given name parameter object is expectedName
//then the condition will be included in the query.
When(name).Is(n => n == expectedName)
//you can pass an object instead of an expression
//which will be equivalent to above code
When(name).Is(expectedName)
//There is a shortcut method that does the same job
//that When(name).Is(null) and When(name).Is(default) does
When(name).IsDefault()

How to use IsNot

//if given name parameter object is not excludedName
//then the condition will be included in the query.
When(name).IsNot(c => c == excludedName)
//you can pass an object instead of an expression
//which will be equivalent to above code
When(name).IsNot(excludedName)
//There is a shortcut method that does the same job
//that When(name).IsNot(null) and When(name).IsNot(default) does
When(name).IsNotDefault()

Query Base Class

Query<T> is an abstract class with helper functionalities to make it simple to create queries. All methods in this class are protected. If a method declared in Query<T> class is to be exposed as a business service, you can override its access modifiers with public new keyword.

SingleById and ByIds

public abstract class Query<T> : IQuery<T>
{
...
protected virtual T SingleById(int id) { ... }
protected virtual List<T> ByIds(List<int> ids) { ... }
...
}

These methods are protected helper methods and are used by Gazel to find a record by an id or ids. If you want these methods to be available for internal use, then you can simply do the following;

public class Company { ... }
public class Companies : Query<Company>
{
...
internal new Company SingleById(int id)
{
return base.SingleById(id);
}
...
}

SingleById caches the result in request scope, that is, when you make subsequent calls to SingleById, only first call will hit database.

Query.All

This method lists all records in corresponding table. This method is protected in Query<T> base class. If you want a persistent class to have an All query as a business service do the following;

public class Company { ... }
public class Companies : Query<Company>
{
...
public new Company All()
{
return base.All();
}
...
}

Transaction Management

Business services always runs in a transaction context. Its either main transaction or manually created transactions. In this section you can learn how we deal with database transactions.

Using Main Transaction

Before every service call, Gazel creates a database connection and begins a transaction. If there occurs an exception the transaction is automatically rolled back. If there are no exceptions than transaction is committed.

public class Company
{
...
public virtual void AddEmployee(string name)
{
_context.New<Employee>().With(this, name);
throw new Exception(); //Above insert will be rolled back
}
...
}

How to Disable Main Transaction

If you want to disable this main transaction behaviour, simply put [ManualTransaction] attribute to the service method. This will prevent a transaction to be opened.

public class Company
{
...
[ManualTransaction]
public virtual void AddEmployee(string name)
{
...
}
...
}

When you use [ManualTransaction] you are not allowed to use a persistent object as a parameter because there will be no transaction or connection to fetch that persistent object.

Disabling main transaction can help when you need to make an external or internal API call before you make any database operation. Normally, when you make an API call, you left a connection and a transaction open and waiting in an idle state. If your service requires an external call, we suggest you to disable main transaction and make your external calls without blocking a connection.

Be careful if you implement ISession and IAccount interfaces on persistent classes. You need to check if there is a transaction before accessing the database. You may use _context.TransactionalFactory.TransactionExists or _context.WithTransaction when implementing ISession.GetSession, ISession.Validate and IAccount.HasAccess methods.

public class Price
{
...
[ManualTransaction]
public virtual async Money Calculate(Money amount)
{
// no connection is used until unit converter service responds
var result = await unitConverterService.Convert(amount, 'USD');
// new transaction is used to log conversion into db
await _context.WithNewTransaction().DoAsync(() => {
_context.New<PriceLog>().With(amount, result);
});
// connection is released again
return result;
}
...
}

Here we used the WithNewTransaction method to obtain a new connection and a transaction within a service. This feature is explained in detail in the following section.

Creating New Transactions

Most of the time one transaction will be enough for business services. However there are cases when you will need a record to be updated or inserted and committed to database.

public class Company
{
...
public virtual void AddEmployee(string name)
{
_context.WithNewTransaction().Do(() => {
_context.New<Employee>().With(this, name);
});
throw new Exception(); //Above insert will not be rolled back
}
...
}

In above example _context.WithNewTransaction().Do(() => { ... }) creates a new database connection and begins a transaction on this new connection to provide you with a new transaction context.

When an exception occurs in a transaction scope, Gazel catches the exception, rollbacks the transcation, closes the connection and rethrows the exception.

public class Company
{
...
public virtual void AddEmployee(string name)
{
_context.WithNewTransaction().Do(() => {
_context.New<Employee>().With(this, name);
throw new Exception(); //Above insert will be rolled back
});
}
...
}

Using Persistent Objects

In previous example we used a variable (name) from outer scope. This is not a problem when variables are non-persistent objects such as string, int, a manager object or any class or record in your modules.

Consider you need to pass a persistent object to AddEmployee method.

public class Company
{
...
public virtual void AddEmployee(string name, Branch employeeBranch)
{
_context.WithNewTransaction().Do(() => {
//employeeBranch object will cause trouble
_context.New<Employee>().With(this, name, employeeBranch);
});
}
...
}

When you pass a persistent object directly to a new transaction context, this means that a Branch instance from outer scope will be assigned to an Employee instance from inner scope. This causes an unexpected state for NHibernate. To assign a persistent object to another persistent object, they need to be loaded from the same transaction scope and ISession instance. To achieve this you need to pass Branch object with a different way.

public class Company
{
...
public virtual void AddEmployee(string name, Branch employeeBranch)
{
_context.WithNewTransaction(this, employeeBranch).Do((@this, eb) => {
//correct way to pass a persistent object to new transaction
_context.New<Employee>().With(@this, name, eb);
});
}
...
}

employeeBranch is passed to new transaction scope using _context.WithNewTransaction(employeeBranch).Do(eb => { ... }). When you do this, Gazel gets the id of given employeeBranch, loads it in new transaction scope and passes it as a parameter (eb).

You can pass up to 15 parameters to WithNewTransaction method.

Nested Transactions

You can create as many nested transactions as you want like below;

//this level uses main transaction
_context.WithNewTransaction().Do(() => {
//this level uses first transaction
_context.WithNewTransaction().Do(() => {
//this level uses second transaction
_context.WithNewTransaction().Do(() => {
//this level uses third transaction
});
});
});

When you create nested transactions, beware that you are using more than one database connections at a time.

Example: Update Balance of an Account

Below is an example to demonstrate an update to a balance in an account.

public class Account
{
...
public virtual void Withdraw(Money amount)
{
_context.WithNewTransaction(this).Do(@this => {
@this.Balance -= amount;
});
}
...
}

If you want to lock a record, you can make use of IRepository<T>.Lock method.

public class Account
{
...
public virtual void Withdraw(Money amount)
{
_context.WithNewTransaction(this).Do(@this => {
//use repository of @this object.
//"this._repository" uses outer scope whereas "@this._repository" uses inner scope.
@this._repository.Lock(@this);
@this.Balance -= amount;
});
}
...
}

To make it more readable, lets extract balance operation to another method.

public class Account
{
...
public virtual void Withdraw(Money amount)
{
_context.WithNewTransaction(this).Do(@this => {
@this.LockAndChangeBalance(amount);
});
}
private void LockAndChangeBalance(Money amount)
{
_repository.Lock(this);
Balance -= amount
}
...
}

Exception Handling

Gazel uses .NET exceptions to handle errors in your business code. For every error message there should be a corresponding exception class.

Writing an Exception

Create a parent static class for each group of your exceptions and define all related exceptions under this class as a nested class.

using static MyProduct.ResultCodes;
namespace MyProduct;
public static class CommonExceptions
{
public class NameShouldBeUnique : ServiceException
{
public NameShouldBeUnique() : base(Common.Err(0)) { }
}
public class ValueIsRequired : ServiceException
{
public ValueIsRequired() : base(Common.Err(1)) { }
}
}

Exceptions should inherit from ServiceException to be treated as handled errors. Handled errors are HTTP status 4XX in REST API and they are logged in WARN level.

All other exceptions are treated as unhandled errors with code 99999, logged in ERROR level and HTTP status is always 500 in REST API.

ResultCodes is a class that generates error codes, but we will mention this later in this section. You can find a detailed description in Features / Exception Handling

Throwing an Exception

To throw an exception you can use using static directive to include exceptions class, MyProduct.CommonExceptions in this case;

using static MyProduct.CommonExceptions;

And then just throw the exception like any other .NET exception;

...
public class Company
{
...
protected internal Company With(string name)
{
if(_context.Query<Companies>().AnyByName(name))
{
throw new NameShouldBeUnique();
}
...
}
...
}
...

Result Codes

Above mentioned ResultCodes is a class where you organize your error, warning and info codes as named code blocks;

...
public class ResultCodes : ResultCodeBlocks
{
public static readonly ResultCodeBlock Common = CreateBlock(1, "Common");
}
...

You will use this class potentially from every business module, so it's better for this to be included in the most base business module.

Every code block reserves 700 error codes, 100 warning codes and 100 info codes. In the previous example we've seen Common.Err(0), this means that NameShouldBeUnique error should have the first error code in Common code block.

...
public NameShouldBeUnique() : base(Common.Err(0)) { } // First error of 'Common' code block
...

Here, we've started with Common.Err(0) and can go up to Common.Err(699) using Common code block.

You may create a code block for every business module, so that error codes are grouped according to their business domain.

Parameterized Exceptions

You can accept parameters in your exception classes;

...
public class NameShouldBeUnique : ServiceException
{
public NameShouldBeUnique(string name) : base(Common.Err(0), name) { }
}

Last parameter of base constructor accepts params object[] parameters so that you can add as many parameters as you want. This parameters are used in building the exception message that is included in the response.

Localizing Messages

Error messages will be asked to localization with a key that is unique to each result code. For example, NameShouldBeUnique exception will have ERR-20701 error code.

To include parameters in messages, you can use a format string as your message such as '{0}' already exists, name should be unique. First parameter will replace {0}, second parameter will replace {1}, and so on.