I need to group the following list by the department value but am having trouble with the LINQ syntax. Here's my list of objects:
var people = new List<Person>
{
new Person { name = "John", department = new List<fields> {new fields { name = "department", value = "IT"}}},
new Person { name = "Sally", department = new List<fields> {new fields { name = "department", value = "IT"}}},
new Person { name = "Bob", department = new List<fields> {new fields { name = "department", value = "Finance"}}},
new Person { name = "Wanda", department = new List<fields> {new fields { name = "department", value = "Finance"}}},
};
I've toyed around with grouping. This is as far as I've got:
var query = from p in people
from field in p.department
where field.name == "department"
group p by field.value into departments
select new
{
Department = departments.Key,
Name = departments
};
So can iterate over the groups, but not sure how to list the Person names -
foreach (var department in query)
{
Console.WriteLine("Department: {0}", department.Department);
foreach (var foo in department.Department)
{
// ??
}
}
Any ideas on what to do better or how to list the names of the relevant departments?
Ah, should have been:
foreach (Person p in department.Name) Console.WriteLine(p.name);
Thanks for the extra set of eyes, Fyodor!
Your department property seems like an awkward implementation, particularly if you want to group by department. Grouping with a List as your key is going to lead to a ton of complexity, and it's unnecessary since you only care about one element in the List.
Also, you seem to have created the fields class as a way of simulating either dynamic/anonymous types, or just the Dictionary<string, string> class, I can't really tell. I suggest not doing that; C# already has those types baked in, and working around them will just be inefficient and stop you from using Intellisense. Whatever led you to do that, there's probably a better, more C#-ish way. Besides--and this is key--your code looks like you can just forget all that and make department a simple string.
If you have control over the data structure, I'd suggest reorganizing it:
var people = new List<Person> {
new Person { name = "John", department = "IT"},
new Person { name = "Sally", department = "IT"},
new Person { name = "Bob", department = "Finance"},
new Person { name = "Wanda", department = "Finance"},
};
Suddenly, grouping all that becomes simple:
var departments = from p in people
group p by p.department into dept
select dept;
foreach (var dept in departments)
{
Console.WriteLine("Department: {0}", dept.Key);
foreach (var person in dept)
{
Console.WriteLine("Person: {0}", person.name);
}
}
If you must leave the data structure as it is, you could try this:
from p in people
from field in p.department
where field.name equals "department"
group p by field.value into dept
select dept;
That should work with the above nested loop.
The list of persons for each department can be accessed via department.Name. Simply iterate over it:
foreach( var person in department.Name ) Console.WriteLine( person.name );
The value of department.Department, on the other hand, is of type string. This value comes from departments.Key, which in turn comes from field.value - because that's the key that you group by.
The foreach statement over department.Department still compiles fine, because string implements IEnumerable<char>. Consequently, your foo variable is of type char.
Related
So I have to identify everyone who has a higher income than "JONES".
Here is the Schema:
new Emp{name = "SMITH", income = 800},
new Emp{name = "JONES", income = 600},
new Emp{name = "ADAMS", income = 900},
new Emp{name = "KING", income = 400}
I can't find a way to build this in a Query Syntax...
so let's say you have your data like this. so this should solve your problem. so to explain the code below.
I have a list of data based of the Emp class.
I also have a variable of jones that contains information about jones.
I can then use Linq to query the data list of Emp where the emp income is greater than the matches Jones. then I return then in orderbydescending using Linq.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
//Emp class for
public class Emp
{
public string name { get; set; }
public double income { get; set; }
}
public static void Main()
{
//List of Emp data base off the Emp class object.
var data = new List<Emp>
{
new Emp {name = "SMITH", income = 800},
new Emp {name = "JONES", income = 600},
new Emp {name = "ADAMS", income = 900},
new Emp {name = "KING", income = 400}
};
//Jones data that will be used for querying
var jones = new Emp {name = "JONES", income = 600};
//List of Emp that have income higher than jones.
var higherThanJones = data.Where(item => item.income > jones.income)
.OrderByDescending(i => i.income)
.ToList();
//Foreach loop to show the people with income than jones
foreach (var people in higherThanJones)
{
//printing out the names of the people higher than Jones
Console.WriteLine(people.name);
}
}
}
In query syntax, you can first create a query to find the matching record, or return the default (which will be 0) if there is no match:
var jonesIncome = (from e in emps
where e.name == "JONES"
select e.income).FirstOrDefault();
Then you can use the income query to find the rows desired:
var higherThanJones = from e in emps
where e.income > jonesIncome
select e;
Since queries use deferred execution, the two queries will actually be executed when higherThanJones results are used. If you are querying a database, the two queries will be translated into a single SQL query, depending on the LINQ you are using and the database provider.
You could also use lambda/fluent syntax to combine into a single query (I prefer not to combine query syntax as it doesn't read as well):
var matchName = "JONES";
var higherThanMatch = emps.Where(e => e.income > emps.Where(e2 => e2.name == matchName)
.Select(e2 => e2.income)
.FirstOrDefault());
So I have to identify everyone who has a higher income than "JONES".
Are you certain there is a "Jones"? Is there exactly one "Jones"?
The answer depends on whether you are working IQueryable or IEnumerable.
If you need to do it as Queryable, you need to pack it in one Query:
IQueryable<Employee> employees = ...
var employeesWithHigherIncomes = employees
.Where(employee => employee.Income >
employees.Where(employee => employee.Name == name)
.FirstOrDefault()));
Luckily your database is smart enough not to search Jones again for every Employee.
As Enumerable:
string name = "Jones"
IEnumerable<Employee> employees = ...
var incomeJones = employees.Where(employee => employee.Name == name)
.Select(employee => employee.Income)
.FirstOrDefault();
var employessWithHigherIncome = employees
.Where(employee => employee.Income > incomeJones)
.FirstOrDefault();
You will enumerate your sequence at utmost twice: once (partly) until you found the first "Jones", and once completely to find all higher incomes.
If I had put the query to find the income of Jones in the "Where", like I did in Queryable, then for every Employee I had to enumerate the sequence to find Jones again.
How can I write a linq query to match two condition on same column in the table?
Here one person can be assigned to multiple types of works and it is store in PersonWorkTypes table containing the details of persons and their worktypes.
So I need to get the list of persons who have both fulltime and freelance works.
I have tried
people.where(w => w.worktype == "freelance" && w.worktype == "fulltime")
But it returns an empty result.
You can try this
public class Person {
public string Name {get;set;}
public List<PersonWorkType> PersonWorkTypes {get;set;}
}
public class PersonWorkType {
public string Type {get;set;}
}
public static void Main()
{
var people = new List<Person>();
var person = new Person { Name = "Toño", PersonWorkTypes = new List<PersonWorkType>() { new PersonWorkType { Type = "freelance" } } };
var person2 = new Person { Name = "Aldo", PersonWorkTypes = new List<PersonWorkType>() { new PersonWorkType { Type = "freelance" }, new PersonWorkType { Type = "fulltime" } } };
var person3 = new Person { Name = "John", PersonWorkTypes = new List<PersonWorkType>() { new PersonWorkType { Type = "freelance" }, new PersonWorkType { Type = "fulltime" } } };
people.Add(person);
people.Add(person2);
people.Add(person3);
var filter = people.Where(p => p.PersonWorkTypes.Any(t => t.Type == "freelance") && p.PersonWorkTypes.Any(t => t.Type == "fulltime"));
foreach(var item in filter) {
Console.WriteLine(item.Name);
}
}
This returns person that contains both types in PersonWorkTypes
AS already said, && operator means, that BOTH conditions has to be met. So in your condition it means that you want worktype type to by freelanceand fulltime at the same time, which is not possible :)
Most probably you want employees that have work type freelance OR fulltime, thus your condition should be:
people.Where(w=>w.worktype=="freelance" || w.worktype =="fulltime")
Or, if person can be set more than once in this table, then you could do:
people
.Where(w=>w.worktype=="freelance" || w.worktype =="fulltime")
// here I assume that you have name of a person,
// Basically, here I group by person
.GroupBy(p => p.Name)
// Here we check if any person has two entries,
// but you have to be careful here, as if person has two entries
// with worktype freelance or two entries with fulltime, it
// will pass condition as well.
.Where(grp => grp.Count() == 2)
.Select(grp => grp.FirstOrDefault());
w.worktype=="freelance"
w.worktype=="fulltime"
These are mutually exclusive to each other, and therefore cannot both be true to ever satisfy your AND(&&) operator.
I am inferring that you have two (or more) different rows in your table per person, one for each type of work they do. If so, the Where() method is going to check your list line-by-line individually and won't be able to check two different elements of a list to see if Alice (for example) both has en entry for "freelance" and an entry for "fulltime" as two different elements in the list. Unfortuantely, I can't think of an easy way to do this in a single query, but something like this might work:
var fulltimeWorkers = people.Where(w=>w.worktype=="fulltime");
var freelanceWorkers = people.Where(w=>w.worktype=="freelance");
List<Person> peopleWhoDoBoth = new List<Person>();
foreach (var worker in fulltimeWorkers)
{
if (freelanceWorkers.Contains(worker)
peopleWhoDoBoth.Add(worker);
}
This is probably not the most efficient way possible of doing it, but for small data sets, it shouldn't matter.
I have following list.
One list with Person object has Id & Name property. Other list with People object has Id, Name & Address property.
List<Person> p1 = new List<Person>();
p1.Add(new Person() { Id = 1, Name = "a" });
p1.Add(new Person() { Id = 2, Name = "b" });
p1.Add(new Person() { Id = 3, Name = "c" });
p1.Add(new Person() { Id = 4, Name = "d" });
List<People> p2 = new List<People>();
p2.Add(new People() { Id = 1, Name = "a", Address=100 });
p2.Add(new People() { Id = 3, Name = "x", Address=101 });
p2.Add(new People() { Id = 4, Name = "y", Address=102 });
p2.Add(new People() { Id = 8, Name = "z", Address=103 });
Want to filter list so I used below code. But code returns List of Ids. I want List of People object with matched Ids.
var filteredList = p2.Select(y => y.Id).Intersect(p1.Select(z => z.Id));
You're better off with Join
var filteredList = p2.Join(p1,
people => people.Id,
person => person.Id,
(people, _) => people)
.ToList();
The method will match items from both lists by the key you provide - Id of the People class and Id of Person class.
For each pair where people.Id == person.Id it applies the selector function (people, _) => people. The function says for each pair of matched people and person just give me the people instance; I don't care about person.
Something like this should do the trick :
var result= p1.Join(p2, person => person.Id, people => people.Id, (person, people) => people);
If your list is large enough you should use hashed collection to filter it and improve performance:
var hashedIds = new HashSet<int>(p1.Select(p => p.Id));
var filteredList = p2.Where(p => hashedIds.Contains(p.Id)).ToList();
This will work and work extremely fast because Hashed collections like Dictionary or HashSet allows to perform fast lookups with almost O(1) complexity (which effectively means that in order to find element with certain hash compiler knows exactly where to look for it. And with List<T> to find certain element compiler would have to loop the entire collection in order to find it.
For example line: p2.Where(p => p1.Contains(p.Id)).ToList();
has complexity of O(N2) because using of both .Where and .Contains will form nested loops.
Do not use the simplest answer (and method), use the one that better suits your needs.
Simple performance test against .Join() ...
And the larger collection is the more difference it would make.
Here is my data:
private List<Department> Data
{
get
{
return new List<Department>
{
new Department{
Id = 1,
Name = "Tech",
Employees = new List<Employee>{
new Employee{Name = "x", Id = 1 },
new Employee{ Name = "y", Id = 2}
}
},
new Department{
Id = 2,
Name = "Sales",
Employees = new List<Employee>{
new Employee{Name = "a", Id = 3},
new Employee {Name = "b", Id = 4}
}
}
};
}
}
and here I am getting a list of all employees with their appropriate departments:
List<Employee> employees = (from department in Departments
let d = department
from e in d.Employees
select new Employee{
Id = e.Id,
Name = e.Name
Department = d
}).ToList();
What is bothering me is that I have to recreate my Employee object in order to attach the appropriate department to it. Is there a way that I could write my LINQ statement where I don't have to recreate the Employee?
There might be a better way to phrase this question-- so feel free to let me know is there is.
Edit
The reason I'm going down this path is that I'm storing my data by serializing my department:
[
{
"Id":1,
"Name":"Sales",
"Employees":[{"Id":2,"Name":"x"},{"Id":1,"Name":"y"}]
},
{
"Id":2,
"Name":"Tech",
"Employees":[{"Id":3,"Name":"d"},{"Id":4,"Name":"f"}]
}
]
It looks like you want to use LINQ to update an instance. This is not the intended use. Use LINQ to query the instances you want to have, and then loop over the results to update. (non-nested) Loops are not evil.
var query =
from d in Departments
from e in d.Employees
select new { Employee = e, Department = d };
foreach(var x in query)
{
x.Employee.Department = x.Department;
}
You should not have this problem in the first place - You should fully construct your Employee instances when you initially create them, not sometime later - if an employee needs a department to be used, you should add a constructor that allows/enforces providing it:
public Employee(int id, string name, Department department)
{
...
}
You could, if you really, really want, use a let-clause for a side-effect, since assignment expressions return a value:
List<Employee> employees = (from department in Departments
from e in department.Employees
let _ = e.Department = department
select e).ToList();
Also I fully agree with BrokenGlass...
Using let is redundant and not useful in your example query.
Besides, LINQ is not the right tool here. You want to affect the state of the objects you're querying (i.e. creating side-effects), which is generally not recommended.
By direct comparison, this is a better alternative to what you're trying do to:
foreach(var department in Departments)
foreach(var employee in department.Employees)
employee.Department = department;
If you can however, you should do the department assignment at the time you add the employees to the department, either in an AddEmployee method in the Department class, or maybe in a Employee.Department property setter.
Suppose if I add person class instance to list and then I need to query the list using linq.
List lst=new List();
lst.add(new person{ID=1,Name="jhon",salary=2500});
lst.add(new person{ID=2,Name="Sena",salary=1500});
lst.add(new person{ID=3,Name="Max",salary=5500});
lst.add(new person{ID=4,Name="Gen",salary=3500});
Now I want to query the above list with linq. Please guide me with sample code.
I would also suggest LinqPad as a convenient way to tackle with Linq for both advanced and beginners.
Example:
Well, the code you've given is invalid to start with - List is a generic type, and it has an Add method instead of add etc.
But you could do something like:
List<Person> list = new List<Person>
{
new person{ID=1,Name="jhon",salary=2500},
new person{ID=2,Name="Sena",salary=1500},
new person{ID=3,Name="Max",salary=5500}.
new person{ID=4,Name="Gen",salary=3500}
};
// The "Where" LINQ operator filters a sequence
var highEarners = list.Where(p => p.salary > 3000);
foreach (var person in highEarners)
{
Console.WriteLine(person.Name);
}
If you want to learn details of what all the LINQ operators do, and how they can be implemented in LINQ to Objects, you might be interested in my Edulinq blog series.
var persons = new List<Person>
{
new Person {ID = 1, Name = "jhon", Salary = 2500},
new Person {ID = 2, Name = "Sena", Salary = 1500},
new Person {ID = 3, Name = "Max", Salary = 5500},
new Person {ID = 4, Name = "Gen", Salary = 3500}
};
var acertainperson = persons.Where(p => p.Name == "jhon").First();
Console.WriteLine("{0}: {1} points",
acertainperson.Name, acertainperson.Salary);
jhon: 2500 points
var doingprettywell = persons.Where(p => p.Salary > 2000);
foreach (var person in doingprettywell)
{
Console.WriteLine("{0}: {1} points",
person.Name, person.Salary);
}
jhon: 2500 points
Max: 5500 points
Gen: 3500 points
var astupidcalc = from p in persons
where p.ID > 2
select new
{
Name = p.Name,
Bobos = p.Salary*p.ID,
Bobotype = "bobos"
};
foreach (var person in astupidcalc)
{
Console.WriteLine("{0}: {1} {2}",
person.Name, person.Bobos, person.Bobotype);
}
Max: 16500 bobos
Gen: 14000 bobos
Since you haven't given any indication to what you want, here is a link to 101 LINQ samples that use all the different LINQ methods: 101 LINQ Samples
Also, you should really really really change your List into a strongly typed list (List<T>), properly define T, and add instances of T to your list. It will really make the queries much easier since you won't have to cast everything all the time.