LINQ Query to filter Data? - c#

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

Related

Change a flat collection of data into a nested tree hierarchy in C#

I have created a view that combines multiple foreign key tables.So the fetched data looks something like this:
Id Name TagId DepartmentId Code
1 N1 T1 NULL NULL
1 N1 T2 NULL NULL
1 N1 NULL D1 NULL
1 N1 NULL NULL C1
1 N1 NULL NULL C2
2 N2 T3 NULL NULL
2 N2 NULL D2 NULL
Want the result in model format like this:
[{
Id: 1,
Name: N1,
TagIds: [T1, T2],
DepartmentId: D1,
Codes: [C1, C2]
},
{
Id: 2,
Name: N2,
TagIds: [T3],
DepartmentId: D2,
Codes: []
}]
What is the most easy and efficient way to achieve this?
You haven't actually shown us what you've tried, but if you grab your entire view data into an IEnumerable into memory, you can use Linq's GroupBy to define groups, from which within you can use Linq filtering and projecting to map out the various properties in each Group.
I've assumed Id and Name have a 1:1 cardinality, so have used that in a Value Tuple grouping key. The output is projected to an anonymous type.
Your final output requires Json Serialization, but you can find any number of answers on SO to do that:
var groups = items.GroupBy(i => (i.Id, i.Name))
.Select(grp => new
{
grp.Key.Id,
grp.Key.Name,
TagIds = grp.Where(i => i.TagId != null)
.Select(x => x.TagId)
.ToArray(),
DepartmentId = grp.FirstOrDefault(i => i.DepartmentId != null)?.DepartmentId,
Codes = grp.Where(i => i.Code != null)
.Select(x => x.Code)
.ToArray(),
});
Test Data Here:
var items = new[]{
new {Id = 1, Name = "N1", TagId = "T1", DepartmentId = (string)null, Code = (string)null},
new {Id = 1, Name = "N1", TagId = "T2", DepartmentId = (string)null, Code = (string)null},
new {Id = 1, Name = "N1", TagId = (string)null, DepartmentId = "D1", Code = (string)null},
new {Id = 1, Name = "N1", TagId = (string)null, DepartmentId = (string)null, Code = "C1"},
new {Id = 1, Name = "N1", TagId = (string)null, DepartmentId = (string)null, Code = "C2"},
new {Id = 2, Name = "N2", TagId = "T3", DepartmentId = (string)null, Code = (string)null},
new {Id = 2, Name = "N2", TagId = (string)null, DepartmentId = "D2", Code = (string)null},
};
The output looks like so in LinqPad:
You can easily do it using Linq`s GroupBy method.
Here is a clean example:
var result = items.GroupBy(i => new { i.Id, i.Name })
.Select(g => new Result
{
Id = g.Key.Id,
Name = g.Key.Name,
TagIds = g.Select(i => i.TagId).ToList(),
DepartmentId = g.FirstOrDefault(i => i.DepartmentId != null)?.DepartmentId,
Codes = g.Select(i => i.Code).ToList();
});

Combining LINQ queries with method syntax

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?

Select Single Element from Jagged Array

I'm working on a problem that's making my brain melt although I don't think it should be this hard. My example is long so I'll try to keep my question short!
I have an Array object that contains some elements that are also Arrays. For example:
customerAddresses = new customer_address[]
{
new // address #1
{
customer_id = 6676979,
customer_address_seq = 1,
customer_address_match_codes = new []
{
new
{
customer_address_seq = 1,
customer_id = 6676979,
customer_match_code_id = 5
}
}
},
new // address #2
{
customer_id = 6677070,
customer_address_seq = 1,
customer_address_match_codes = new []
{
new
{
customer_address_seq = 1,
customer_id = 6677070,
customer_match_code_id = 4
},
new
{
customer_address_seq = 1,
customer_id = 6677070,
customer_match_code_id = 5
},
new
{
customer_address_seq = 1,
customer_id = 6677070,
customer_match_code_id = 3
}
}
},
new // address #3
{
customer_id = 6677070,
customer_address_seq = 2,
customer_address_match_code = new []
{
new
{
customer_address_seq = 2,
customer_id = 6677070,
customer_match_code_id = 4
},
new
{
customer_address_seq = 2,
customer_id = 6677070,
customer_match_code_id = 5
}
}
}
};
As you can see, the Array contains a number of address records, with one record per combination of customer_id and customer_address_seq. What I'm trying to do is find the best matching customer_address according to the following rules:
There must be customer_match_code_id equal to 4 and there must be one equal to 5
If there is a customer_match_code_id equal to 3, then consider that customer_address a stronger match.
According to the above rules, the 2nd customer_address element is the "best match". However, the last bit of complexity in this problem is that there could be multiple "best matches". How I need to handle that situation is by taking the customer_address record with the minimum customer_id and minimum customer_address_seq.
I was thinking that using LINQ would be my best bet, but I'm not experienced enough with it, so I just keep spinning my wheels.
Had to make a change to your class so that you are actually assigning your one collection to something:
customer_address_match_codes = new customer_address_match_code[]
{
new
{
customer_address_seq = 1,
customer_id = 6676979,
customer_match_code_id = 5
}
}
And then here is the LINQ that I've tested and does what you specify:
var result = (from c in customerAddresses
let isMatch = c.customer_address_match_codes
.Where (cu => cu.customer_match_code_id == 4).Any () &&
c.customer_address_match_codes
.Where (cu => cu.customer_match_code_id == 5).Any ()
let betterMatch = isMatch && c.customer_address_match_codes
.Where (cu => cu.customer_match_code_id == 3).Any () ? 1 : 0
where isMatch == true
orderby betterMatch descending, c.customer_id, c.customer_address_seq
select c)
.FirstOrDefault ();
I've worked up an example using your data with anonymous types here: http://ideone.com/wyteM
Not tested and not the same names but this should get you going
customer cb = null;
customer[] cs = new customer[] {new customer()};
foreach (customer c in cs.OrderBy(x => x.id).ThenBy(y => y.seq))
{
if(c.addrs.Any(x => x.num == "5"))
{
if(c.addrs.Any(x => x.num == "3"))
{
if (cb == null) cb = c;
if (c.addrs.Any(x => x.num == "2"))
{
cb = c;
break;
}
}
}
}
This sounds like a job for LINQ
var bestMatch = (from address in DATA
where address.customer_address_match_code.Any(
x => x.customer_match_code_id == 4)
where address.customer_address_match_code.Any(
x => x.customer_match_code_id == 5)
select address).OrderBy(
x => x.customer_address_match_code.Where(
y => y.customer_match_code_id >= 3)
.OrderBy(y => y.customer_match_code_id)
.First()
.customer_match_code_id).FirstOrDefault();
My theory is this: Select addresses that have both a customer_match_code_id == 4 and a customer_match_code_id == 5. Then sort them by the the lowest customer_match_code_id they have that are at least 3, and then take the very first one. If there are a customer_match_code_id that equals 3 then that one is selected, if not, some else is selected. If nothing matches both 4 and 5 then null is returned.
Untested.
Seems quite straight forward in LINQ:
var query =
from ca in customerAddresses
where ca.customer_address_match_codes.Any(
mc => mc.customer_match_code_id == 4)
where ca.customer_address_match_codes.Any(
mc => mc.customer_match_code_id == 5)
orderby ca.customer_id
orderby ca.customer_address_seq
orderby ca.customer_address_match_codes.Any(
mc => mc.customer_match_code_id == 3) descending
select ca;
var result = query.Take(1);
How does that look?

"in" operator in linq c#?

I have a generic list which contains member details and I have a string array of memberIds..I need to filter the list and get the results which contains all the memberIds..How can I achieve this using LINQ.
I tried the following
string[] memberList = hdnSelectedMemberList.Value.Split(',');
_lstFilteredMembers = lstMainMembers.Where(p =>memberList.Contains(p.MemberId))
.ToList();
But the above query is giving me only the results that match the first member ID..so lets say if I have memberIds 1,2,3,4 in the memberList array..the result it returns after the query contains only the members with member ID 1..even though the actual list has 1,2,3,4,5 in it..
Can you please guide me what I am doing wrong.
Thanks and appreciate your feedback.
Strings make terrible primary keys. Try trimming the list:
string[] memberList = hdnSelectedMemberList.Value
.Split(',')
.Select(p => p.Trim())
.ToList();
_lstFilteredMembers = lstMainMembers.Where(p => memberList.Contains(p.MemberId)).ToList();
Because I have a feeling hdnSelectedMemberList may be "1, 2, 3, 4".
Use a join:
var memquery = from member in lstMainMembers
join memberid in memberList
on member.MemberId equals memberid
select member;
With jmh, I'd use a join
var members = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var ids = new[] { 1, 3, 6, 14 };
var result = members.Join(ids, m => m, id => id, (m, id) => m);
foreach (var r in result)
Console.WriteLine(r); //prints 1, 3, 6
The code you are showing is correct, and works in a Unit Test:
public class Data
{
public string MemberId { get; set; }
}
[TestMethod]
public void Your_Code_Works()
{
// Arrange fake data.
var hdnSelectedMemberList = "1,2,3,4";
var lstMainMembers = new[]
{
new Data { MemberId = "1" },
new Data { MemberId = "2" },
new Data { MemberId = "3" },
new Data { MemberId = "4" },
new Data { MemberId = "5" }
};
// Act - copy/pasted from StackOverflow
string[] memberList = hdnSelectedMemberList.Split(',');
var _lstFilteredMembers = lstMainMembers.Where(p => memberList.Contains(p.MemberId)).ToList();
// Assert - All pass.
Assert.AreEqual(4, _lstFilteredMembers.Count);
Assert.AreEqual("1", _lstFilteredMembers[0].MemberId);
Assert.AreEqual("2", _lstFilteredMembers[1].MemberId);
Assert.AreEqual("3", _lstFilteredMembers[2].MemberId);
Assert.AreEqual("4", _lstFilteredMembers[3].MemberId);
}
There must be something wrong with your code outside what you have shown.
Try Enumerable.Intersect to get the intersection of two collections:
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.intersect.aspx
_lstFilteredMembers = lstMainMembers.Intersect(memberList.Select(p => p.MemberID.ToString())).ToList()
Why not just project the IDs list into a list of members?
var result = memberList.Select(m => lstMainMembers.SingleOrDefault(mm => mm.MemberId == m))
Of course, that will give you a list that contains null entries for items that don't match.
You could filter those out, if you wanted to...
result = result.Where(r => r != null)
Or you could filter it before the initial select...
memberList.Where(m => lstMainMembers.Any(mm => mm.MemberId == m)).Select(m => lstMainMembers.Single(mm => mm.MemberId == m))
That's pretty ugly, though.

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