How do I create multiple joins using LINQ extension methods? - c#

I'm having trouble using LINQ method calls with multiple joins. I'm trying to do something like this:
if (!isDepSelect)
{
query = (from Items in db.DEPARTMENTs
select Items);
}
else
{
query = (from Items in db.DEPARTMENTs
from gDept in db.DEPT_PROFILE
from wAccess in db.WEB_ACCESS
where Items.DEPT_CODE == gDept.DEPT_CODE && gDept.USER_ID == wAccess.USER_ID && wAccess.EMP_ID == id
select Items);
}
I had done this:
IQueryable<DEPARTMENT> query = db.DEPARTMENTs;
if (isDepSelect)
{
query = query.Join(db.DEPT_PROFILE,depts => depts.DEPT_CODE,prof => prof.DEPT_CODE,(depts, prof) => depts);
}
But now I don't know how to add the JOIN of DEPT_PROFILE table with the WEB_ACCESS table and the condition of the EMP_ID = id.
The reason I'm doing this is that the isDepSelect boolean is not the only condition that this query will change its relations and I need someway to add this relations without repeating my LINQ for each of my conditions.
Thank you for your time.

Try with,
List<DEPARTMENTs> list = db.DEPARTMENTs.Join(db.DEPT_PROFILE, dept => dept.DEPT_CODE, prof => prof.DEPT_CODE, (dept,prof) => new {dept, prof})
.Join(Wdb.WEB_ACCESS, depts => depts.prof.USER_ID,web => web.USER_ID,(depts,web) => new { depts, web})
.Where(result => result.web.EMP_ID== id).Select(s => s.depts.dept).ToList<DEPARTMENTs>();

If you have your associations setup, you can do this without any joins in your code at all:
query = db.DEPARTMENTs
.Any(item => item.DEPT_PROFILEs
.Any(gDept => gDept.WEB_ACCESSs
.Any(wAccess => wAccess.EMP_ID == id)));
Of course this is assuming a 1-m relationship between each of the objects in the graph. You can eliminate some of the Any methods if there are 1-0..1 relationships in the graph as necessary.

you should use the equals operator...
query = from Items in db.DEPARTMENTs
from gDept in db.DEPT_PROFILE
join wAccess in db.WEB_ACCESS on
gDept.DEPT_CODE equals Items.DEPT_CODE
select Items;
thats just a snippet of your example query, but you can see how i am using the join operator to introduce a 2nd table and the equals operator to declare the joining columns.

This should work:
query = (from Items in db.DEPARTMENTs
join gDept in db.DEPT_PROFILE
on Items.DEPT_CODE equals gDept.DEPT_CODE
join wAccess in db.WEB_ACCESS
on gDept.USER_ID equals wAccess.USER_ID
where wAccess.EMP_ID == id
select Items);

Related

LINQ to SQL - multiple left join, group by, left join on same table, count

I have a SQL query that is pretty simple, but turns out to be a nightmare to transform into LINQ to SQL (or LINQ to Entity). I've been reading lots of questions and articles and I just cannot manage to make it work. Here is the SQL query (Items table 1-N to Operations table, Items has a ParentItemId column):
select
i.Id
, i.Code
, count(oAdd.ItemId) "Added"
, count(oRem.ItemId) "Removed"
, count(iChild.Id) "Existing"
from items i
left join operations oAdd on oAdd.ItemId = i.Id and oAdd.OperationTypeId = 10
left join operations oRem on oRem.ItemId = i.Id and oRem.OperationTypeId = 20
left join items iChild on iChild.ParentItemId = i.Id
group by
i.Id
, i.Code
Now after all the reseach and multiple attemps, I came up with the following code, which compiles but throws a EntityCommandCompilation exception ("The nested query is not supported. Operation1='GroupBy' Operation2='MultiStreamNest'"):
var query =
from item in dbContext.Items
join oAdd in dbContext.Operations on item.Id equals oAdd.ItemId into oAddJoin
join oRem in dbContext.Operations on item.Id equals oRem.ItemId into oRemJoin
join oChild in dbContext.Items on item.Id equals oChild.ParentItemId into oChildJoin
from oAddLeftJoin in oAddJoin.Where(o => o.OperationTypeId == (int)OperationTypes.AddTo).DefaultIfEmpty()
from oRemLeftJoin in oRemJoin.Where(o => o.OperationTypeId == (int)OperationTypes.RemoveFrom).DefaultIfEmpty()
from oChildLeftJoin in oChildJoin.DefaultIfEmpty()
group oChildLeftJoin by new
{
ItemId = item.Id,
ItemCode = item.Code,
Added = oAddJoin.Count(),
Removed = oRemJoin.Count(),
Existing = oChildJoin.Count()
}
into oChildLeftJoinGrouped
select new
{
oChildLeftJoinGrouped.Key.ItemId,
oChildLeftJoinGrouped.Key.Added,
oChildLeftJoinGrouped.Key.Removed,
oChildLeftJoinGrouped.Key.Existing
};
var summaryList = query.ToList();
I've also tried not grouping, not "joining into" but selecting from where instead, not putting the count in the group by but in the select new, not grouping by added/removed/existing. Nothing works, and the more I try the less I feel I understand what this is about. It worked only once but without the "Existing" count (count of child items, i.e. join on the same table - see SQL query above).
Seems to me like this is an easy SQL query. Should I put that in a view instead? Is it even possible to achieve this with LINQ (if possible without subqueries)?
Thank you for your help!
EDIT 1
The code below works but if a parent has N children it will appear N times in the result. Because there is no group by.
var query =
from item in dbContext.Items
join oAdd in dbContext.Operations on item.Id equals oAdd.ItemId into oAddJoin
join oRem in dbContext.Operations on item.Id equals oRem.ItemId into oRemJoin
from oAddLeftJoin in oAddJoin.Where(o => o.OperationTypeId == (int)OperationTypes.AddTo).DefaultIfEmpty()
from oRemLeftJoin in oRemJoin.Where(o => o.OperationTypeId == (int)OperationTypes.RemoveFrom).DefaultIfEmpty()
let existingCount = dbContext.Items.Count(i => i.ParentItemId == item.Id)
select new
{
item,
Added = oAddJoin.Count(),
Removed = oAddJoin.Count(),
existingCount
};
var summaryList = query.ToList();
EDIT 2
Actually, below is the one which works and return good values. Even the SQL Query above is wrong. Sorry about the spam.
var query =
from item in dbContext.Items
let addedCount = dbContext.Operations.Count(o => o.ItemId == item.Id && o.OperationTypeId == (int)OperationTypes.AddTo)
let removedCount = dbContext.Operations.Count(o => o.ItemId == item.Id && o.OperationTypeId == (int)OperationTypes.RemoveFrom)
let existingCount = dbContext.Items.Count(i => i.ParentItemId == item.Id)
select new
{
item,
Added = addedCount,
Removed = removedCount,
Existing = existingCount
};
var summaryList = query.Distinct().ToList();
EDIT 3
The super ugly SQL query that works:
select distinct
i.Id
, i.Code
, (select count(*) from operations oAdded where oAdded.itemid = i.id and oAdded.operationtypeid = 10) "Added"
, (select count(*) from operations oAdded where oAdded.itemid = i.id and oAdded.operationtypeid = 20) "Removed"
, (select count(*) from items ic where ic.ParentItemId = i.id) "Existing"
from items i
If this is Entity Framework (you said LINQ to Entity), then you could make the operations and item tables navigation properties of item. Then, you could use a view model to calculate your counts. Something like:
public class ViewModel : Item
{
public int Added {get;set;}
public int Removed {get;set;}
public int Existing {get;set;}
public ViewModel(Item i) {
this.id = i.id;
this.code = i.code;
this.Added = i.operations.Where(o => o.operationTypeID == 10).Count;
this.Removed = i.operations.Where(o => o.operationTypeID == 20).Count;
this.Existing = i.Items.Count;
}
}
Then your query would just be:
dbContext.Items.Select(i => new ViewModel(i)).ToList();

Linq query w/ sub query and max

How do I rewrite this SQL into a Linq query?
Plain SQL
SELECT *
FROM contracts
INNER JOIN
(SELECT contractid, max(date) date
FROM contractlogs GROUP BY contractId) b
ON contracts.id = b.contractId
Attempt at Linq
from c in _db.Contracts
join sub in (from cl in _db.ContractLogs
group cl by cl.contractId into g
select new { contractId = g.contractId, changedate = g.Max(x => x.date)})
on c.id equals sub.contractId
select new { c, cl }
Goal of the query is to select all contracts w/ their newest update (first) (in contractLogs). I'm currently stumped on how the select would work. Ideally i'm trying to return an object with c & cl.
You can get the most recent log by sorting them in descending order and taking the first one:
from c in _db.Contracts
let mostRecentContractLog = c.ContractLogs
.OrderByDescending(cl => cl.date)
.FirstOrDefault()
select new { c, mostRecentContractLog }
As you see, I assume you have a navigation property Contract.ContractLogs. It's always strongly recommended to work with navigation properties in stead of manually coded joins.
The most literal translation is going to involve you calling groupby on ContractLogs and then joining that into Contacts. I think the ordering of your operations in your LINQ attempt is a little off however I don't often use the query syntax so I'm not positive about that. Regardless, I think you'd prefer something like this;
_db.ContractLogs.GroupBy(x => x.contractId).Select(x => new { contractid = x.Key, changedate = x.Max(y => y.date) })
With that you can do the join into _db.Contracts but I think you could write it more simply with a where though that might be less optimized by the LINQ to SQL provider. Anyway, just completing the example with a join;
OldQuery.Join(_db.Contracts, cl => cl.contractid,
c => c.contractid, (cl, c) => cl);
In cases like this it's often easier to write the query and subquery separately:
var subQuery =
from cl in _db.ContractLogs
group cl by cl.contractId into g
select new { contractId = g.Key, date = g.Max(cl => cl.date) };
var query =
from c in _db.Contracts
join cl in subQuery on c.contractId equals cl.contractId
select new { contract = c, cl.date };
You can try this:
from c in _db.Contracts
select new
{
c,
cl = _db.ContractLogs.Where(l => l.contractId == c.contractId).OrderByDescending(l => l.date).FirstOrDefault()
}

linq to entities, a where in where clause? (inner where)

I have a table with a one to many mapping to a table that has a many to many mapping to another table. I'd like to do the following:
var results = context.main_link_table
.Where(l => l.some_table.RandomProperty == "myValue" &&
l.some_table.many_to_many_table
.Where(m => m.RandomProperty == "myValue"));
How can I achieve this? The first part will work but when trying it without the 'inner WHERE', I can't access the many_to_many_table's properties, but the "inner where" obviously won't compile. I basically want to achieve something like the following SQL query:
SELECT * from main_link_table
INNER JOIN some_table AS t1 ON t1.association = main_link_table.association
INNER JOIN many_to_many_table AS t2 ON t2.association = some_table.association
WHERE t1.RandomProperty = 'MyValue' AND t2.RandomProperty = 'MyValue'
It's seemingly simple but I can't find a way to achieve it in one single line of linq - using multiple lines to achieve the desired effect returns too much results and I end up having to loop through them. I also tried stuff like:
var results = main_link_tbl.Include("some_table.many_to_many_table")
.Where(l => l.some_table.many_to_many_table.<property>
== "MyValue")
But at this point I can't select a property of many_to_many_table unless I add a FirstOrDefault(), which nullifies the effect since it won't search through all the records.
What did work, but requires multiple lines of code and in the background returns too many results in the SQL query built by the linq-to-entities framework:
var results = db.main_link_table.Include("some_table")
.Include("some_table.many_to_many_table")
.Where(s => s.some_table.RandomProperty
== "myValue")
.Select(s => s.some_table);
foreach(var result in results) {
var match_data = result.Where(s => s.many_to_many_table.RandomProperty
== "myValue");
}
This piece of code will return all rows inside some_table that match the first Where condition and then applies the next Where condition, while I obviously only need a single row where the many_to_many_table.RandomProperty equals myValue.
It should work if you change the inner Where to Any:
var results = context.main_link_table
.Where(l => l.some_table.RandomProperty == "myValue" &&
l.some_table.many_to_many_table
.Any(m => m.RandomProperty == "myValue"));
If you want to do a join, why don't you just do a join?
var query = from main in context.MainLinks
join t1 in context.Some on main.Association equals t1.Association
where t1.RandomProperty == "MyValue"
join t2 in context.ManyToMany on t1.Association equals t2.Association
where t2.RandomProperty == "MyValue"
select new { main, t1, t2 };
That should achieve exactly what your SQL does...
from link in db.main_link_table
join s in db.some_table on link.association1 = s.association
join m in db.many_to_many_table on link.association2 = m.association
where s.X = 'MyValue' AND m.Y = 'MyValue'
select m; // or s or link or both 3 as you want

Crazy Query need some feedback

var query =context.Categories.Include("ChildHierarchy")
.Where(c =>
context.CategoryHierarchy.Where(ch => ch.ParentCategoryID == ch.ParentCategoryID)
.Select(ch => ch.ChildCategoryID).Contains(c.CategoryID));
Questions:
I need to include some data from another Navigation Propery (".Include("otherprop")")
Is it possible to do a select new after all of this?
Thanks
The title to your question intrigued me with the words "Crazy Query", and yes, you're right, it is a bit crazy.
You have a .Where(...) clause with the following predicate:
ch => ch.ParentCategoryID == ch.ParentCategoryID
Now that's going to always be true. So I guess that you're trying to do something else. I'll have a crack at what that might be at the end of my answer.
I then did some cleaning up of your query to get a better idea of what you're doing. This is what it now looks like:
var query =
context
.Categories
.Where(c => context
.CategoryHierarchy
.Select(ch => ch.ChildCategoryID)
.Contains(c.CategoryID));
So rather than use nested queries I would suggest something like this might be better in terms of readability and possibly performance:
var query =
from c in context.Categories
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID into ghs
where ghs.Any()
select c;
This gives the same results as your query so hopefully this is helpful.
I do get the impression that you're trying to do a query where you want to return each Category along with any child categories it may have. If that's the case here are the queries you need:
var lookup =
(from c in context.Categories
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID
select new { ParentCategoryID = h.ParentCategoryID, Category = c, }
).ToLookup(x => x.ParentCategoryID, x => x.Category);
var query =
from c in context.Categories
select new { Category = c, Children = lookup[c.CategoryID], };
The lookup query first makes a join on categories and the category hierarchies to return all children categories and their associated ParentCategoryID and then it creates a lookup from ParentCategoryID to a list of associated Category children.
The query now just has to select all categories and perform a lookup on the CategoryID to get the children.
The advantage of using the .ToLookup(...) approach is that it easily allows you to include categories that don't have children. Unlike using a Dictionary<,> the lookup does not throw an exception when you use a key that it hasn't got a value for - instead it returns an empty list.
Now, you can add back in the .Include(...) calls too.
var lookup =
(from c in context.Categories
.Include("ChildHierarchy")
.Include("otherprop")
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID
select new { ParentCategoryID = h.ParentCategoryID, Category = c, }
).ToLookup(x => x.ParentCategoryID, x => x.Category);
var query =
from c in context.Categories
.Include("ChildHierarchy")
.Include("otherprop")
select new { Category = c, Children = lookup[c.CategoryID], };
Is that what you're after?
1) Then add it - context.Categories.Include("ChildHierarchy").Include("OtherCollection");
2) Absolutely, yes
var query = context.Categories
.Include("ChildHierarchy")
.Include("OtherProp")
.Where(c => context.CategoryHierarchy.Where(ch => ch.ParentCategoryID == ch.ParentCategoryID)
.Select(ch => ch.ChildCategoryID).Contains(c.CategoryID))
.Select(c => new { c.A, c.B, c.etc });

Linq query to display a proper sort order

Table 1: Lookups
LookUpID
LookUpName
Desc
DisplayOrder
Table 2: BillingRates
BillingRateID
BillingRate
ClientID
LookupID
I want the lookup name to be displayed (sort by Bill rate)
DataContext DataContext1 = new DataContext1(AppSettings.ConnectionString);
return ( from Lookups in DataContext1.Lookups
join BillingRates in DataContext1.BillingRates
on Lookups.LookupID equals BillingRates.LookupID
orderby BillingRates.BillingRate
select new
{
Lookups.LookupID,
Lookups.LookupName,
Lookups.Desc
}).Distinct();
It gave me all the row, so I used Distinct(); The lookup Name is still not based on billing rate.
I am new to LINQ. Any pointers would be appreciated.
Why not just do the OrderBy at the end?
return (from Lookups in DataContext1.Lookups
join BillingRates in DataContext1.BillingRates
on Lookups.LookupID equals BillingRates.LookupID
select new
{
Lookups.LookupID,
Lookups.LookupName,
Lookups.Desc,
BillingRates.BillingRate
})
.GroupBy(x => x.LookupID)
.Select(y => y.OrderByDescending(x => x.BillingRate).First())
.OrderByDescending(x => x.BillingRate);
EDIT: I am kind of confused but try the following and let me know if that helps.
First of all, if you have a foreign key relationship set up, LINQ will create the join for you automatically, so it would be just:
DataContext1.Lookups.Max(LkUp => LkUp.BillingRate.BillingRate)
Otherwise, (with the explicit join)
return ( from Lookups in DataContext1.Lookups
join BillingRates in DataContext1.BillingRates
on Lookups.LookupID equals BillingRates.LookupID
orderby BillingRates.BillingRate desc
select new
{
Lookups.LookupID,
Lookups.LookupName,
Lookups.Desc,
BillingRates.BillingRate
}).First();

Categories

Resources