Relations

Gazel provides a restricted usage for relations among persistent classes. In this document you can learn about the types of relations Gazel supports.

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.

About One-to-Many Relations

Gazel does not configure NHibernate for one-to-many relations. So creating List<T> properties on persistent classes will not cause a one-to-many relation.

This is because accessing a subset of child records is a more likely need than fetching all of them at once. If you want to fetch all of the child records, it is not too much of a burden anyway;

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); } ... }

About One-to-One Relations

Gazel does support one-to-one relations through a convention. However, we don't see it practical enough and don't recommend it. Nevertheless, you can find an example of a one-to-one relation;

public class Employee { ... public virtual string Name { get; protected set; } public virtual Account Account { get; protected set; } ... protected internal virtual Employee With(string name, string accountName) { Name = name; repository.Insert(this); Account = context.New<Account>().With(accountName, this); return this; } ... } public class Employees : Query<Employee> { ... internal new Employee SingleById(int id) { return base.SingleById(id); } ... } ... public class Account { ... public virtual string Name { get; protected set; } protected internal virtual int EmployeeId { get; protected set; } public virtual Employee Employee { get { return context.Query<Employees>().SingleById(EmployeeId); } } ... protected internal virtual Employee With(string name, Employee employee) { Name = name; EmployeeId = employee.Id repository.Insert(this); return this; } ... }

There are a few notes on this type of relation;

  • Account class does not have a query class. This means that you can access to Account records only through Employee records. If you write a query class Accounts : Query<Account>, one-to-one relation will not take place.
  • Public methods of Account class will not be business services. This means that you have to write public methods to Employee class to expose business services of Account class.
  • There is a special case for one-to-one relations. context.Query<Employees>().SingleById(EmployeeId); statement will never cause N+1 select problem, that's why we put it in a property getter.
  • Surprisingly Account class will not have a corresponding column for EmployeeId property, but Employee class will have a column named AccountId mapped to Account property.

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 proxies to be loaded lazily.

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 } } ... }

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 WebAddress { ... public virtual IWebAddressOwner Parent { get; protected set; } ... }

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

  1. ParentId: The id value of related record, like in a Many-to-One relation,
  2. ParentType: 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 IWebAddressOwner interface.

public interface IWebAddressOwner { } public enum WebAddressOwnerType { Company = 1, Employee = 2 } public class Company : IWebAddressOwner { ... } public class Employee : IWebAddressOwner { ... }

Conventions for one-to-any mapping are;

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

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

public class WebAddresses : Query<WebAddress> { ... internal List<WebAddress> ByParent(IWebAddressOwner parent) { return By(wa => wa.Parent == parent); //uses both ParentId and ParentType columns } ... }

Or you can filter by type like this;

public class WebAddresses : Query<WebAddress> { ... internal List<WebAddress> ByParentType<T>() where T : IWebAddressOwner { return By(wa => wa.Parent is T); //uses only ParentType column } ... }

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