Using the DTO Class Model with Linq to SQL
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, are available to you. The examples below use an attribute based mapping, but if you're using xml file based mapping, the examples are the same, except instantiating the context class, which requires an extra line of code for xml mappings. See for details regarding xml file based mappings the Linq to Sql support documentation for LLBLGen Pro.
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 Linq to SQL, 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 Linq to SQL. Linq to SQL 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 Linq to SQL: 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.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();
// first update
entity.UpdateFromCustomerOrder(dto);
// then attach, to avoid PK overwrite exception
ctx.Customers.Attach(entity);
}
else
{
entity.UpdateFromCustomerOrder(dto);
}
// update entity with values of dto
entity.UpdateFromCustomerOrder(dto);
// save entity.
ctx.SubmitChanges();
}
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)
{
// doesn't exist, so create a new instance.
entity = new NW.EntityClasses.Customer();
// first update
entity.UpdateFromCustomerOrder(dto);
// then attach, to avoid PK overwrite exception
ctx.Customers.Attach(entity);
}
else
{
entity.UpdateFromCustomerOrder(dto);
}
}
// persist all changed entities in one transaction
ctx.SubmitChanges();
}
There are no examples for Linq to SQL and updating an entity obtained from an in-memory storage, as Linq to SQL has a limited Attach() method, which can't deal with an entity instance obtained through another context: that's not supported by Linq to SQL.