Using the DTO Class Model with LLBLGen Pro Runtime Framework
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>
, or a QuerySpec query,
formulated in an EntityQuery<T>
. Below an example is given of using these projection methods, both in Linq and QuerySpec.
The methods fetch a set of Customer DTO class instances projected from a set of Customer entity instances, using a Linq query and QuerySpec query. The Customer DTO class instantiated is given in the Generated DTO class structure section.
The generated projection methods automatically add prefetch path directives to the Linq query and QuerySpec query. It's therefore not necessary to specify prefetch paths to load the related entities for the projection.
Fetching the queries is equal to fetching a Linq or QuerySpec query, so all the methods, including async/await, are available to you.
Inheritance and derived elements is limited, and only available using the Linq based projection methods. 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.
Example projection on database query
List<Customer> results = null;
using(var adapter = new DataAccessAdapter())
{
var metaData = new LinqMetaData(adapter);
var q = (from c in metaData.Customer
where c.VisitingAddressCountry == "USA"
select c)
.ProjectToCustomer();
results = q.ToList();
}
List<Customer> results = null;
using(var adapter = new DataAccessAdapter())
{
var qf = new QueryFactory();
var q = qf.Customer.Where(CustomerFields.VisitingAddressCountry
.Equal("USA")).ProjectToCustomer(qf);
results = adapter.FetchQuery(q);
}
var metaData = new LinqMetaData();
var q = (from c in metaData.Customer
where c.VisitingAddressCountry == "USA"
select c)
.ProjectToCustomer();
List<Customer> results = q.ToList();
var qf = new QueryFactory();
var q = qf.Customer.Where(CustomerFields.VisitingAddressCountry
.Equal("USA")).ProjectToCustomer(qf);
List<Customer> results = new TypedListDAO().FetchQuery(q);
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 the ProjectToCustomer
methods are generated code, you can see for yourself how it's done: they contain a straightforward Linq or QuerySpec 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
Projecting in-memory datastructures is only available through the IQueryable<T>
interface. In practice this doesn't really matter much, but if you extend the
QuerySpec projection method, it won't run when you use the in-memory projection: you have to extend the Linq variant.
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<CustomerEntity> entities = null;
using(var adapter = new DataAccessAdapter())
{
var metaData = new LinqMetaData(adapter);
var q = (from c in metaData.Customer
where c.VisitingAddressCountry == "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();
var metaData = new LinqMetaData();
var q = (from c in metaData.Customer
where c.VisitingAddressCountry == "USA"
select c);
List<CustomerEntity> 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 adapter = new DataAccessAdapter())
{
var metaData = new LinqMetaData(adapter);
// fetch the entity from the DB
var entity = metaData.Customer
.FirstOrDefault(CustomerOrderPersistence.CreatePkPredicate(dto));
if(entity==null)
{
// doesn't exist, so create a new instance.
entity = new CustomerEntity();
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// save entity
adapter.SaveEntity(entity);
}
var metaData = new LinqMetaData();
// fetch the entity from the DB
var entity = metaData.Customer
.FirstOrDefault(CustomerOrderPersistence.CreatePkPredicate(dto));
if(entity==null)
{
// doesn't exist, so create a new instance.
entity = new CustomerEntity();
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// save entity
entity.Save();
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 adapter = new DataAccessAdapter())
{
var metaData = new LinqMetaData(adapter);
// fetch the entities from the DB
var entities = metaData.Customer
.Where(CustomerOrderPersistence.CreatePkPredicate(dtos))
.ToList();
var uow = new UnitOfWork2();
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 CustomerEntity();
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// log for persistence
uow.AddForSave(entity);
}
// persist all changed entities in one transaction
uow.Commit(adapter);
}
var metaData = new LinqMetaData();
// fetch the entities from the DB
var entities = metaData.Customer
.Where(CustomerOrderPersistence.CreatePkPredicate(dtos))
.ToList();
var uow = new UnitOfWork();
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 CustomerEntity();
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// log for persistence
uow.AddForSave(entity);
}
// persist all changed entities in one transaction
using(var trans = new Transaction(IsolationLevel.ReadCommitted, "Update DTOs"))
{
uow.Commit(trans);
}
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));
if(entity==null)
{
// doesn't exist, so create a new instance.
entity = new CustomerEntity();
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
using(var adapter = new DataAccessAdapter())
{
// save entity
adapter.SaveEntity(entity);
}
// obtain the entity from an in-memory list
var entity = entities
.FirstOrDefault(CustomerOrderPersistence.CreateInMemoryPkPredicate(dto));
if(entity==null)
{
// doesn't exist, so create a new instance.
entity = new CustomerEntity();
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// save entity
entity.Save();
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.
var uow = new UnitOfWork2();
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 CustomerEntity();
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// log for persistence
uow.AddForSave(entity);
}
using(var adapter = new DataAccessAdapter())
{
// persist all changed entities in one transaction
uow.Commit(adapter);
}
var uow = new UnitOfWork();
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 CustomerEntity();
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// log for persistence
uow.AddForSave(entity);
}
// persist all changed entities in one transaction
using(var trans = new Transaction(IsolationLevel.ReadCommitted, "Update DTOs"))
{
uow.Commit(trans);
}