Using the DTO Class Model with NHibernate
Fetching DTO instances
To fetch DTO instances, the projection methods are used to append a projection to a Linq query formulated in an IQueryable<T>
.
Below an example is given of using these projection methods, by fetching a set of Customer DTO class instances projected from a set of Customer entity instances, using a
Linq query. The Customer DTO class instantiated is given in the Generated DTO class structure section.
The generated projection methods automatically add eager-loading directives to the Linq query. It's therefore not necessary to specify eager loading directives to load the related entities for the projection.
Fetching the queries is equal to fetching a Linq query, so all the methods, including async/await, are available to you.
Inheritance and derived elements is limited. Due to the fact the Linq query projection is run on the raw data coming from the database, it's not possible to determine at runtime which derived element subtype to materialize: no supported ORM framework supports this. The downside is that for m:1/1:1 related embedded derived elements, subtypes aren't materialized.
On NHibernate, no linq query will be able to fetch sets of related derived subtypes. This is a limitation in how nested sets are handled in Linq queries for NHibernate. NHibernate does support fetching of sets of related entities which are subtypes, but projections of these related sets are not able to project to derived element subtypes, as at projection time it can't determine which type a row represents during projection of these related elements. Inheritance in related derived elements is therefore not usable on NHibernate: no queries are able to fetch the data. If you require this feature, consider the LLBLGen Pro Runtime Framework instead.
Example projection on database query
List<Customer> results = null;
using(var session = SessionManager.OpenSession())
{
var q = (from c in session.Query<Customer>()
where c.VisitingAddress.Country == "USA"
select c)
.ProjectToCustomer();
results = q.ToList();
}
In the query above, a normal entity fetch query is appended with a call to ProjectToCustomer
which is a generated method in the generated class
RootNamespace.Persistence.CustomerPersistence. This method simply constructs a lambda which converts the entity into a DTO during query fetch, which means
the data read from the database is immediately converted to the DTO class Customer
, without first instantiating entity class instances.
As ProjectToCustomer
is generated code, you can see for yourself how it's done and it's a straightforward Linq projection. It's located in a partial class, and it's easy
to add your own projections to the class if you want to.
The generated ProjectToDerivedElementName method has a user code region which isn't overwritten by the code generator, and which allows you to add additional elements to the projection (C# only. VB.NET doesn't allow comments in multi-line spanning statements)
Example projection on in-memory datastructure
Instead of projecting DTOs from IQueryable<T>
queries directly on the database, you can also project an in-memory construct to DTO instances. This is illustrated below.
First the set of entity instances to project are fetched as entity instances and placed into a list. Then the entity instances in the list are projected into DTO instances.
In this case, this is less efficient than the query in the previous example, but in case you have to do specific work with the entity instances, it can be beneficial.
List<NW.EntityClasses.Customer> entities = null;
using(var session = SessionManager.OpenSession())
{
var q = (from c in session.Query<Customer>()
where c.VisitingAddress.Country == "USA"
select c);
entities = q.ToList();
}
// 'entities' is now a list of materialized entity instances.
// you can now project these entities into DTO instances.
List<Customer> dtos = entities.AsQueryable().ProjectToCustomer().ToList();
Be aware that the in-memory projection doesn't contain any null checks, so if a related entity is null (Nothing in VB.NET) and it's used in a navigation in the projection, using the projection method will lead to a NullReferenceException
being thrown.
Writing changes from DTO to entity instances
For the preset SD.DTOClasses.ReadWriteDTOs, additional extension methods are generated to use the DTO instance to update the root entity instance it was initially projected from. This is useful if you receive a filled DTO instance or set of DTO instances from e.g. the client application and the entity instances they represent have to be updated with the values in the DTO / DTOs.
The generated code is designed to use the following pattern:
- Load the original entity instance using a filter created from the DTO instance
- Update the loaded entity instance with the values from the DTO instance
- Persist the loaded, updated entity instance to the database.
For step 1 there are two methods available: 'RootDerivedElementNamePersistence.CreatePkPredicate(dto)' and 'RootDerivedElementNamePersistence.CreateInMemoryPkPredicate(dto)'. The first method, CreatePkPredicate(dto)
, is used with a Linq query to fetch the entity or entities
from the persistence storage (database). The second method is used to obtain the entity from an in-memory datastructure, e.g. an IEnumerable<T>
and is used in a Linq-to-objects query. The method CreatePkPredicate(dto)
has an overload which accepts a set of DTOs instead of a single DTO instance. This is usable if you want to update a set of entities based on a set of DTO instances, you can then obtain the entities with one Linq query.
For step 2, the extension method 'UpdateFromRootDerivedElementName(dto)' is used, on the entity instance to update. For step 3, the regular code to save an entity is used.
Examples
Below examples are given for obtaining entities from in-memory datastructures as well as obtaining entities from the database using Linq. The Root Derived Element used is a 'CustomerOrder' which is derived from the entity 'Customer' and has embedded 'Order' derived elements derived from the related 'Order' entity.
By design, only the entity the Root Derived Element derives from is updated, related entities aren't updated.
Updating single entity, fetched from database
The variable dto
contains the dto with the data, received from the client.
using(var session = SessionManager.OpenSession())
{
// fetch the entity from the DB
var entity = session.Query<Customer>()
.FirstOrDefault(CustomerOrderPersistence.CreatePkPredicate(dto));
if(entity==null)
{
// doesn't exist, so create a new instance.
entity = new NW.EntityClasses.Customer() { VisitingAddress=new AddressVt() };
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// save entity.
using(var transaction = session.BeginTransaction())
{
session.SaveOrUpdate(entity);
transaction.Commit();
}
}
Updating set of entities, fetched from database
The variable dtos
contains the set of dto instances with the data, received from the client.
using(var session = SessionManager.OpenSession())
{
// fetch the entities from the DB
var entities = session.Query<Customer>()
.Where(CustomerOrderPersistence.CreatePkPredicate(dtos))
.ToList();
using(var transaction = session.BeginTransaction())
{
foreach(var dto in dtos)
{
// find the entity the current dto derived from, using in-memory pk filter
var entity = entities.FirstOrDefault(CustomerOrderPersistence.CreateInMemoryPkPredicate(dto));
if(entity == null)
{
// new entity
entity = new NW.EntityClasses.Customer() { VisitingAddress=new AddressVt() };
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
session.SaveOrUpdate(entity);
}
transaction.Commit();
}
}
Updating single entity, obtained from in-memory structure
The variable dto
contains the dto with the data, received from the client, entities
is a List<CustomerEntity>
in-memory structure.
// obtain the entity from an in-memory list
var entity = entities
.FirstOrDefault(CustomerOrderPersistence.CreateInMemoryPkPredicate(dto));
using(var session = SessionManager.OpenSession())
{
if(entity==null)
{
// doesn't exist, so create a new instance.
entity = new NW.EntityClasses.Customer() { VisitingAddress=new AddressVt() };
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// save entity.
using(var transaction = session.BeginTransaction())
{
session.SaveOrUpdate(entity);
transaction.Commit();
}
}
Updating set of entities, obtained from in-memory structure
The variable dtos
contains the set of dto instances with the data, received from the client, entities
is a List<CustomerEntity>
in-memory structure.
using(var session = SessionManager.OpenSession())
{
using(var transaction = session.BeginTransaction())
{
foreach(var dto in dtos)
{
// find the entity the current dto derived from, using in-memory pk filter
var entity = entities.FirstOrDefault(CustomerOrderPersistence.CreateInMemoryPkPredicate(dto));
if(entity == null)
{
// new entity
entity = new NW.EntityClasses.Customer() { VisitingAddress=new AddressVt() };
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
session.SaveOrUpdate(entity);
}
transaction.Commit();
}
}