Organization units (OU) can be used to hierarchically group users and entities.
An OU is represented by the OrganizationUnit entity. Fundamental properties of this entity are;
OrganizationUnit entitiy's primary key (Id) is long and it derives from FullAuditedEntity which provides audit information and implements ISoftDelete interface (so, OUs are not deleted from database, they are just marked as deleted).
Since an OU can have a parent, all OUs of a tenant are in a tree structure. There are some rules for this tree;
OU code is automatically generated and maintained by OrganizationUnit Manager. It's a string something like:
"00001.00042.00005"
This code can be used to easily query database for all children (recursively) of an OU. There are some rules for this code;
OrganizationUnitManager class can be injected and used to manage OUs. Common use cases are:
OrganizationUnitManager is designed to work for a single tenant in a time. It works for the current tenant as default.
Here, we will see common use cases for OUs. You can find source code of the samples here.
Most obvious usage of OUs is to assign an entity to an OU. Let's see a sample entity:
public class Product : Entity, IMustHaveTenant, IMustHaveOrganizationUnit
{
public virtual int TenantId { get; set; }
public virtual long OrganizationUnitId { get; set; }
public virtual string Name { get; set; }
public virtual float Price { get; set; }
}
We simple created OrganizationUnitId property to assign this entity to an OU. IMustHaveOrganizationUnit defines the OrganizationUnitId property. We don't have to implement it, but it's suggested to provide standardization. There is also an IMayHaveOrganizationId which has a nullable OrganizationUnitId property.
Now, we can relate a Product to an OU and query products of a specific OU.
Notice that; Product entity have a TenantId (which is a property of IMustHaveTenant) to distinguish products of different tenants in a multi-tenant application (see multi tenancy document). If your application is not multi-tenant, you don't need this interface and property.
Getting Products of an OU is simple. Let's see this sample domain service:
public class ProductManager : IDomainService
{
private readonly IRepository<Product> _productRepository;
public ProductManager(IRepository<Product> productRepository)
{
_productRepository = productRepository;
}
public List<Product> GetProductsInOu(long organizationUnitId)
{
return _productRepository.GetAllList(p => p.OrganizationUnitId == organizationUnitId);
}
}
We can simply write a predicate against Product.OrganizationUnitId as shown above.
We may want to get Products of an organization unit including child organization units. In this case, OU Code can help us:
public class ProductManager : IDomainService
{
private readonly IRepository<Product> _productRepository;
private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;
public ProductManager(
IRepository<Product> productRepository,
IRepository<OrganizationUnit, long> organizationUnitRepository)
{
_productRepository = productRepository;
_organizationUnitRepository = organizationUnitRepository;
}
[UnitOfWork]
public virtual List<Product> GetProductsInOuIncludingChildren(long organizationUnitId)
{
var code = _organizationUnitRepository.Get(organizationUnitId).Code;
var query =
from product in _productRepository.GetAll()
join organizationUnit in _organizationUnitRepository.GetAll() on product.OrganizationUnitId equals organizationUnit.Id
where organizationUnit.Code.StartsWith(code)
select product;
return query.ToList();
}
}
First, we got code of the given OU. Then we created a LINQ with a join and StartsWith(code) condition (StartsWith creates a LIKE query in SQL). Thus, we can hierarchically get products of an OU.
We may want to get all products those are in OUs of a specific user. Example code:
public class ProductManager : IDomainService
{
private readonly IRepository<Product> _productRepository;
private readonly UserManager _userManager;
public ProductManager(
IRepository<Product> productRepository,
UserManager userManager)
{
_productRepository = productRepository;
_organizationUnitRepository = organizationUnitRepository;
_userManager = userManager;
}
public async Task<List<Product>> GetProductsForUserAsync(long userId)
{
var user = await _userManager.GetUserByIdAsync(userId);
var organizationUnits = await _userManager.GetOrganizationUnitsAsync(user);
var organizationUnitIds = organizationUnits.Select(ou => ou.Id);
return await _productRepository.GetAllListAsync(p => organizationUnitIds.Contains(p.OrganizationUnitId));
}
}
We simply found Ids of OUs of the user. Then used Contains condition while getting products. Surely, we could create a LINQ query with join to get the same list.
We may want to get products in user's OUs including their child OUs:
public class ProductManager : IDomainService
{
private readonly IRepository<Product> _productRepository;
private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;
private readonly UserManager _userManager;
public ProductManager(
IRepository<Product> productRepository,
IRepository<OrganizationUnit, long> organizationUnitRepository,
UserManager userManager)
{
_productRepository = productRepository;
_organizationUnitRepository = organizationUnitRepository;
_userManager = userManager;
}
[UnitOfWork]
public virtual async Task<List<Product>> GetProductsForUserIncludingChildOusAsync(long userId)
{
var user = await _userManager.GetUserByIdAsync(userId);
var organizationUnits = await _userManager.GetOrganizationUnitsAsync(user);
var organizationUnitCodes = organizationUnits.Select(ou => ou.Code);
var query =
from product in _productRepository.GetAll()
join organizationUnit in _organizationUnitRepository.GetAll() on product.OrganizationUnitId equals organizationUnit.Id
where organizationUnitCodes.Any(code => organizationUnit.Code.StartsWith(code))
select product;
return query.ToList();
}
}
We combined Any with StartsWith condition in a LINQ join statement.
Surely, there may be much more complex requirements, but all can be done with LINQ or SQL.
You can inject and use IOrganizationUnitSettings interface to get Organization Units setting values. Currently, there is just a single setting that can be changed for your application needs:
You can change setting values using setting manager.