I'm trying to solve a problem where I need to filter a list which hold my custom reference object. Search criteria is based on nested properties. For a reference guide, let consider Student and Subject classes.
public class Student
{
public String Name {get;set;}
public List<Subject> Subjects {get;set;}
}
public Subject
{
public String Name {get;set;}
}
Not only I want to search Student by their names but the same search should also work with subject names as well. I've a single field where the text can be entered. For student search by their names, I've done:
FilteredList = Students.Where(s => s.Name.Contains(searchQuery));
Now, I also want to search students by the subject names but only want to show the matching results. A student can take many courses but a query of "Chemistry" should only show students who are taking this course but the rest of the courses they're taking should be ignored.
Basically my FilteredList is bound with ListView and Subjects list should only contain matching results. I'm keeping original source aside as Students. Any help implementing this search is highly appreciated.
You could use the LINQ function Any in this case. Something like this should give you the indented results :
FilteredList = Students.Where(s => s.Subjects.Any(subs => subs.Name.Contains(subjectSearchQuery))
If you want to use both filters at the same time, you can chain
FilteredList = Students.Where(s => s.Name.Contains(searchQuery))
.Where(s => s.Subjects.Any(subs => subs.Name.Contains(subjectSearchQuery))
EDIT : Seems like I understood the question wrong, here is what I think is the right answer (see the comments on this answer)
In this you want to use Select, in a fashion like this :
FilteredList = Students.Where(s => s.Name.Contains(studentNameFilter))
.Select(s => new Student()
{
Name = s.Name,
subjects = s.Subjects.Where(sub => sub.Name.Contains(subjectNameFilter))
});
This should give you the results you want.
Related
I have a list of Students, each student can enter one or more addresses.
I have to find either any of the addresses overlapped in the list and then retrieve those overlapping objects from the list.
below is the example of the list of objects
[
{
"ID" : 1,
"Addresses": ["SRJ", "SJ"]
},
{
"ID" : 2,
"Addresses": ["SJ"}
},
{
"ID" : 3,
"Addresses": ["FRT", "FRI"}
},
{
"ID" : 4,
"Addresses": ["NR", "SJ"}
},
]
in the above list SJ is a repeated string in the three of the objects so, I have to return those three objects from the list with ID 1,2,4.
I have already done the above using loops but I am looking into the most efficient way of doing this task.
Assuming each list item in your question is mapped to a class like below:
public class AddressRecord
{
public int ID { get; set; }
public List<string> Addresses { get; set; }
}
You can then use a Linq expression like below to find duplicates and construct an object that tracks all duplicate addresses and IDs those addresses belong to:
var result = list.SelectMany(x => x.Addresses).GroupBy(x => x).Where(x => x.Count() > 1)
.Select(x => new { x.Key, IDs = list.Where(y => y.Addresses.Contains(x.Key)).Select(y => y.ID).ToList() })
.ToList()
First line in linq expression flattens the address list, runs a groupby expression on them and finds all addresses that exist more than once (across all "addresses" lists.)
Then the second line select each address with duplicates and IDs corresponding to that address. (Note that x in x.Key is the IGrouping object created by the groupby expression and x.Key is the address that the grouping was created on.)
The result object should look something like this when serialized:
[{"Key":"SJ","IDs":[1,2,4]}]
Runtime performance wise, a handmade for loop would most certainly beat this expression. However, from maintainability perspective, this linq expression is likely much easier to manage. (This does depend on comfort level of team with linq.)
I assume that you have a given address and want to check if that exists somewhere else:
string givenAddress = "SJ";
List<Student> overlappingAddressesStudents = students
.Where(s => s.Addresses.Contains(givenAddress))
.ToList();
This might not be more efficient that your loop approach, but maybe it's more readable.
I have a class Employee:
public class Employee
{
public string SSN;
public string empNumber;
public int someValue;
}
I want to check if the employees share a similar SSN AND a similar empNumber. I have a List<Employee> available to search through. Employees cannot have the same SSN and empNumber. Ultimately, I want to populate a list that contains the employees that share only SSN and empNumber. If this list is not a size 0, then I know to send an error message.
I know I can use LINQ or a foreach, but I am not sure which would be best for this situation.
Seems like a pretty simple GroupBy - assuming your List<Employee> is in a variable employees:
var dupes = employees.GroupBy(e => new {e.SSN, e.empNumber})
.Where(g => g.Count() > 1);
The variable dupes will now contain an enumerable list of anonymous objects with the properties
SSN
empNumber
which will represent your duplicates. Each item is also itself an IEnumerable<Customer> containing the duplicates themselves (from the original list).
I am creating a videorental system and I'm told that there are to be multiple entries of the same movie, as in a real videostore, but they should all have unique IDs. I'm using LINQ to fetch the data, and currently my view shows me the entire table, so the same movie name gets repeated alot. I tried using the .Distinct extension method, but since the IDs are not the same, they are all distinct. I need to ignore the ID portion of the object, but can't figure it out.
public static List<Movie> GetAllMovies()
{
return Context.Movie.ToList();
}
public static List<Movie> GetAllMoviesDistinct()
{
return Context.Movie.Distinct().ToList();
}
These two methods do the exact same thing
You can do this with MoreLINQ:
var result = Context.Movie
.DistinctBy(m => new {
// take all properties except your ID
})
.ToList();
You can use GroupBy to get movies with for example unique name.
return Context.Movie.GroupBy(m => m.Name).Select(x => x.First()).ToList();
You'd need something like this:
public static List<string> GetAllMovieTitlesDistinct()
{
return Context.Movie.GroupBy(x => x.Title).Select(x => x.Key).ToList();
}
Since you have multiple entries for the same movie, it doesn't really make sense to return a random (or just the first) movie with a specific name. What I'd rather do, is get the unique movie titles, select one title and then list all the entries for that.
What the method above does is, it groups all the movies by their names and then just selects each unique movie title. Please note that it does not return a specific entry for each title, as that would be highly arbitrary and might lead to unwanted results.
var groupesList = Context.Movie.GroupBy(x => x.Name,
(key, val) => new {Key = key, Value = val}).ToList();
then you can call Key(unuque) or Value by key for all inform for example all ID
If this is a duplicate, I apologize; I have done my share of searching, but I have figured out what to search for.
Let's say you have a student database and you want to average their scores based on gender. With your standard issue relational database, this is pretty trivial. It might require a query with an explicit join, or you may just use navigation properties or something, but it's going to look a little like this:
var averageScore = db.Grades
.Where(grade => grade.Student.Gender == selectedGender)
.Average();
But what if you're connected to a document-based system and your data structure is, instead, just a Student object with a collection of Grade objects embedded in it?
var averageScore = db.Students.GroupBy(student => student.Gender)
.ThisDoesNotWork(no => matter.What);
I have tried three dozen different ways to do a GroupBy that manages to transform collections of values into a single collection of values sharing a common key, but none of them have worked. Most of my attempts have involved attempting a SelectMany inside the GroupBy, and--if that's possible--let's just say that the compiler doesn't like my bedside manner.
Edit: Not sure what you mean by "format." The data structure we're talking about is just a class with a collection as one of its members.
class Student
{
public string Name { get; set; }
public Gender Gender { get; set; }
public ICollection<int> Grades { get; set; }
}
SelectMany will flatten the collection for you.
var average = db.Students
.Where(s => s.Gender == selectedGender)
.SelectMany(s => s.Grades)
.Average();
GroupBy, on the other hand, will group specific elements together. So, if you want to group all by gender:
var averages = db.Students
.GroupBy(
s => s.Gender,
(gender, group) => group
.SelectMany(s => s.Grades)
.Average());
"group" is an IEnumerable, ie. all the students that fit each group.
Rich's answer got me thinking a little harder about SelectMany() (plus I got off work and was bored), so I put in a little more work and here's what I've got:
var averagesByGender = db.Students
.SelectMany(
student => student.Grades,
(student, grade) => new { Gender = student.Gender, Grade = grade })
.GroupBy(
record => record.Gender,
record => record.Grade)
.Select(group => new { group.Key, Average = group.Average() });
The SelectMany() works pretty much exactly like a join statement would in any SQL database: you get one record per grade with the associated student information, and from there you can query whatever you want to in the old-fashioned way (or, as in my example, you can get one result for each of the genders represented).
The only wrinkle is that, apparently, this is too relational... As in RavenDB refuses to try to translate it into a query. Luckily enough, that's irrelevant if you hide it behind .ToList(). Wonder if it will work the same way with MongoDB.
I have a situation where i display a list of products for a customer. So, there are two kinds of products. So, if customer is registerd to two products, then both the products get displayed. So, I need to display distinct rows. I did this:
var queryProducts = DbContext.CustomerProducts.Where(p => p.Customers_Id ==
customerID).ToList().Select(r => new
{
r.Id,
r.Products_Id,
ProductName = r.Product.Name,
ShortName = r.Product.ShortName,
Description = r.Product.Description,
IsActive = r.Product.IsActive
}).Distinct();
In this, customerID is the value that i get from dropdownlist. However, it still displays the same row twice. So, can you please let me know how i can display only distinct records.
The most likely reasons could be that Distinct when called with no parameter by default compares all the public properties for equality. I suspect your Id is going to be unique. Hence the Distinct is not working for you.
You can try something like
myCustomerList.GroupBy(product => product.Products_Id).Select(grp => grp.First());
I found this as answers to
How to get distinct instance from a list by Lambda or LINQ
Distinct() with lambda?
Have a look at LINQ Select Distinct with Anonymous Types
I'm guessing r.ID is varying between the two products that are the same, but you have the same Products_Id?
You can write an implementation of IEqualityComparer<CustomerProduct>. Once you've got that, then you can use this:
DbContext.CustomerProducts.Where(p => p.Customers_Id == customerId)
.ToList()
.Distinct(new MyComparer())
.Select(r => new {
// etc.
public class MyComparer : IEqualityComparer<CustomerProduct>
{
// implement **Equals** and **GetHashCode** here
}
Note, using this anonymous comparer might work better for you, but it compares all properties in the anonymous type, not just the customer ID as specified in the question.