Combining LINQ queries with method syntax - c#

I'm looking to merge the following queries into a single call to the database.
string name = Item
.Where(x => x.ItemID == 180)
.Select(x => x.ItemName).FirstOrDefault();
int itemID = Item
.Where(x => x.ItemName == name && x.Other == 50)
.Select(x => x.ItemID).FirstOrDefault();
It's pretty much getting the name using it's ID, and using that name to get the ID of another row with the same name. The "Other" in this case narrows down the second query to a single result in this simplified example.

This should work:
int itemID = Item.Where(x => x.ItemName == Item.Where(y => y.ItemID == 180)
.Select(y => y.ItemName).FirstOrDefault() && x.Other == 50)
.Select(x=>x.ItemID)
.FirstOrDefault();

Here is a full working example if you choose to use a Join.
Example Data:
List<Item> Items = new List<Item>()
{
new Item { ItemId = 1, ItemName = "Item1", Other = 1 },
new Item { ItemId = 2, ItemName = "Item2", Other = 2 },
new Item { ItemId = 3, ItemName = "Item3", Other = 3 },
new Item { ItemId = 4, ItemName = "Item4", Other = 4 },
new Item { ItemId = 5, ItemName = "Item5", Other = 5 },
new Item { ItemId = 6, ItemName = "Item6", Other = 6 },
new Item { ItemId = 7, ItemName = "MyExpectedName", Other = 50 },
new Item { ItemId = 8, ItemName = "MyExpectedName", Other = 50 },
new Item { ItemId = 180, ItemName = "MyExpectedName", Other = 8 },
};
Example query with a Join:
var itemId = Items
.Join(Items.Where(a => a.ItemId == 180), x => x.ItemName, y => y.ItemName, (x, y) => x)
.Where(x => x.Other == 50)
.Select(x => x.ItemId)
.FirstOrDefault();
Note that you will get a different ItemID than from the provided ItemID of 180 in this example. In this case, since I statically created this list as an example, I know that I will get ItemID of 7 returned every time. However, if this query is being run against an underlying database it is possible that the database won't return the items ordered by the ItemID (unless you tell it explicitly to do so using the OrderedBy clause). So it is possible that ItemId = 8 could be returned from the database as the first record set rather than 7.

This finds the ItemName of the item with ItemID 180 and uses it to filter all items by this value.
int itemID = (from i in Item
where i.ItemName = (
from j in Item
where j.ItemID == 180
select j.ItemName).FirstOrDefault()
&& j.Other == 50
select i.ItemID).FirstOrDefault();
This is in query syntax which I find much easier to read, but it's not to complex to convert to method call syntax if you prefer. ReSharper can do it automagically if you have it.

It can be simplified:
var name = Items.FirstOrDefault(x => x.ItemID == 180)?.ItemName;
var id = Items.FirstOrDefault(x => x.ItemName == name && x.Other == 50)?.ItemID;

Something like this:
Item
.Join(Item, x => x.Name, x => x.Name, (x1, x2) => new {x1, x2})
.FirstOrDefault(arg => arg.x1.ItemID == 180 && arg.x2.Other == 50)?.x2.ItemID;
But do you realy need single query?

Related

LINQ to return distinct value by specific column

I have a Colour table like this:
Id Name VendorId
--------------------
1 Purple NULL
2 Blue NULL
3 Black NULL
4 Purple 1
5 Orange 1
6 Mauve 2
And I want to get all colours with VendorId as NULL, unless that colour Name has an entry with a VendorId attached, so e.g. for VendorId = 1 I'd like
Id Name VendorId
--------------------
2 Blue NULL
3 Black NULL
4 Purple 1
5 Orange 1
Noting that the Purple row Id 1 with the NULL VendorId is not on the list. For Id = 2 I'd get the rows 1,2,3 and 6
I thought initially to .Select .Distinct on Name but I need the entire object or at least the Id's
var result = _context.Colours
.Where(x => x.Vendor == null || x.Vendor.Id == vendorId)
.Select(x => x.Name)
.Distinct()
.ToList();
but if I use .Select(x => x.Id).Distinct() on then I get two instances of Purple
How can I achieve this in LINQ?
Edit:
I've just tried using
var result = _context.Colours
.Where(x => x.Vendor == null || x.Vendor.Id == vendorId)
.OrderByDescending(x => x.Id)
.GroupBy(x => x.Name)
.Distinct()
.ToList()
.Select(x => x.First())
.ToList();
Trying to get all null and id = 1, then order by descending Id and trying .GroupBy but I got Client side GroupBy is not supported.
Give this a go:
var vendorId = 1;
var result =
_context
.Colours
.Where(x => x.VendorId == null || x.VendorId == vendorId)
.OrderByDescending(x => x.VendorId)
.GroupBy(x => x.Name)
.SelectMany(xs => xs.Take(1))
.OrderBy(x => x.ID)
.ToList();
With this sample data:
var colours = new []
{
new { ID = 1, Name = "Purple", VendorId = (int?)null },
new { ID = 2, Name = "Blue", VendorId = (int?)null },
new { ID = 3, Name = "Black", VendorId = (int?)null },
new { ID = 4, Name = "Purple", VendorId = (int?)1 },
new { ID = 5, Name = "Orange", VendorId = (int?)1 },
new { ID = 6, Name = "Mauve", VendorId = (int?)2 },
};
I get this result:
This one is working for me and generating expected results:
var idOrNameNotWithId =
colours.Where(x => x.VendorId == cid ||
(!x.VendorId.HasValue && !colours.Where(x => x.VendorId == cid).Select(x => x.Name).ToList().Contains(x.Name)))
.ToList();
The idea behind it: anything with expect cid (colour ID) or anything that's null but only if, when you take anything with ID the name of the colour is not there.
And the full working method. I borrowed the colour collection definition from #Enigmativity's answer.
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var colours = new[]
{
new { ID = 1, Name = "Purple", VendorId = (int?)null },
new { ID = 2, Name = "Blue", VendorId = (int?)null },
new { ID = 3, Name = "Black", VendorId = (int?)null },
new { ID = 4, Name = "Purple", VendorId = (int?)1 },
new { ID = 5, Name = "Orange", VendorId = (int?)1 },
new { ID = 6, Name = "Mauve", VendorId = (int?)2 },
};
var cid = 2;
var idOrNameNotWithId =
colours.Where(x => x.VendorId == cid ||
(!x.VendorId.HasValue && !colours.Where(x => x.VendorId == cid).Select(x => x.Name).ToList().Contains(x.Name)))
.ToList();
}
}
}

LINQ Query to filter Data?

I have a table which contains Branch Ids and Department Ids. I have three branches and 1st branch has only 1 Department, the 2nd branch has two departments and 3rd branch has three Departments.
Now, I need to write a query to find branches which have department 1 but doesn't have dept. 2 and dept. 3.
This is just an example, I have a much more complex scenario which is very dynamic. I am using this example to put forward my question.
I am attaching the picture to understand the problem.
Here's query:
db.ConnectedBRDE.Where(x => x.DeptId == 1 && x.DeptId != 2)
.Select(x => x.BranchId)
.ToList();
This query is giving my all three Branches, whereas, I only need branch 1 because this is the only branch which doesn't have department 2.
This part && x.DeptId != 2 is wrong, I guess. What should I write here to make my filter working?
Stephen Muecke's comment does indeed work.
I have tested it in DotNetFiddle.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main(string[] args)
{
List<TestClass> lstOfItems = new List<TestClass>();
var itemOne = new TestClass(){BranchName = "Branch One", BranchId = 1, DeptId = 1};
var itemTwo = new TestClass(){BranchName = "Branch Two", BranchId = 2, DeptId = 1};
var itemThree = new TestClass(){BranchName = "Branch Two", BranchId = 2, DeptId = 2};
var itemFour = new TestClass(){BranchName = "Branch Three", BranchId = 3, DeptId = 1};
var itemFive = new TestClass(){BranchName = "Branch Three", BranchId = 3, DeptId = 2};
var itemSix = new TestClass(){BranchName = "Branch Three", BranchId = 3, DeptId = 3};
lstOfItems.Add(itemOne);
lstOfItems.Add(itemTwo);
lstOfItems.Add(itemThree);
lstOfItems.Add(itemFour);
lstOfItems.Add(itemFive);
lstOfItems.Add(itemSix);
var myList = lstOfItems.GroupBy(x => x.BranchName).Where(y => y.Count() == 1 && y.First().DeptId == 1).ToList();
foreach(var item in myList){
Console.WriteLine(item.Key);
}
// Output
// Branch One
}
}
public class TestClass
{
public string BranchName {get;set;}
public int BranchId {get;set;}
public int DeptId {get;set;}
}
Basically, once all of the records are grouped by BranchName property, then we want to count all of the records under each branch name.. and if the count equals 1 then that means that branch only has 1 record.. and then we find the DeptId of that record and if it equals 1 then that satisfies your condition.
I think bellowing code is what are you looking for
var list = new List<Model>();
list.Add(new Model(1, 1));
list.Add(new Model(2, 1));
list.Add(new Model(2, 2));
list.Add(new Model(3, 1));
list.Add(new Model(3, 2));
list.Add(new Model(3, 3));
var notValidBranchIds = list.Where(x => x.DeptId == 2 || x.DeptId == 3).Select(x => x.BranchId);
var result = list.Where(x => x.DeptId == 1 && !notValidBranchIds.Contains(x.BranchId)).Select(x => x.BranchId);
// you can also use this. It solve the problem in a line
var betterResult = list.GroupBy(x => new { x.DeptId })
.Select(x => x.FirstOrDefault(a => a.DeptId == 1))
.Where(y => y != null)
.ToList();
return only first branchId's record.
Hope it helps to you.
if you have access to Branch and Department models, i suggest use this query: Branches.Where(b=>b.Departments.All(d=>d.Id != 2) && b.Departments.Any(d=>d.Id==1))

Using LINQ, how would you filter out all but one item of a particular criteria from a list?

I realize my title probably isn't very clear so here's an example:
I have a list of objects with two properties, A and B.
public class Item
{
public int A { get; set; }
public int B { get; set; }
}
var list = new List<Item>
{
new Item() { A = 0, B = 0 },
new Item() { A = 0, B = 1 },
new Item() { A = 1, B = 0 },
new Item() { A = 2, B = 0 },
new Item() { A = 2, B = 1 },
new Item() { A = 2, B = 2 },
new Item() { A = 3, B = 0 },
new Item() { A = 3, B = 1 },
}
Using LINQ, what's the most elegant way to collapse all the A = 2 items into the first A = 2 item and return along with all the other items? This would be the expected result.
var list = new List<Item>
{
new Item() { A = 0, B = 0 },
new Item() { A = 0, B = 1 },
new Item() { A = 1, B = 0 },
new Item() { A = 2, B = 0 },
new Item() { A = 3, B = 0 },
new Item() { A = 3, B = 1 },
}
I'm not a LINQ expert and already have a "manual" solution but I really like the expressiveness of LINQ and was curious to see if it could be done better.
How about:
var collapsed = list.GroupBy(i => i.A)
.SelectMany(g => g.Key == 2 ? g.Take(1) : g);
The idea is to first group them by A and then select those again (flattening it with .SelectMany) but in the case of the Key being the one we want to collapse, we just take the first entry with Take(1).
One way you can accomplish this is with GroupBy. Group the items by A, and use a SelectMany to project each group into a flat list again. In the SelectMany, check if A is 2 and if so Take(1), otherwise return all results for that group. We're using Take instead of First because the result has to be IEnumerable.
var grouped = list.GroupBy(g => g.A);
var collapsed = grouped.SelectMany(g =>
{
if (g.Key == 2)
{
return g.Take(1);
}
return g;
});
One possible solution (if you insist on LINQ):
int a = 2;
var output = list.GroupBy(o => o.A == a ? a.ToString() : Guid.NewGuid().ToString())
.Select(g => g.First())
.ToList();
Group all items with A=2 into group with key equal to 2, but all other items will have unique group key (new guid), so you will have many groups having one item. Then from each group we take first item.
Yet another way:
var newlist = list.Where (l => l.A != 2 ).ToList();
newlist.Add( list.First (l => l.A == 2) );
An alternative to other answers based on GroupBy can be Aggregate:
// Aggregate lets iterate a sequence and accumulate a result (the first arg)
var list2 = list.Aggregate(new List<Item>(), (result, next) => {
// This will add the item in the source sequence either
// if A != 2 or, if it's A == 2, it will check that there's no A == 2
// already in the resulting sequence!
if(next.A != 2 || !result.Any(item => item.A == 2)) result.Add(next);
return result;
});
What about this:
list.RemoveAll(l => l.A == 2 && l != list.FirstOrDefault(i => i.A == 2));
if you whould like more efficient way it would be:
var first = list.FirstOrDefault(i => i.A == 2);
list.RemoveAll(l => l.A == 2 && l != first);

Applying a filter to subsequences of a sequence using Linq

If I have a List<MyType> as so, with each line representing an item in the collection:
{{ Id = 1, Year = 2010 },
{ Id = 1, Year = 2009 },
{ Id = 1, Year = 2008 },
{ Id = 2, Year = 2010 },
{ Id = 2, Year = 2009 },
{ Id = 2, Year = 2008 }}
I wish to retrieve a collection from this collection of the most recent item for each Id. What will the Linq for this look like?
Desired output:
{{ Id = 1, Year = 2010 },
{ Id = 2, Year = 2010 }}
I have a naiive implementation using a second list variable and a foreach loop, but it's inefficient.
//naiive implementation "p-code"
//...
var mostRecentItems = new List<MyType>();
var ids = collection.Select(i => i.Id).Distinct();
foreach(var id in ids)
{
mostRecentItems.Add(collection.Where(i => i.Id == id).OrderByDescending().First);
}
return mostRecentItems;
Most simply:
var mostRecentById = from item in list
group item by item.Id into g
select g.OrderByDescending(x => x.Year).First();
Group by id, then select the first item in each group ordered in a descending fashion.
var mostRecentItems = collection.GroupBy( c => c.Id )
.Select( g => g.OrderByDescending( i => i.Year ).First() );
or more simply still:
var result = list
.GroupBy(i => i.Id)
.Select(g => new {Id = g.Key, Year = g.Max(y => y.Year)});

Better Linq Query for filtering Parent List with no Children

I have 2 Lists.
var adultList = new List<Dude>();
adultList.Add(new Dude() { ID = 2, Name = "Randy Marsh" });
adultList.Add(new Dude() { ID = 3, Name = "Jimbo Kern" }); // no kids
adultList.Add(new Dude() { ID = 4, Name = "Gerald Broflovski" });
adultList.Add(new Dude() { ID = 5, Name = "Stuart McCormick" });
adultList.Add(new Dude() { ID = 6, Name = "Liane Cartman" });
adultList.Add(new Dude() { ID = 7, Name = "Ned Gerblansky" }); // no kids
var childList = new List<Dude>();
childList.Add(new Dude() { ID = 8, Name = "Stan Marsh", ParentID = 2 });
childList.Add(new Dude() { ID = 9, Name = "Kyle Broflovski", ParentID = 4 });
childList.Add(new Dude() { ID = 10, Name = "Ike Broflovski", ParentID = 4 });
childList.Add(new Dude() { ID = 11, Name = "Kenny McCormick", ParentID = 5 });
childList.Add(new Dude() { ID = 12, Name = "Eric Cartman", ParentID = 6 });
I want a Linq query to return that returns any Dudes in the adultList that do NOT have any kids. The result list should also have no null entries (in sample above, should have a Count() of 2 and return only Jimbo and Ned).
var nullList = new List<Dude>();
nullList.Add(null);
var adultsWithNoChildren = adultList.GroupJoin(
childList,
p => p.ID,
c => c.ParentID,
(p, c) =>
{
if (c.FirstOrDefault() == null) return p;
return null;
})
.Except(nullList);
Is this the best way to accomplish this? is there another Linq function or something else?
I don't like the idea of having the nullList created, but that is the only ensure that the result list has an accurate count.
Thanks
My approach would be like this:
var adultNoChildren = (from adult in adultList
where !childList.Any(child => child.ParentID == adult.ID)
select adult).ToList();
This can also be done using the other LINQ Syntax, but I can never remember that :) (The nice thing about .Any is that it stops as soon as it finds a result, so the entire child list is only traversed for adults with no children)
If the lists have any size at all, I have to recommend a solution involving GroupJoin, due to hashjoin costing n+m, versus where!any costing n*m
IEnumerable<Dude> noKids =
from adult in adultList
join child in childList
on adult.ID equals child.ParentID into kids
where !kids.Any()
select adult;
Or in method form
IEnumerable<Dude> noKids = adultList.GroupJoin(
childList,
adult => adult.ID,
child => child.ParentID,
(adult, kids) => new {Dude = adult, AnyKids = kids.Any() })
.Where(x => !x.AnyKids)
.Select(x => x.Dude);
Lastly, is Liane really a dude?
adultList.Where(x => !childList.Select(y => y.ParentID).Contains(x.ID));
var noKids = from adult in adultList
join child in childList
on adult.ID equals child.ParentID into g
from item in g.DefaultIfEmpty()
where item == null
select adult;
var noKids = adultList.Where(a => !childList.Any(c => c.ParentID == a.ID));

Categories