LINQ GroupBy and Select on different property - c#

If I have the following collection:
var foos = new List<Foo>
{
new Foo{ Name = "A", Value = 1 },
new Foo{ Name = "B", Value = 1 },
new Foo{ Name = "B", Value = 2 },
new Foo{ Name = "C", Value = 1 },
};
And I want to end-up with:
A-1
B-2
C-1
Where in the case of the duplicate "B" I want to select the "B" with the highest Value?
Something like:
var filteredFoos = foos.GroupBy(x => x.Name).Select_Duplicate_With_Highest_Value

var query = from p in foos
group p by p.Name into g
select new
{
Name = g.Key,
Value = g.Max(a => a.Value)
};

var filteredFoos =
foos.GroupBy(x => x.Name)
.Select(x => new { Name = x.Key, Value = x.Max(f => f.Value) });

Try this query:
var filteredFoos = foos.GroupBy(x => x.Name)
.Select(p => new { p.Key, p.Max(x => x.Value) });

For anyone with more than 2 columns:
var subquery = from p in foos
group p by p.Name into g
select new
{
Name = g.Key,
Value = g.Max(a => a.Value)
};
var query = from f in foos
join s in subquery
on f.Name equals s.Name
where f.Value == s.Value
select f;
If this is against SQL, make sure Name is a primitive.

Related

Execute query using LINQ or EF to fetch records from multiple tables

I've been searching for a while now. But all the solutions seems to be different than what I expect.
So this is my query in SQL:-
Select * from
(
select Name,Description Descr from CourseTbl
union all
select MainDesc Name,MainDesc Descr from CoursedescTbl
union all
select SubHeading Name,SubDesc Descr from CourseSubDesc
union all
select Name,Descr as Descr from InternTbl
)A where A.Name like '%D%' or A.Descr like '%D%'
I want to execute the above query using LINQ or EF. and return the list in Json format. So I tried many failed attempts and this is one of them:-
public JsonResult SearchDetail()
{
string SearchKey = Request.Form["SearchName"].ToString();
IEnumerable<SearchList> QueryResult;
using (EBContext db = new EBContext())
{
try
{
QueryResult =
(from x in db.Courses
select new { A = x.Name, B = x.Description })
.Concat(from y in db.CourseDesc
select new { A = y.MainHeading, B = y.MainDesc })
.Concat(from z in db.CourseSubDesc
select new { A = z.SubDesc, B = z.SubHeading })
.Concat(from w in db.Interns
select new { A = w.Name, B = w.Descr })
.ToList();
}
catch (Exception ex)
{
return new JsonResult
{
Data = ex.Message,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
return new JsonResult
{
Data = QueryResult,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
And my SearchList Class is like this:-
public class SearchList
{
public string Name { get; set; }
public string Descr { get; set; }
}
I'm not able to put the where clause in linq query which will search in all table.
I'm getting error when I assign queryresult to my ef query. It says cannot cast to Innumerable.
Thanks in Advance.
Could you explain more on the error you are getting?
Also, have you tried using .Union() in linq?
QueryResult = db.Courses.Select(x=> new { A = x.Name, B= x.Description})
.Union(db.CourseDesc.Select(y=> new {A = y.MainHeading, B = y.MainDesc })
.Union( //so on
.ToList(); //this isn't necessary
Edit: There are two ways to input where clause, either with each search, or at the end:
QueryResult = db.Courses.Where(x=>x.Name == "Name").Select(x=> new { A = x.Name, B= x.Description})
.Union(db.CourseDesc.Where(y=>y.MainHeading == "Name").Select(y=> new {A = y.MainHeading, B = y.MainDesc })
.Union( //so on
.ToList();
Or:
QueryResult = db.Courses.Where(x=>x.Name == "Name").Select(x=> new { A = x.Name, B= x.Description})
.Union(db.CourseDesc.Where(y=>y.MainHeading == "Name").Select(y=> new {A = y.MainHeading, B = y.MainDesc })
.Union( //so on
//Where can go either before or after .ToList
.Where(item=>item.A == "Name")
.ToList();
You did not say what error/exception you are getting. But your QueryResult is of type IEnumerable<SearchList> and you appear to be assigning it an enumerable of anonymous type { A, B }.
Try this:
QueryResult = (from x in db.Courses
select new SearchList { Name = x.Name, Descr = x.Description })
.Concat(...)
.ToList();
Or
QueryResult = db.Courses.Select(x => new SearchList
{ Name = x.Name, Descr = x.Description})
.Concat(...)
.ToList();
UPDATE
Your #2 issue will be fixed if you changed your select to new up a SearchList as I did above, instead of new-ing an anonymous type.
As for your issue #1, you should insert the Where() before your Select():
result1 = db.Courses
.Where(x => x.Name.Contains('D') || x.Description.Contains('D'))
.Select(x => new SearchList { Name = x.Name, Descr = x.Description});
result2 = db.CourseDesc
.Where(y => y.MainHeading.Contains('D') || y.MainDesc.Contains('D'))
.Select(y => new SearchList { Name = y.MainHeading, Descr = y.MainDesc});
result3 = db.CourseSubDesc
.Where(...)
.Select(...);
QueryResult = result1.Concat(result2).Concat(result3).ToList();
Doing Where() as part of the query on each table is important so you do not fetch all records from that table, unlike if you do the Where() after Concat(). Also note that Concat() may throw an ArgumentNullException.
Take the lists Separately and query and concat
check this example
List<string> a = new List<string>() { "a", "b", "c" };
List<string> b = new List<string>() { "ab", "bb", "cb" };
IEnumerable<SearchList> QueryResult =
a.Where(x => x.Contains("a")).Select(x => new SearchList() { Name = x, Descr = x })
.Concat(b.Where(x => x.Contains("a")).Select(x => new SearchList() { Name = x, Descr = x }));

C# Linq To Create Group On Multiple Properties and format the resultant group into single key/property?

I have following scenario where I want to find duplicates after forming the group and realign/format the duplicate data with some common class.
Example -
var lst = new List<Test>
{
new Test{Category="A",Class="Class1",Id="101",Name="John"},
new Test{Category="B",Class="Class2",Id="102",Name="Peter"},
new Test{Category="A",Class="Class2",Id="103",Name="David"},
new Test{Category="C",Class="Class3",Id="104",Name="Julia"},
new Test{Category="D",Class="Class4",Id="105",Name="Ken"},
new Test{Category="A",Class="Class1",Id="106",Name="Robert"},
};
I have created the group as -
var group =
from c in lst
group c by new
{
c.Category,
c.Class
} into g
select new
{
Category = g.Key.Category,
Class = g.Key.Class,
Id = lst.Where(x => g.Key.Category == x.Category && g.Key.Class==x.Class)
.Select(y => y.Id).ToList()
};
Which results me 2 group items for Category A with different Classes -
GroupItem1 - Category = "A" , Class = "Class1", Id = {101,106}
GroupItem2 - Category = "A" , Class = "Class2", Id = {103}
So I have requirement to show result in such case as below with other categories as -
Category = "A", Class = "Class1 OR SomeCommonClass", Id = {101,106,103}
Is it possible to achieve this result with minimum code and optimized logic.
If you want to group by Category and get the result below is the query.
var group =
from c in lst
group c by new
{
c.Category
} into g
select new
{
Category = g.Key.Category,
Class = lst.Where(x => g.Key.Category == x.Category).Select(y => y.Class).ToList(),
Id = lst.Where(x => g.Key.Category == x.Category)
.Select(y => y.Id).ToList()
};
Replace your group query with this:
var groups =
from c in lst
group c by c.Category into g
select new { Category = g.Key, Class = g.Select(c => c.Class).Distinct().Join(" or "), IDs = g.Select(c => c.Id).ToList() };
where Join is an IEnumerable extension method:
public static string Join(this IEnumerable<string> strings, string sep) => String.Join(sep, strings.ToArray());
var group = lst.GroupBy(l => l.Category)
.Select(x => new
{
Category = x.Key,
Class = string.Join(" OR ", x.Select(c => c.Class).Distinct()),
Ids = x.Select(c => c.Id).ToList()
}).ToList();

How to sort linq with fixed values and show all the rest with another sorting [duplicate]

This question already has answers here:
LINQ OrderBy versus ThenBy
(4 answers)
Closed 5 years ago.
IQueryable<Employee> query = ((IEnumerable<Employee>)employeeList)
.Select(x => x)
.AsQueryable();
var strListEmployees = input.MustIncludeIdsInPage.Split(",").ToList();
//the list of employee is dynamic, it'd return 3, 4, 5 or more data
var entities = query
.OrderBy(item => strListEmployees.IndexOf(item.Id.ToString()))
.PageBy(input)
.ToList();
example data
What I want is something like this in order:
by employee name
D
F
A
B
C
E
G
H
Employee D, F, A on top (fix value in List) and show the rest with name sorting (order by).
As M. Wiśnicki mentioned, this is easily solveable as You got only 3 elements. But to dynamically resolve this, I would stick to some function, where You would enter the List (or IEnumerable) of the objects and also the Names, based on which You want to filter them.
The code below is recursion, which will go through the array and select the 1st element (from array) and add the rest. Rest is calling the same function without the 1st name & without the element we have already added.
Something like:
public IEnumerable<Employee> GetOrderedPrefered(IEnumerable<Employee> aList, string[] aNames)
{
if (aNames.Length == 0) return aList.OrderBy(a => a.Name).ToList();
var lRes = new List<Employee>()
{
aList.FirstOrDefault(a => a.Name == aNames[0])
};
lRes.AddRange(
GetOrderedPrefered(
aList.Where(a => a.Name != aNames[0]),
aNames.Where(a => a != aNames.First()
).ToArray()
));
return lRes;
}
Usage:
var lRes = GetOrderedPrefered(persons, names);
foreach (var item in lRes)
Console.WriteLine(item.Name);
> D
> F
> A
> B
> C
> E
> G
You can use OrderBy() and ThenBy()
List<Test> tests = new List<Test>()
{
new Test() {EmployeeID = "1", Name = "A"},
new Test() {EmployeeID = "2", Name = "B"},
new Test() {EmployeeID = "3", Name = "C"},
new Test() {EmployeeID = "4", Name = "D"},
new Test() {EmployeeID = "5", Name = "E"},
new Test() {EmployeeID = "6", Name = "F"},
new Test() {EmployeeID = "7", Name = "G"},
new Test() {EmployeeID = "8", Name = "H"},
};
var x = tests.OrderBy(name => name.Name != "D")
.ThenBy(name => name.Name != "F")
.ThenBy(name => name.Name != "A")
.ThenBy(name => name.Name)
.ToList();
Result is: First D,F,A and others names
Edit:
string[] filtr = new[] {"D", "F", "A"};
var fdata = tests.Where(d => filtr.Contains(d.Name)).OrderBy(z=>z.Name).ToList();
var odata = tests.Where(d => !filtr.Contains(d.Name)).OrderBy(z => z.Name).ToList();
fdata.AddRange(odata);
var set = Enumerable.Range(0, 8)
.Select(i => new {
Name = new string(new[] { (char)('A' + i) })
});
var before = string.Join(",", set.Select(i => i.Name)); //A,B,C,D,E,F,G,H
var priorities = "D,F".Split(',').Select((v, i) => new { Value = v, Index = i });
var query = from s in set
join p in priorities on s.Name equals p.Value into m
from x in m.DefaultIfEmpty(new { Value = s.Name, Index = int.MaxValue })
orderby x.Index, s.Name
select s.Name;
var result = string.Join(",", query); //D,F,A,B,C,E,G,H

Getting duplicate data based on dynamic key

I have a list of Person objects:
List<PersonData> AllPersons
From this list I want all those person objects that are duplicated based on a certain property.
Example, this code give all the duplicates based on the Id
var duplicateKeys = AllPersons.GroupBy(p => p.Id).Select(g => new { g.Key, Count = g.Count() }).Where(x => x.Count > 1).ToList().Select(d => d.Key);
duplicates = AllPersons.Where(p => duplicateKeys.Contains(p.Id)).ToList();
Can the part p.Id be dynamic?
Meaning if the user specifies the unique column in a config file and it's read like so:
string uniqueColumn = "FirstName";
How can the query be composed to add that functionality?
Regards.
You can use Reflection to achieve that:
List<PersonData> AllPersons = new List<PersonData>()
{
new PersonData { Id = 1, FirstName = "Tom" },
new PersonData { Id = 2, FirstName = "Jon" },
new PersonData { Id = 3, FirstName = "Tom" }
};
string uniqueColumn = "FirstName";
var prop = typeof(PersonData).GetProperty(uniqueColumn);
var duplicateKeys = AllPersons.GroupBy(p => prop.GetValue(p, null))
.Select(g => new { g.Key, Count = g.Count() })
.Where(x => x.Count > 1)
.Select(d => d.Key)
.ToList();
var duplicates = AllPersons.Where(p => duplicateKeys.Contains(prop.GetValue(p, null))).ToList();
duplicates have 2 elements with FirstName == "Tom" after query execution.
You might want to look into Dynamic LINQ or PredicateBuilder.

GroupBy and count the unique elements in a List

I have a list that contains only strings. What I would love to do is group by and return a count.
For instance:
Foo1
Foo2
Foo3
Foo1
Foo2
Foo2
Would result in Foo1: 2, Foo2: 3, Foo3: 1. I've tried with Linq but the list has a GroupBy that might do the trick but i messed it up, can't figure the use :(
var list = new List<string> { "Foo1", "Foo2", "Foo3", "Foo2", "Foo3", "Foo3", "Foo1", "Foo1" };
var grouped = list
.GroupBy(s => s)
.Select(group => new { Word = group.Key, Count = group.Count() });
var items= myList
.GroupBy(g => g)
.Select(t => new {count= t.Count(), key= t.Key });
foreach (var group in items)
Console.WriteLine ( group.key + " " + group.count);
var grouped = select new
{
Foo= grp.Key,
Bar= grp.Select(x => x.SomeField).Distinct().Count()
};
a working example with the NorthWind database so that you can check::
NWindCustomersDataContext dc = new NWindCustomersDataContext();
var query = (from c in dc.Customers
join o in dc.Orders on c.CustomerID equals o.CustomerID
group o by c.CustomerID into g
select new
{
CustomerID = g.Key,
Company = (from cust in dc.Customers
where cust.CustomerID == g.Key
select cust).ToList(),
Count = g.Select(x => x.OrderID).Distinct().Count()
}).OrderByDescending(y => y.Count);
foreach (var item in query)
{
Response.Write("CustomerID: " + item.CustomerID + "</br>" + "CompanyName: " + item.Company[0].CompanyName.ToString() + "</br>");
}
Here you can find a very good example
A good solution is available on http://msdn.microsoft.com/en-us/library/vstudio/bb534304(v=vs.100).aspx
It groups data by key; each key has it's own list of data you can iterate over it.

Categories