Using the DTO Class Model with Entity 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.
Fetching the queries is equal to fetching a Linq query, so all the methods, including async/await, are available to you. The examples below are using Entity Framework 6 code-first, but the methods also work on Entity Framework with DbContext and for older Entity Framework versions.
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 Entity Framework, 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 Entity Framework 6. Entity Framework 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 Entity Framework: 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 ctx = new NorthwindDataContext())
{
var q = (from c in ctx.Customers
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.
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 ctx = new NorthwindDataContext())
{
var q = (from c in ctx.Customers
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();
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 ctx = new NorthwindDataContext())
{
// fetch the entity from the DB
var entity = ctx.Customer
.FirstOrDefault(CustomerOrderPersistence.CreatePkPredicate(dto));
if(entity==null)
{
// doesn't exist, so create a new instance.
entity = new NW.EntityClasses.Customer() { VisitingAddress=new AddressVt() };
ctx.Customers.Add(entity);
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// save entity.
ctx.SaveChanges();
}
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 ctx = new NorthwindDataContext())
{
// fetch the entities from the DB
var entities = ctx.Customers
.Where(CustomerOrderPersistence.CreatePkPredicate(dtos))
.ToList();
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() };
ctx.Customers.Add(entity);
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
}
// persist all changed entities in one transaction
ctx.SaveChanges();
}
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 ctx = new NorthwindDataContext())
{
if(entity==null)
{
// doesn't exist, so create a new instance.
entity = new NW.EntityClasses.Customer() { VisitingAddress=new AddressVt() };
ctx.Customers.Add(entity);
}
else
{
// exists, attach it.
ctx.Customers.Attach(entity);
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// save entity
ctx.SaveChanges();
}
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 ctx = new NorthwindDataContext())
{
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() };
ctx.Customers.Add(entity);
}
else
{
// exists, attach it.
ctx.Customers.Attach(entity);
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
}
// persist all changed entities in one transaction
ctx.SaveChanges();
}