Crazy Query need some feedback - c#

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 });

Related

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()
}

How do I create multiple joins using LINQ extension methods?

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);

Linq to SQL join and group

I have two tables in my database:
Town:
userid, buildingid
Building:
buildingid, buildingname
What i want is to populate a GridView like this:
But I don't want the buildings to be shown more than once. Here is my code:
var buildings = dc.Towns
.Where(t => t.userid == userid)
.GroupJoin(dc.Buildings,
t => t.buildingid,
b => b.buildingid,
(Towns, Buildings) => new
{
BuildningName = Buildings.First().buildingname,
Count = Towns.Building.Towns.Count()
});
gvBuildings.DataSource = buildings.ToList();
gvBuildings.DataBind();
New code which works:
var buildings = (from t in dc.Towns
where t.userid == userid
join b in dc.Buildings
on t.buildingid equals b.buildingid
into j1
from j2 in j1.DefaultIfEmpty()
group j2 by j2.buildingname
into grouped
select new
{
buildingname = grouped.Key,
Count = grouped.Count()
});
gvBuildings.DataSource = buildings.ToList();
gvBuildings.DataBind();
var buildings = from t in dc.Towns
join b in dc.Buildings on t.buildingid equals b.buildingid into j1
from j2 in j1.DefaultIfEmpty()
group j2 by b.buildingname into grouped
select new { buildingname = grouped.key, Count = grouped.Count()}
I think this should do it. I have not tested it so it might give error but it will be something like this.
Wouldn't something like this do it?
Users
.Select(User => new {User, User.Building})
.GroupBy(x => x.Building)
.Select(g=> new {Building = g.Key, Count = g.Count()})
According to my experience with Linq to SQL, when the expression is becoming complicated it is better to write a stored procedure and call it with Linq to SQL. In this way you get better maintainability and upgradeability.
Rather than an option to pure SQL, I see “Linqu to SQL” as a tool to get hard typed object representation of SQL data sets. Nothing more.
Hope it helps you.

How to add a where clause on a linq join (lambda)?

I have two database tables Contact (Id, Name, ...) and ContactOperationalPlaces (ContactId, MunicipalityId), where a contact can be connected to several ContactOperationalPlaces.
What I'm trying to do is to build a query (ASP .NET, C#) with IQueryable, that only selects all the contacts that exists in the ContactOperationalPlaces table, with a given MunicipalityId.
The sql query looks like this:
select * from Contacts c
right join ContactOperationPlaces cop on c.Id = cop.ContactId
where cop.MunicipalityId = 301;
With linq it would look something like this:
//_ctx is the context
var tmp = (from c in _ctx.Contacts
join cop in _ctx.ContactOperationPlaces on c.Id equals cop.ContactId
where cop.MunicipalityId == 301
select c);
So, I know how to do this if the point was to select all of this at once, unfortunately it's not. I'm building a query based on user input, so I don't know all of the selection at once.
So this is what my code looks like:
IQueryable<Contacts> query = (from c in _ctx.Contacts select c);
//Some other logic....
/*Gets a partial name (string nameStr), and filters the contacts
so that only those with a match on names are selected*/
query = query.Where(c => c.Name.Contains(nameStr);
//Some more logic
//Gets the municipalityId and wants to filter on it! :( how to?
query = query.where(c => c.ContactOperationalPlaces ...........?);
The difference with the two where statements is that with the first one, each contact has only one name, but with the latter a contact can contain several operational places...
I have managed to find one solution, but this solution gives me an unidentyfied object, that contains both of the tables. And I don't know how to proceed with it.
query.Join(_ctx.ContactOperationPlaces, c => c.Id, cop => cop.ContactId,
(c, cop) => new {c, cop}).Where(o => o.cop.municipalityId == 301);
The object returned from this expression is System.Linq.Iqueryable<{c:Contact, cop:ContactOperationalPlace}>, and it can't be cast to Contacts...
So, that's the issue. The answer is probably pretty simple, but I just can't find it...
You create an anonymous type with both objects before your where clause and filter it on ContactOperationPlaces value. You just have to select the Contact after that.
query.Join(_ctx.ContactOperationPlaces, c => c.Id, cop => cop.ContactId,
(c, cop) => new {c, cop}).Where(o => o.cop.municipalityId == 301)
.Select(o => o.c)
.Distinct();
You don't need to return new objects in the result selector function. The delegate provides both variables so you can choose one or the other, or some other variation (which would require a new object). Try this:
query.Join(_ctx.ContactOperationPlaces, c => c.Id, cop => cop.ContactId,
(c, cop) => c).Where(o => o.cop.municipalityId == 301);
can you just cast it to var and try to use intellisense on it?
var myCast = query.Join(_ctx.ContactOperationPlaces, c => c.Id, cop => cop.ContactId,
(c, cop) => new {c, cop}).Where(o => o.cop.municipalityId == 301);
Just a thought
I think it would be much easier if you start this as 2 different queries, then combine them. I'm assuming the relation is Contact (1 <-> many) Contactoperationplaces ? And in the end, you will be showing 1 item per Contactoperationplaces, not 1 item per Contact?
Do it like this:
IQueryable<Contacts> query = (from c in _ctx.Contacts select c);
...
query = query.Where(x=> x.Name.ToLower().Contains(nameStr.ToLower());
...
IQueryable<ContactOperationPlaces> query_2 =
(from c in _ctx.ContactOperationPlaces
where query.Where(x=> x.Name == c.Contact.Name).Count() > 0
select c);
//Now query_2 contains all contactoperationsplaces which have a contact that was found in var query
Conversely, there is a much easier way to do this, and that's by skipping the first part entirely.
IQueryable<ContactOperationPlaces> query_2 =
(from c in _ctx.ContactOperationPlaces
where c.Contact.Name.ToLower().Contains(strName.ToLower())
select c);
If you're using Entity Framework, you don't have to do any joins as long as you defined associations between the tables.
Now that I look at it, my second solution is far more efficient and easier. But if you need to do some other processing inbetween these commands, solution one works too :)
If you need more explanation, feel free to ask :)

How to cast a Linq Dynamic Query result as a custom class?

Normally, I do this:
var a = from p in db.Products
where p.ProductType == "Tee Shirt"
group p by p.ProductColor into g
select new Category {
PropertyType = g.Key,
Count = g.Count() }
But I have code like this:
var a = Products
.Where("ProductType == #0", "Tee Shirt")
.GroupBy("ProductColor", "it")
.Select("new ( Key, it.Count() as int )");
What syntax could I alter to produce identical results, i.e., how do I do a projection of Category from the second Linq statement?
I know in both that g and it are the same and represent the entire table record, and that I am pulling the entire record in just to do a count. I need to fix that too. Edit: Marcelo Cantos pointed out that Linq is smart enough to not pull unnecessary data. Thanks!
Why would you have to do it at all? Since you still have all of the information after the GroupBy call, you can easily do this:
var a = Products
.Where("ProductType == #0", "Tee Shirt")
.GroupBy("ProductColor", "it")
.Select(c => new Category {
PropertyType = g.Key, Count = g.Count()
});
The type of Products should still flow through and be accessible and the regular groupings/filtering shouldn't mutate the type that is flowing through the extension methods.

Categories

Resources