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

Tip

The generated projection methods automatically add prefetch path directives to the Linq 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 query, so all the methods, including async/await, are available to you.

Info

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.

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();
}
var metaData = new LinqMetaData();
var q = (from c in metaData.Customer
         where c.VisitingAddressCountry == "USA"
         select c)
        .ProjectToCustomer();
List<Customer> 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.

Tip

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<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();
Important!

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:

  1. Load the original entity instance using a filter created from the DTO instance
  2. Update the loaded entity instance with the values from the DTO instance
  3. 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.

Info

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