Query values in a Dictionary<ObjectId, Class> using LINQ? - c#

Consider the following simple example of Students and Teachers;
// person
public class Person
{
public ObjectId Id { get; set; }
public string Name { get; set; }
public Person() {
Id = ObjectId.GenerateNewId(DateTime.Now);
}
}
// student has a Classroom
public class Student : Person
{
public string Classroom { get; set; }
}
// teacher has a Dictionary<ObjectId, Student> Students
public class Teacher : Person
{
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
public Dictionary<ObjectId, Student> Students { get; set; }
public Teacher() {
Students = new Dictionary<ObjectId, Student>();
}
}
class Program
{
static void Main(string[] args)
{
var server = MongoServer.Create("mongodb://localhost/database?safe=true");
var database = server.GetDatabase("sandbox");
var collection = database.GetCollection<Teacher>("teachers");
collection.Drop();
// create students
var s1 = new Student() { Name = "s1", Classroom = "foo" };
var s2 = new Student() { Name = "s2", Classroom = "foo" };
var s3 = new Student() { Name = "s3", Classroom = "baz" };
var s4 = new Student() { Name = "s4", Classroom = "foo" };
// teacher 1
var t1 = new Teacher() { Name = "t1" };
t1.Students.Add(s1.Id, s1);
t1.Students.Add(s2.Id, s2);
collection.Insert(t1);
// teacher 2
var t2 = new Teacher {Name = "t2"};
t2.Students.Add(s3.Id, s3);
collection.Insert(t2);
// add teacher 3
var t3 = new Teacher() {Name = "t3"};
t3.Students.Add(s4.Id, s4);
collection.Insert(t3);
// select via key
var onlyt1 = collection.AsQueryable().Where(t => t.Students.ContainsKey(s1.Id)).ToList();
Console.WriteLine("onlyt1 : {0}", onlyt1.ToJson());
Console.ReadLine();
}
}
I can select via the key (shown above), but how do I find all the teachers who have students with classroom of "foo"? I want to write something like;
// select via value
var shouldBeJustT1andT3 = collection.AsQueryable().Where(t => t.Students.Values.Where(s => s.Classroom == "foo")).ToList();

You can use Any to get any teacher for whom there are students in a given classroom "foo":
List<Teacher> shouldBeJustT1andT3 = collection.Where(
teacher => teacher.Students.Any(student => student.Classroom == "foo")
).ToList();
Edit
Since Mongo's IQueryable isn't supporting Any by default, maybe you could just use Where and Count instead of Any:
List<Teacher> shouldBeJustT1andT3 = collection.Where(
teacher => teacher.Students.Where(student => student.Classroom == "foo").Count() > 0
).ToList();

Can't you have Students of type just ICollection<Person>?
Then you don't need query dictionary's values but flat objects' list, i.e. where s.ID == x && s.Classroom == "blah".
Dictionary makes sense to find object by key only, i.e. t.Students[studentId].
To find teachers: see dbaseman's answer, he's correct.

Related

why books list not filtered and both item with name N1 & N2 appears and how to prepare ObservableCollection

I have below class and their associated data, here I am trying to filter books with book name having name "N1", but it's not working and I am getting both book items in `filterList, please suggest why and what is the best way to fill out observable collection based on filter data?
var lstStudents = new List<Student>
{
new Student
{
Name = "studen1",
Standards = new List<Standard> {new Standard {Name = "std1"}, new Standard {Name = "std2"}},
Books = new List<Book> {new Book {Name = "N1", Page = "20"}, new Book {Name = "N2", Page = "30"}}
},
new Student
{
Name = "studen2",
Standards = new List<Standard> {new Standard {Name = "std1"}},
Books = new List<Book> {new Book {Name = "N1", Page = "20"}, new Book {Name = "N2", Page = "30"}}
},
new Student
{
Name = "studen3",
Standards = new List<Standard> {new Standard {Name = "std1"}},
Books = new List<Book> {new Book {Name = "N1", Page = "20"}, new Book {Name = "N2", Page = "30"}}
}
};
var filterList = lstStudents.Where(c => c.Standards.Count == 1
&& c.Standards.Any(d => d.Name == "std1")
&& c.Books.Any(d => d.Name == "N1"))
.ToList();
//why both books with Name N1 & N2 both filtered as I am filtering with name = N1?
var data = new ObservableCollection<Data>();
foreach (var item in filterList)
{
data.Add(new Data { BookName = item.Name, BookPage = item.Books[0].Page });
}
Supporting Classes are,
public class Data
{
public string StudentName { get; set; }
public string BookName { get; set; }
public string BookPage { get; set; }
}
public class Student
{
public string Name { get; set; }
public List<Standard> Standards { get; set; }
public List<Book> Books { get; set; }
}
public class Standard
{
public string Name { get; set; }
}
public class Book
{
public string Name { get; set; }
public string Page { get; set; }
}
You could :
1 - Filter Allstudents that have count of Standards equals 1 and name std1,
2 - And Flatten all books and student Name to Data object directly,
3 - Use second filter for book BookName == "N1",
4 - Put directly the result in ObservableCollection, like the following code:
List<Data> filterList = lstStudents.Where(c => c.Standards.Count == 1 && c.Standards.Any(d => d.Name == "std1"))
.SelectMany(x => x.Books.Select(y => new Data { StudentName = x.Name, BookName = y.Name, BookPage = y.Page }))
.Where(d => d.BookName == "N1")
.ToList();
var newData = new ObservableCollection<Data>(filterList);
Demo
foreach (Data data in filterList)
{
Console.WriteLine($"StudentName:{data.StudentName} BookName:{data.BookName} BookPage:{data.BookPage}");
}
Result
StudentName:studen2 BookName:N1 BookPage:20
StudentName:studen3 BookName:N1 BookPage:20
I hope this helps you out.
Your query returns students that have any "N1" book in their books list (along with the other filters).
So the books themselves are not being filtered out, just students that don't have that book.
An example of how you could return a list of "N1" books for the students that meet the initial filters is:
var filterList = lstStudents.Where(c =>
c.Standards.Count == 1
&& c.Standards.Any(d => d.Name == "std1"))
.SelectMany(s => s.Books.Select(b =>
new {
StudentName = s.Name,
BookName = b.Name,
BookPage = b.Page
})
.Where(b => b.BookName == "N1"));
Then, e.g:
foreach (var item in filterList)
{
data.Add(new Data { StudentName = item.StudentName, BookName = item.BookName, BookPage = item.Page });
}
This first of all retrieves students matching the supplied criteria then retrieves the books from those students into one list of books (the SelectMany method).
It then filters those books to return only those with the required criteria into an anonymous type with student name also.

Linq - populate lists with outer joins data

Is is possible to have a linq query that populates a class with List for any outer join subqueries?
I've tried various variations of this, but can't get it to work.
Another option would be to populate the class by having more queries, but that would be bad performance wise.
Here's an example, where I try to populate MyClass, using a single query
var result = from p in PersonTable
join cars in CarTable on p.id equals cars.id_person into carsGroup.DefaultIfEmpty()
select new MyClass
{
Person = new Person
{
Id = p.id,
Name = p.name
},
Cars = new List<Car>()
{
Id = carsGroup....??
}
}
public class MyClass
{
public Person Person { get; set; }
public List<PersonCar> Cars { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
public class PersonCar
{
public int Id { get; set; }
pubint int IdPerson {get; set;}
public string Description { get; set; }
}
The LINQ query you have provide is incorrect. The following is a Test that will demonstrate functionality that you're probably looking for:
[TestMethod]
public void TestMethod1()
{
var PersonTable = new List<Person>
{
new Person
{
Id = 1,
Name = "Test1"
},
new Person
{
Id = 2,
Name = "Test2"
},
};
var CarTable = new List<PersonCar>
{
new PersonCar
{
Id = 1,
IdPerson = 2
},
new PersonCar
{
Id = 2,
IdPerson = 3
}
};
var result = (from person in PersonTable
join cars in CarTable on person.Id equals cars.IdPerson into carsGroup
from args in carsGroup.DefaultIfEmpty()
select new MyClass
{
Person = person,
Cars = carsGroup.ToList()
}).ToList();
Assert.AreEqual(2, result.Count);
Assert.AreEqual(1, result.Count(res => res.Cars.Count == 0));
Assert.AreEqual(1, result.Count(res => res.Cars.Count == 1));
}

Using Custom IComparer to Order by Pointer value

I have a class that defines a student, and has a property (FollowedBy) that is immediately behind that student. I am looking for a way to order the students based on this linkage.
class Student
{
public int StudentID { get; set; }
public string Name { get; set; }
public int? FollowedBy { get; set; }
}
var lstStudents = new List<Student>()
{ new Student() { StudentID = 2, Name = "Mark", FollowedBy =4 },
new Student() { StudentID = 1, Name = "Sam", FollowedBy = 2},
new Student() { StudentID = 4, Name = "Fred", FollowedBy =null } ,
new Student() { StudentID = 3, Name = "Janice", FollowedBy = 1}};
for (var s in lstStudents.OrderBy(x => ????))
{
console.Write(s.Name);
}
// The output I'm looking for
// Janice
// Sam
// Mark
// Fred
public List<Student> GetOrderedStudents(List<Student> students)
{
Student[] reverseOrder = new Student[students.Count];
Student last = students.Single(s => s.FollowedBy == null);
reverseOrder[0] = last;
Student next = last;
for (var i = 1; i < students.Count; i++)
{
next = students.Single(s => s.FollowedBy == next.StudentID);
reverseOrder[i] = next;
}
return reverseOrder.Reverse().ToList();
}
What you're trying to do isn't strictly sorting, and it won't support certain sort algorithms that rely on comparitive principles like A > B > C => A > C without making the implementation of IComparer aware of the entire set. Such an IComparer is likely to run much slower than simply sorting using a search.
It seems it would be easier to make this into a helper method (extension method if you want to use linq-like syntax) which used its own mechanics in order to search the set for each FollowedBy/StudentID combo.
You can find the root and then follow FollowedBy:
Dictionary<int, Student> dict = lstStudents
.ToDictionary(item => item.StudentID);
// root
Student s = dict[lstStudents
.Select(item => item.StudentID)
.Except(lstStudents
.Where(item => item.FollowedBy.HasValue)
.Select(item => item.FollowedBy.Value))
.First()];
for (; s != null; s = s.FollowedBy == null? null : dict[s.FollowedBy.Value]) {
Console.WriteLine(s.Name);
}

Linq query- Collection within a dictonary

I am trying to get result from a dictionary using linq but I am not sure how to do it.
I am trying to write a linq query to get the employee name,salary of the empId=2. Please correct me with the right linq query.
class Program
{
static void Main(string[] args)
{
Dictionary<int, List<Employee>> empDic = new Dictionary<int, List<Employee>>();
var emp = new List<Employee>();
emp.Add(new Employee{EmpID = 1,Name="affsa",Salary=2000}) ;
emp.Add(new Employee { EmpID = 2, Name = "axy",Salary=3000 });
emp.Add(new Employee { EmpID = 3, Name = "xyz",Salary=4000 });
empDic.Add(1,emp);
var selectedEmpDetails = empDic.Values
.Select(r => r.Where(f => f.EmpID ==2))
.ToList();
}
}
public class Employee
{
public string Name { get; set; }
public int EmpID { get; set; }
public int Salary { get; set; }
}
You could try something like this:
var selectedEmpDetails = empDic.SelectMany(x=>x.Value)
.Where(x=>x.EmpId==2)
.Select(x=> new { Name = x.Name, Salary = x.Salary });
Initially we flatten the values of the dictionary in a list of Employee objects.
Then we select the object with the EmpId equals to 2.
Last we select it's name and salary properties.
I would use SelectMany() function in your case so that I get a flattened list of Employee instances. Then try to get the first Employee instance that matches the employee id (if any) with the FirstOrDecault() function.
var selectedEmpDetails = empDic.Values.SelectMany(r => r.Where(f => f.EmpID == 2)).FirstOrDefault();
if(selectedEmpDetails != null)
{
string employeeName = selectedEmpDetails.Name;
int employeeSalary = selectedEmpDetails.Salary;
}

get elements from list based on another list

I got two classes, like:
public class Person
{
public long Id { get; set; }
public string Name { get; set; }
}
public class Vampire
{
public long Id { get; set; }
}
Then, I have two lists, a list of persons and a list of vampires. All vampires are persons.
What I need is two children lists of persons, infected and notInfected. I'm building the two lists with a for, but I know it's possible using linq or something.
Any help?
Something like this:
var vampireIds = new HashSet<long>(vampireList.Select(x => x.Id));
var infectedPersons = personList.Where(x => vampireIds.Contains(x.Id));
var regularPersons = personList.Where(x => !vampireIds.Contains(x.Id));
I would go with something like the following:
void Main()
{
var list = new List<Person>(){ new Person(){ Id = 1 }, new Vampire(){ Id = 2 } };
var infected = list.Where (x => x is Vampire);
var notInfected = list.Except(infected);
}
public class Person
{
public long Id { get; set; }
public string Name { get; set; }
}
public class Vampire : Person
{
}
If only a person can be a Vapire, you could inherit Vampire from Person and then iterate through all persons and see if they are Vampires; if yes -> add to Vampire list, otherwise to non-Vampire list.
Try this:
var people = new List<Person>
{
new Person {Id = 1, Name = "John"},
new Person {Name = "Dave", Id = 2},
new Person {Id = 3, Name = "Sarah"}
};
var vamps = new List<Vampire> {new Vampire {Id = 1}};
var theInfected = people.Where(p => vamps.Select(v => v.Id).Contains(p.Id));
var theAfraid = people.Except(theInfected);
foreach (var person in theInfected)
{
System.Console.WriteLine(person.Name + " Is Infected!");
}
foreach (var person in theAfraid)
{
System.Console.WriteLine(person.Name + " Is Afraid!");
}
Hope it's helpful.

Categories

Resources