I want to get records from the database using EF and assign the values to a DTO class.Consider the following tables for a Linq query.
TableA,TableB, TableC
F
UPDATE
As others pointed out, flattening the results (as shown below) is not needed when working with Entity Framework 4.0, since it can translate the LINQ query to an efficient flattened result for you. Therefore, the following code is only needed when working with LINQ to SQL (or possibly other LINQ providers). Note that I have only tested this with EF over SQL Server and not over Oracle, since this behavior could be LINQ provider specific, which means that the Oracle provider (still in beta) or the commercial Devart provider for Oracle could still be doing N + 1.
What you are trying to do is to get a set of objects that are structured like a tree. Without any special care, you will be triggering many queries to the database. With one level of nesting you would be triggering N + 1 queries, but since your nesting is two levels deep, you will be triggering M x (N + 1) + 1 queries, which will almost certainly be very bad for performance (no matter what the size of your data set is). What you want is to make sure that there is only a single query sent to the database. To ensure this, you must create an intermediate query that flattens the result, just as you would have done in the good old SQL days, to retrieve tree like data :-). Take a look at the following example:
var records =
from record in db.TableC
where ... // any filtering can be done here
select record;
// important to call ToArray. This ensures that the flatterned result
// is pulled in one single SQL query.
var results = (
from c in records
select new
{
tableA_rowid = c.B.A.Id,
tableA_Prop1 = c.B.A.Property1,
tableA_Prop2 = c.B.A.Property2,
tableA_PropN = c.B.A.PropertyN,
tableB_rowid = c.B.Id,
tableB_Property1 = c.B.Property1,
tableB_Property2 = c.B.Property2,
tableB_PropertyN = c.B.PropertyN,
tableC_rowid = c.Id,
tableC_Property1 = c.Property1,
tableC_Property2 = c.Property2,
tableC_PropertyN = c.PropertyN,
})
.ToArray();
The next step is to transform that in-memory data structure (using that anonymous type) into the tree structure of DTO objects:
// translate the results to DTO tree structure
TableA_DTO[] dtos = (
from aresult in results
group aresult by aresult.tableA_rowid into group_a
let a = group_a.First()
select new TableA_DTO
{
tableA_rowid = a.tableA_rowid,
tableA_Prop1 = a.tableA_Prop1,
tableA_Prop2 = a.tableA_Prop2,
TableB_records = (
from bresult in group_a
group bresult by bresult.tableB_rowid into group_b
let b = group_b.First()
select new TableB_DTO
{
tableB_rowid = b.tableB_rowid,
tableB_Prop1 = b.tableB_Prop1,
tableB_Prop2 = b.tableB_Prop2,
TableC_records = (
from c in group_b
select new TableC_DTO
{
tableC_rowid = c.tableC_rowid,
tableC_Prop1 = c.tableC_Prop1,
tableC_Prop2 = c.tableC_Prop2,
}).ToList(),
}).ToList()
})
.ToArray();
As you can see, the first part of the solution is actually the 'old' way of doing this, way back when we would still write our SQL queries by hand. Nice however is, that once we get this typed set of in-memory data, we can leverage LINQ (to Objects) again to get this data in the structure we want.
Note that this also allows you to do paging and sorting. This will be a bit more tricky, but certainly not impossible.