Converting Entity to DTO along with child entities - c#

Trying to convert an entity object to local object so i can use it for further transformations.
Here is the code that i am using to convert the entity object;
IEnumerable<SystemArea> result = (from sa in CurrentContext.systemarea
select new SystemArea
{
SystemAreaId = sa.SystemAreaId,
SystemAreaCode = sa.SystemAreaCode,
SystemAreaType = sa.SystemAreaType,
SystemAreaDescription = sa.SystemAreaDescription,
SystemAreaCreatedDate = sa.SystemAreaCreatedDate,
SystemAreaUpdateDate = sa.SystemAreaUpdateDate,
SystemAreaStatus = sa.SystemAreaStatus,
Count = sa.systemareafunctionality.Count,
SystemAreaFunctionality = sa.systemareafunctionality.Select(e => new SystemAreaFunctionality { SystemAreaCode =e.SystemAreaCode })
}).ToList();
Here the count variable is to confirm whether there is any child data in it.
SystemAreaFunctionality is the child object that i am trying to convert here by using SELECT function but it is always blank collection. Rest data is getting assigned to parent object but the only thing missing here is the child table records. Where am i going wrong, please help!
Generated SQL :
SELECT
`Project3`.`C1`,
`Project3`.`SystemAreaId`,
`Project3`.`SystemAreaCode`,
`Project3`.`SystemAreaType`,
`Project3`.`SystemAreaDescription`,
`Project3`.`SystemAreaCreatedDate`,
`Project3`.`SystemAreaUpdateDate`,
`Project3`.`SystemAreaStatus`,
`Project3`.`C3` AS `C2`,
`Project3`.`C2` AS `C3`,
`Project3`.`SystemAreaCode1`
FROM (SELECT
`Project1`.`SystemAreaId`,
`Project1`.`SystemAreaCode`,
`Project1`.`SystemAreaType`,
`Project1`.`SystemAreaDescription`,
`Project1`.`SystemAreaCreatedDate`,
`Project1`.`SystemAreaUpdateDate`,
`Project1`.`SystemAreaStatus`,
1 AS `C1`,
`Project2`.`SystemAreaCode` AS `SystemAreaCode1`,
`Project2`.`C1` AS `C2`,
`Project1`.`C1` AS `C3`
FROM (SELECT
`Extent1`.`SystemAreaId`,
`Extent1`.`SystemAreaCode`,
`Extent1`.`SystemAreaType`,
`Extent1`.`SystemAreaDescription`,
`Extent1`.`SystemAreaCreatedDate`,
`Extent1`.`SystemAreaUpdateDate`,
`Extent1`.`SystemAreaStatus`,
(SELECT
COUNT(1) AS `A1`
FROM `systemareafunctionality` AS `Extent2`
WHERE `Extent1`.`SystemAreaCode` = `Extent2`.`SystemAreaCode`) AS `C1`
FROM `systemarea` AS `Extent1`) AS `Project1` LEFT OUTER JOIN (SELECT
`Extent3`.`SystemAreaCode`,
1 AS `C1`
FROM `systemareafunctionality` AS `Extent3`) AS `Project2` ON `Project1`.`SystemAreaCode` = `Project2`.`SystemAreaCode`) AS `Project3`
ORDER BY
`Project3`.`SystemAreaCode` ASC,
`Project3`.`C2` ASC
JSON output:
[{"SystemAreaId":1,"SystemAreaCode":"KIO","SystemAreaType":"KIOSK","SystemAreaDescription":"tasks
related to
receptionist","SystemAreaCreatedDate":"/Date(1543421018000)/","SystemAreaUpdateDate":"/Date(1543421018000)/","SystemAreaStatus":true,"SystemAreaFunctionality":[],"Count":1}]
PS : Please don't suggest automapper or extension methods. Thanks!

OPINION :
Took me two days to make MySQL(latest version) work with EF and trust me it was painstaking and on the contrary EF with MSSQL is so simple and easy to implement.
One thing i experienced is that Oracle is not interested in providing support for the free version of MySQL whatsoever, so they are being sloppy on the documentation of new version and are providing unstable .NET connectors.
ACTUAL ANSWER :
EF was behaving so weirdly, that it would only load the data in the child entity (SystemAreaFunctionality) only if i asked EF to load the child of the child entity (i.e. SystemAreaFunctionalityEmployeeRoleMapping which is child to SystemAreaFuncionality), which also means that i had to take unnecessary data.
So my link query looks like this :
var result = (from sa in CurrentContext.systemarea
select new SystemArea
{
SystemAreaId = sa.SystemAreaId,
SystemAreaType = sa.SystemAreaType,
Count = sa.systemareafunctionality.Count,
SystemAreaFunctionalities = sa.systemareafunctionality.Select(saf => new SystemAreaFunctionality
{
SystemAreaId = saf.SystemAreaId,
SystemAreaFunctionalityController = saf.SystemAreaFunctionalityController,
SystemAreaFunctionalityAction = saf.SystemAreaFunctionalityAction,
SystemAreaFunctionalityType = saf.SystemAreaFunctionalityType,
SystemAreaFunctionalityEmployeeRoleMappings = saf.systemareafunctionalityemployeerolemapping.Select(saferm => new SystemAreaFunctionalityEmployeeRoleMapping
{
SystemAreaFunctionalityEmployeeRoleMappingId = saferm.SystemAreaFunctionalityEmployeeRoleMappingId,
SystemAreaFunctionalityCreatedDate = saferm.SystemAreaFunctionalityCreatedDate
})
})
}).ToList();
ALTERNATIVELY :
Tried using the same linq query (posted in OP) with different database this time with PostgreSQL plus npgsql connector and surprisingly EF gives me exactly what i want with out extra baggage.
On top of that PostgreSQL gives better performance with EF than MySQL. So i presume that switching to PostgreSQL would be a better option.
PS : If you are deciding on open sources DBMS then please refer this before jumping in with MySQL :
https://www.youtube.com/watch?v=emgJtr9tIME
https://www.reddit.com/r/csharp/comments/40x3nx/which_orm_is_the_right_choice_for_mysql

Use this:
CurrentContext.systemarea.Include('systemareafunctionality')
or
IEnumerable<SystemArea> result = (from sa in CurrentContext.systemarea
join systemareafunctionality in CurrentContext.systemareafunctionality on sa.systemareafunctionalityID equals systemareafunctionality.ID
select new SystemArea
{
SystemAreaId = sa.SystemAreaId,
SystemAreaCode = sa.SystemAreaCode,
SystemAreaType = sa.SystemAreaType,
SystemAreaDescription = sa.SystemAreaDescription,
SystemAreaCreatedDate = sa.SystemAreaCreatedDate,
SystemAreaUpdateDate = sa.SystemAreaUpdateDate,
SystemAreaStatus = SystemAreaStatus,
Count = systemareafunctionality.Count,
SystemAreaFunctionality = systemareafunctionality.Select(e => new SystemAreaFunctionality { SystemAreaCode =e.SystemAreaCode })
}).ToList();
or
IEnumerable<SystemArea> result = (from sa in CurrentContext.systemarea
join systemareafunctionality in CurrentContext.systemareafunctionality on sa.systemareafunctionalityID equals systemareafunctionality.ID into item1 from subitem1 in item1.DefaultIfEmpty()
select new SystemArea
{
SystemAreaId = sa.SystemAreaId,
SystemAreaCode = sa.SystemAreaCode,
SystemAreaType = sa.SystemAreaType,
SystemAreaDescription = sa.SystemAreaDescription,
SystemAreaCreatedDate = sa.SystemAreaCreatedDate,
SystemAreaUpdateDate = sa.SystemAreaUpdateDate,
SystemAreaStatus = SystemAreaStatus,
Count = systemareafunctionality.Count,
SystemAreaFunctionality = systemareafunctionality.Select(e => new SystemAreaFunctionality { SystemAreaCode =e.SystemAreaCode })
}).ToList();

Apparently your CurrentContext is a Dbcontext with at least a table of SystemAreas and a table of SystemAreaFunctionalities.
It seems that every SystemArea has zero or more SystemAreaFunctionalities; every SystemAreaFunctionality belongs to exactly one SystemArea, a straightforward one-to-many relationship using a foreign key.
Note: it might be that you have a many-to-many relation, the answer will be similar
Alas you forgot to write your classes, so I'll give a shot:
class SystemArea
{
public int Id {get; set;}
... // other properties
// every SystemArea has zero or more SystemAreaFunctionalities (one-to-many)
public virtual ICollection<SystemAreaFunctionality> SystemAreaFunctionalities {get; set;}
}
class SystemAreaFunctionality
{
public int Id {get; set;}
... // other properties
// every SystemAreaFunctionality belongs to exactly one SystemArea, using foreign key
public int SystemAreaId {get; set;}
public virtual SystemArea SystemArea {get; set;}
}
In entity framework the columns of your tables are represented by non-virtual properties, the virtual properties represent the relationships between the tables. (one-to-many, many-to-many, ...)
for completeness:
class CurrentContext : DbContext
{
public DbSet<SystemArea> SystemAreas {get; set;}
public DbSet<SystemAreaFunctionality> SystemAreaFunctionalities {get; set;}
}
If people want items with their sub-items, like Schools with their Students, Customers with their Orders, etc. people tend to perform a (group)Join. However, when using entity framework joins are seldom necessary. Ise the ICollections instead. Entity framework knows the relationships and knows which (group)join to perform.
Regularly I see people use Include, but if you do that, you'll select the complete object, which is not very efficient. Suppose you have a SystemArea with Id = 10, and 1000 SystemAreaFunctionalities, you know that every SystemAreaFunctionality has a foreign key SystemAreaId with a value 10. Instead of sending this value only once as primary key of SystemArea, Include will also select all 1000 foreign keys with this value 10. What a waste of processing power!
When querying data, always use Select and select only the properties you actually plan to use. Only use Include if you plan to Update the included object.
You wrote:
SystemAreaFunctionality is the child object that i am trying to convert here...
It is not clear what you really want. Do you want a collection of all used SystemAreaCodes? Or do you really want a collection of new SystemAreaFunctionalities where only one field is filled: SystemAreaCode? Because of you use of singular property name, it seems you don't want a collection but only one item.
var result = currentContext.SystemAreas.Select(systemArea => new
{
Id = systemArea.Id,
Code = systemArea.Code,
...
// if you want a collection of SystemAreaFunctionalities
// where every SystemAreaFunctionality is filled with only SysemAreaCode
// do the following:
SystemAreaFunctionalities = systemArea.SystemAreaFunctionalities
.Select(systemAreaFunctionality => new SystemAreFunctionality
{
SystemAreaCode = systemAreaFunctionality.SystemAreaCode,
})
.ToList(), // DON'T FORGET THIS, OR YOU WON'T GET RESULTS!
})
.ToList()
}
I think the cause of your empty SystemAreaFunctionalities is because you forgot to do ToList().
Because you used ToList(), you automatically have the Count of the selected SystemAreaFunctionalities. There is no need to select this Count separately.
One of the slower parts of database queries is the transport of the selected data from the database management system to your local process. It is good practice to only select data you actually plan to use
You query is not very efficient because you select complete SystemAreaFunctionalities and fill only the SystemAreaCode. All other fields will be filled by default values. Apart from the slower query, you also give your callers the impression that they get properly filled SystemAreaFunctionalities. An improved version would be:
var result = currentContext.SystemAreas.Select(systemArea => new
{
// select only the properties you actually plan to use
Id = systemArea.Id,
Code = systemArea.Code,
...
// if you only need the SystemAreaCodes, select only that property
SystemAreaCodes = systemArea.SystemAreaFunctionalities
.Select(systemAreaFunctionality => systemAreaFunctionality.SystemAreaCode)
.ToList(),
})
.ToList()
};
Of courds, if you need mrre than only the SystemAreaCodes, but several SystemAreaFunctionalities, go ahead, select them:
...
SystemAreaFunctionalities = systemArea.SystemAreaFunctionalities
.Select(systemAreaFunctionality => new
{
// again: select only the properties you plan to use!
Id = systemAreaFunctionality.Id
SystemAreaCode = systemAreaFunctionality.SystemAreaCode,
})

An include should do the trick for you like so:
IEnumerable<SystemArea> result = (from sa in CurrentContext.systemarea.Include("systemareafunctionality")
select new SystemArea
{
SystemAreaId = sa.SystemAreaId,
SystemAreaCode = sa.SystemAreaCode,
SystemAreaType = sa.SystemAreaType,
SystemAreaDescription = sa.SystemAreaDescription,
SystemAreaCreatedDate = sa.SystemAreaCreatedDate,
SystemAreaUpdateDate = sa.SystemAreaUpdateDate,
SystemAreaStatus = sa.SystemAreaStatus,
Count = sa.systemareafunctionality.Count,
SystemAreaFunctionality = sa.systemareafunctionality.Select(e => new SystemAreaFunctionality { SystemAreaCode =e.SystemAreaCode })
}).ToList();

Related

LINQ how do I specify select certain columns in a one to many joins

I have a situation where I have a LINQ query. It has two joins (one to many) but it is bringing back all of the columns in the joined tables. I'm not sure how to create the LINQ query to only being back a few fields from the joined tables.
var data = from mc in ctx.MembershipChapters
where mc.PartitionKey == controllerStateManager.PartitionKey && mc.MembershipId == membershipId
join prd in ctx.Products on mc.ProductId
equals prd.Id into prods
from prd in prods.DefaultIfEmpty()
join oli in ctx.OrderLineItems on mc.OrderLineItemId equals oli.OrderLineItemId into olis
from oli in olis.DefaultIfEmpty()
select new
{
MembershipName = mc.Membership.Name,
Products = prods.Select(p => new {
ProductName = p.Name, ProductId = p.Id }),
OrderLineItems = olis.Select(o => new { OrderLineItemName = o.Description, OrderLineItemId = o.OrderLineItemId })
};
controllerStateManager.Data = data.ToList();
This does not work...I get an error: "o" is not in scope.
Basically the output should follow this:
MembershipChapter
---> OrderLineItems
----------> Products
I'm new to LINQ and I have been struggling on this for far too long.
If you have a one-to-many relationship, and you want the "one" items, each one with its zero or more subitems, like Schools with their zero or more Students; Customers with their zero or more Orders, or, as in your case: MembershipChapters with their OrderLineItems, consider to use one of the overloads of Queryable.GroupJoin.
If you start on the "many" side, and you want each item with its one parent item, so you want the Student with the School he attends, or the Order with the one and only Customer who placed the order, use one of the overloads of Queryable.Join.
I almost always use the overload that has a parameter resultSelector, so you can exactly define what should be in the result.
Requirement: given tables MembershipChapters, OrderLineItems and Products. There is a one-to-many relationship between MembershipChapters and OrderLineItems. Every MembershipChapter has zero or more OrderLineItems, every OrderLineItem belongs to exactly one MembershipChapter, namely the MembershipChapter that the foreign key refers to. There is a similar one to many relation between OrderLineItems and Products. Give me all (or some) MembershipChapters, each MembershipChapter with its zero or more OrderlineItems, and each OrderLineItem with its zero or more Products.
var result = dbContext.MemberShipChapters
.Where(membershipChapter => ...) // only if you don't want all MembershipChapters
.GroupJoin(dbContext.OrderLineItems,
membershipChapter => membershipChapter.Id, // from every membershipChapter get the primary key
orderlineItem => orderLineItem.MembershipChapterId, // from every OrderlineItem get the foreign key
// parameter resultSelector: from every MembershipChapter with its zero or more
// OrderLineItems, make one new:
(membershipChapter, orderLineItemsOfThisMembershipChapter) => new
{
// Select only the membershipChapter properties that you plan to use
Id = membershipChapter.Id,
Name = membershipChapter.Name,
...
// The zero or more OrderLineItems of this membershipChapter
OrderLineItems = orderLineItemsOfThisMembershipChapter
.Select(orderLineItem => new
{
// Select only the OrderLineItems that you plan to use:
Id = orderLineItem.Id,
...
// not needed, you already know the value
// MembershipChapterId = orderLineItem.MembershipChapterId,
})
.ToList(),
});
This is fairly straightforward. However, if you want to GroupJoin three tables, then this looks horrible, although it is doable.
Another method that looks simpler:
var result = dbContext.MemberShipChapters
.Where(membershipChapter => ...)
.Select((membershipChapter => new
{
Id = membershipChapter.Id,
Name = membershipChapter.Name,
...
OrderLineItems = dbContext.OrderLineItems
// keep only the OrderLineItems with a foreign key referring to this MembershipChapter
.Where(orderLineItem => orderLineItem.MemberShipChapterId == membershipChapter.Id)
.Select(orderLineItem => new
{
Id = orderLineItem.Id,
...
// do the same with the third table
Products = dbContext.Products
.Where(product => product.OrderLineItemId == orderLineItem.Id)
.Select(product => new
{
Id = product.Id,
Price = product.Price,
...
})
.ToList(),
})
.ToList(),
});
It is a little hard to read, but if the domain is linked correctly then I think you just want to end up with a query like this:
from ol in ctx.OrderLines where
ol.MembershipChapter.PartitionKey == controllerStateManager.PartitionKey
select new {ol.Whatever, ol.Product.Whatever};

EF core - the most effective query in this case

Sorry for the illegibility, I have to use code anonymization
I use EF Core and have 4 database entities:
class A
{
public int AId {get; set;}
public ICollection<AB> ABs {get; set;}
}
class AB
{
public int ABId {get; set;}
public A A {get; set;}
public B B {get; set;}
}
class B
{
public int BId {get; set;}
public ICollection<AB> ABs {get; set;}
public ICollection<C> Cs {get; set;}
}
class C
{
public int CId {get; set;}
public int UId {get; set;}
public B B {get; set;}
}
I am looking for the most effective query. I get AId and UId. I would like to get A where AId.AId = AId with all C where C.UId = UId. If there is no C with UId for some B (in A->AB->B->C) I would like to return new C {B = B} for that B.
I wrote something like this and later operate on this object. But I think it is not effective, because I load all UQ from database
var result = context.QS
.Where(qs => qs.QSId == QSId)
.Include(qs => qs.QSQ)
.ThenInclude(qsq => qsq.Q)
.ThenInclude(q => q.UQ)
.FirstOrDefault();
When using entity framework always use Select to select items, and Select only the properties that you plan to use. Only use Include if you plan to update the fetched items.
A database management system is extremely optimized to select values, way better than your local process. The slower part of a database query is the transfer of the selected values from the DBMS to your local process. Hence it is wise to select only the properties that you actually plan to use.
For example. Suppose you have a schools database, with a straightforward one-to-many relation between Schools and Students: every School has zero or more Students, and every Student attends exactly one School, namely the School that foreign key SchoolId refers to.
Now if you query "School [10] with all its 2000 Students", you know that every Student of this School will have a foreign key SchoolId with a value of 10. If you would use Include, you would transfer this value 10 2000 times, while you already fetched the value, namely the primary key of School.
Another advantage of using Select is that you can decouple your database tables from the results. If you later will change your database table, for instance add a column, or rename it, then the users of your queries won't have to change as long as they don't need the value of the newly added column.
virtual ICollection
A side remark: shouldn't the properties that express the relations between your tables not be virtual?
In entity framework the columns of your tables are represented by non-virtual properties; the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...).
Hence a foreign key is non-virtual; the object that the foreign key refers to should be declared virtual.
Back to your question
Requirement: Given a QSId and a UId, get all QSses where QSId.QSId = QSId, each QS with all their UQs where UQ.UId = UId, or a default UQ if there are no UQs.
There are two methods for this: user the ICollection, in which case entity framework will know
Method using the ICollections
This method works with full entity framework. Sometimes I hear that EF core doesn't support this. You know core better than I do, so maybe you should edit this part of the answer.
int QSId = ...
int UId = ...
var result = dbContext.QSses
.Where(qs => qs.Id == QSId)
.Select(qs => new
{
// Select only the QS properties that you plan to use:
Id = qs.Id, // not really needed, you already know that it equals QSId
Name = qs.Name,
...
UQs = qs.QSQs.SelectMany(qsq => qsq.UQs)
.Where(uq => uq.UId == Uid)
.Select(uq => new UQ
{
// Only the properties that you plan to use:
Id = uq.UqId,
Name = uq.Name,
...
})
.ToList(),
})
The problem is, that if your qs has no UQs at all, you want a default value. The smartest is to transfer the empty collection to your local process, and if you detect that it is empty, use a new default item.
Continuing the code above:
// Transfer to local process:
.AsEnumerable()
.Select(qs => new
{
Id = qs.Id
Name = qs.Name,
...
UQs = qs.UQs.Any() ? qs.UQs : new List<UQ>() {new UQ {...}},
});
Whenever possible I use the method with the virtual ICollections, instead of doing a GroupJoin yourself. IMHO it looks way more similar to your requirements.
Do the (Group-)Joins yourself
Some people don't trust that entity framework creates the correct (Group-)Joins, or they use a version of entity framework (or provider) that doesn't support using the virtual ICollections. In that case you'll have to do the GroupJoins yourself.
When going from one to many, use GroupJoin. When going from many-to-one use Join.
So To the junction table: GroupJoin, from the junction table to your UQ use Join.
var result = dbContext.QSses
.Where(qs => qs.Id == QSId)
.GroupJoin(dbContext.QSQs,
qs => qs.Id, // from every QS take the primary key
qsq => qsq.QSId, // from every QSQ take the foreign key to the QS
// parameter resultSelector: from every QS with its zero or more QSQs make one new
(qs, qsqs) => new
{
Id = qs.Id,
Name = qs.Name,
...
// To get the UQs belonging to these qsqs, do a Join
// not a GroupJoin, every qsq has exactly one UQ.
UQs = qs.QSQs.Join(dbContext.UQs.Where(uq => uq.UId == Uid),
qsq => qsq.UQId, // take the foreign key to the UQ
uq => uq.Id, // take the primary key
// parameter resultSelector, take the qsq with its matching uq
(qsq, uq) => new
{
Id = uq.UqId,
Name = uq.Name,
...
})
.ToList(),
})
.AsEnumerable()
.Select(... See above);
Is this what you need? I got only 1 data of TableC.
var tableA = db.TableA.Where(x => x.TableAid == 2)
.Include(x => x.TableAb)
.Include("TableAb.TableB")
.Include("TableAb.TableB.TableC")
.FirstOrDefault();

Query Entity Framework using C# and Navigation Properties

First of all I am new to both C# and EF.
I have created a number of entities with the the Model designer in VS 2015 CE and set the relationships.
I would like to query the entities to return all the customers for a specific Contract (e.g. Contract_ID = 1), along with related properties from the CustomerLocker and ContractCustomer entities (For the CustomerLocker Entity if they are present, or null if they are not). I also have the LockerNumber value from the Contract entity (e.g. 100).
I would be grateful if someone can help with the LINQ query required to select the properties I require. I would prefer to be able to use navigation properties if possible.
So far I am able to select the customers but not able to select properties from the CustomerLocker entity.
var myCustomers = (from cc in context.ContractCustomers
where cc.Contract_ID.Equals(contractID)
select new
{
Licencee = cc.IsLicencee,
Added = cc.AddedDate,
Firstname = cc.Customer.FirstName,
Lastname = cc.Customer.LastName,
DOB = cc.Customer.DateOfBirth,
Postcode = cc.Customer.PostCode,
CustomerNumber = cc.CustomerNumber
}
)
entities shown in VS Model Designer
You could get the HasCard from CustomerLockers by filtering on LockerNumber;
CustomerLockers = cc.Customer.CustomerLockers
The query;
var myCustomers = (from cc in context.ContractCustomers
where cc.Contract_ID.Equals(contractID)
select new
{
Licencee = cc.IsLicencee,
Added = cc.AddedDate,
Firstname = cc.Customer.FirstName,
Lastname = cc.Customer.LastName,
DOB = cc.Customer.DateOfBirth,
Postcode = cc.Customer.PostCode,
CustomerNumber = cc.CustomerNumber,
CustomerLockerHasCard = cc.Customer.CustomerLockers
.Where(x => x.LockerNumber == 1000)
.Select(x => x.HasCard)
}
)
Also, I suggest you to define model classes as known type instead of using anonymous type.
An option would be to get the list of customers instead of just the customer's number :
var myCustomers = (from cc in context.ContractCustomers
where cc.Contract_ID.Equals(contractID)
select new
{
Licencee = cc.IsLicencee,
Added = cc.AddedDate,
Firstname = cc.Customer.FirstName,
Lastname = cc.Customer.LastName,
DOB = cc.Customer.DateOfBirth,
Postcode = cc.Customer.PostCode,
CustomerNumber = cc.CustomerNumber,
listOfCustomers = cc.Customer.ToList() // <-Here, a list
}
)
Then you can use a loop :
foreach(var customer in myCustomers.listOfCustomers)
{
var listOfLockers = customer.CustomerLockers.ToList();
}
But this is more a beginner's way, remember it's always better to take everything you need in a single query, like Stormcloack's answer.
This answer is just to show you how you can dig in the entitys the easy way.

Reuse Object Creation Code With Entity Framework - Project to new class querying only the required columns

Can the select new MobileTeamModel be refactored into a reusable method and still be read by Entity Framework? I have quite a bit of requests that need this same data and would like to reuse it but I know Entity Framework complains about this type of stuff.
var teams = new MobileListResponse<MobileTeamModel>
{
List = (from e in _divisionsRepository.DataContext.DivisionTeams.Where(#where.Expand())
orderby e.Team.Name
select new MobileTeamModel
{
Id = e.Id,
Name = e.Team.Name,
Status = e.Status,
Paid = e.Paid,
Division = e.Division.Name,
City = e.Team.TeamAddress.Address.City,
StateRegion =
e.Team.TeamAddress.Address.StateRegionId.HasValue
? e.Team.TeamAddress.Address.StateRegion.Name
: null
}).ToList()
};
EDIT
The idea is to implement the select new MobileTeamModel { ... } in a reusable way, while having EF only query the required columns.
Probably a nice extension method:
public static class MobileTeamModelExtensions
{
public static IEnumerable<MobileTeamModel> ToMobileTeamModels
(this IQueryable<DivisionTeam> instance)
{
var result = instance.Select(e =>
select new MobileTeamModel
{
Id = e.Id,
Name = e.Team.Name,
Status = e.Status,
Paid = e.Paid,
Division = e.Division.Name,
City = e.Team.TeamAddress.Address.City,
StateRegion =
e.Team.TeamAddress.Address.StateRegionId.HasValue
? e.Team.TeamAddress.Address.StateRegion.Name
: null
}).ToList()
return result;
}
}
So you could:
var query = _divisionsRepository.DataContext.DivisionTeams
.Where(#where.Expand());
var list = query.ToMobileTeamModels();
var query = query.Where(<more where>);
var list2 = query.ToMobileTeamModels();
You simply have to add a constructor or a factory (static method that return a MobileTeamModel) and receives a Team.
Then you can make a simpler query like this:
select new MobileTeamModel(e.Team) // parametreized constructor
or this
select MobileTeamModel.FromTeam(e.Team) // factory
Finallyyou can use something like AutoMapper or ValueInjecter to project the data returned by the query to another class by using conventions or mapping.
NOTE: I can't see how your clasees look like, but this is the basic idea.
EDIT: getting EF to query only the necessary columns
As for your comment, you want to reuse the construction part, and have EF only query the required columns, and not he whole entities.
Another good explanation here: Los Techies. Efficient querying with LINQ, AutoMapper and Future queries
To do so you must use Automapper, with Queryable Extensions.
WHen you use this extensions, you can use a mapping from a query result to the destination class, and have EF only query the mapped columns.

How can I query this hierarchical data using LINQ?

I have 3 kinds of objects: Agency, BusinessUnit and Client (each with their own respective table)
In terms of hierarchy, Agencies own BusinessUnits, and BusinessUnits own Clients.
I have 3 C# POCO Objects to represent them (I usually select new {} into them, rather than use the LINQ generated classes):
public class Agency
{
public IEnumerable<BusinessUnit> BusinessUnits { get; set; }
}
public class BusinessUnit
{
public IEnumerable<Client> Clients { get; set; }
}
public class Client
{
public int NumberOfAccounts { get; set; }
public Decimal AmountOfPlacement { get; set; }
public Decimal AvgBalance { get; set; }
public Double NeuPlacementScore { get; set; }
}
You can see that Agencies contain a list of BusinessUnits, and BusinessUnits contain a list of Clients.
I also have a mapping table called BAC_Map in the database which says which owns which, and it looks something like this:
How can I construct a query, so I can query for and return a list of Agencies? Meaning that, I want each Agency to have its list of BusinessUnit objects set, and I want the list of BusinessObjects to have its list of Clients set.
I can do basic LINQ queries, but this is a little over my head concerning the Map table and the multiple? queries.
How could I construct a method like GetAllAgencies() which would query, for not only all agencies, but populate its BusinessUnits that Agency owns, and the Clients those BusinessUnits own?
Edit: Any tips or info is appreciated. Do I need to do joins? Does this need to be multiple queries to return an Agency list, with its submembers populated?
If you drop all four tables (Agency, BusinessUnit, Client, Map) on the linq to sql designer, and draw relationships from Map to the other three, there will be some useful properties on Map.
//construct a query to fetch the row/column shaped results.
var query =
from m in db.map
//where m.... ?
let a = m.Agency
let b = m.BusinessUnit
let c = m.Client
// where something about a or b or c ?
select new {
AgencyID = a.AgencyID,
AgencyName = a.Name,
BusinessUnitID = b.BusinessUnitID,
ClientID = c.ClientID,
NumberOfAccounts = c.NumberOfAccounts,
Score = c.Score
};
//hit the database
var rawRecords = query.ToList();
//shape the results further into a hierarchy.
List<Agency> results = rawRecords
.GroupBy(x => x.AgencyID)
.Select(g => new Agency()
{
Name = g.First().AgencyName,
BusinessUnits = g
.GroupBy(y => y.BusinessUnitID)
.Select(g2 => new BusinessUnit()
{
Clients = g2
.Select(z => new Client()
{
NumberOfAccounts = z.NumberOfAccounts,
Score = z.Score
})
})
})
.ToList();
If approriate filters are supplied (see the commented out where clauses), then only the needed portions of the tables will be pulled into memory. This is standard SQL joining at work here.
I created your tables in a SQL Server database, and tried to recreate your scenario in LinqPad. I ended up with the following LINQ statements, which basically result in the same structure of your POCO classes:
var map = from bac in BAC_Maps
join a in Agencies on bac.Agency_ID equals a.Agency_ID
join b in BusinessUnits on bac.Business_Unit_ID equals b.Business_Unit_ID
join c in Clients on bac.Client_ID equals c.Client_ID
select new
{
AgencyID = a.Agency_ID,
BusinessUnitID = b.Business_Unit_ID,
Client = c
};
var results = from m in map.ToList()
group m by m.AgencyID into g
select new
{
BusinessUnits = from m2 in g
group m2 by m2.BusinessUnitID into g2
select new
{
Clients = from m3 in g2
select m3.Client
}
};
results.Dump();
Note that I called map.ToList() in the second query. This actually resulted in a single, efficient query. My initial attempt did not include .ToList(), and resulted in nine separate queries to produce the same results. The query generated by the .ToList() version is as follows:
SELECT [t1].[Agency_ID] AS [AgencyID], [t2].[Business_Unit_ID] AS [BusinessUnitID], [t3].[Client_ID], [t3].[NumberOfAccounts], [t3].[AmountOfPlacement], [t3].[AvgBalance], [t3].[NeuPlacementScore]
FROM [BAC_Map] AS [t0]
INNER JOIN [Agencies] AS [t1] ON [t0].[Agency_ID] = [t1].[Agency_ID]
INNER JOIN [BusinessUnits] AS [t2] ON [t0].[Business_Unit_ID] = [t2].[Business_Unit_ID]
INNER JOIN [Clients] AS [t3] ON [t0].[Client_ID] = [t3].[Client_ID]
Here is a screenshot of the results:
alt text http://img411.imageshack.us/img411/5003/agencybusinessunitclien.png
If you are doing this with direct LINQ to SQL, there is no way to do this without some kind of recursion, whether you do it yourself or you hide it behind an extension method. Recursive SQL is very bad (many round trips, many single queries).
There are two options here. One is to pull the entire table(s) with the hierarchy into memory and use LINQ to Objects on it. Leave the "details" tables in SQL. If you have less than several thousand entities, this is probably the most efficient way to go. You can keep a single copy of the table(s) in cache and refresh them when necessary. When you need to fetch more detailed data from the DB for a single record, you can reattach that entity from your cached hierarchy to a new DataContext and fetch it.
The other option is to use a more complex relationship model in your database. Storing parent only by nature demands recursion, but you can use the adjacency list model to construct a single query which can span many levels of inheritance. This will mean your LINQ to SQL queries become less intuitive (querying against Entity.Right and Entity.Left isn't quite as pretty as Parent or Children...) but you can do in one query what might take hundreds or thousands in the literal recursive approach.

Categories

Resources