How to include list based on a condition in LINQ - c#

I have this code:
ClassroomList.Where(x => x.StudentList.Any(y => y.IsMale) == true);
this code returns ClassroomList only if all student in StudentList IsMale == true but returns none if there is at least 1 IsMale == false.
How do I always return ClassroomList that only includes those Students with IsMale == true.
Here is ClassroomList's Class:
public partial class ClassroomList
{
public ClassroomList(){}
public list<Student> StudentList {get; set;}
}
Here is Student Class:
public partial class Student
{
public Student (){}
public bool IsMale {get; set;}
}
Here's a sample of the expected output
Classroomlist Count | Studentlist Count | Male | Female |
2 5 3 2
2 10 10 0
2 8 0 8
Expected output:
1. ClassroomList[1].Studentlist[2]
2. ClassroomList[1].Studentlist[9]
3. ClassroomList[1].Studentlist == null

Add an inner where filter.
ClassroomList.Where(x => x.StudentList.All(y => y.IsMale) );
Update:
So you need like this?
ClassroomList.Select(x => new ClassroomList{
StudentList = x.StudentList.Where(y => y.IsMale).ToList()
}
);

Based on the edited expected output, you can use this:
var results = (from r in ClassroomList
let grp = r.StudentList.GroupBy(x => x.IsMale)
select new
{
ClassRoomsCount = ClassroomList.Count(),
StudentsCount = r.StudentList.Count,
Male = grp.Where(x => x.Key).Count(),
Female = grp.Where(x => !x.Key).Count()
}).ToList();
Side Note : you can remove this line public ClassroomList(){} the compiler already creates a hidden default constructor, so no need to do it yourself.

You are all most there, just use All extension.
ClassroomList.Where(x => x.StudentList.All(y => y.IsMale));
After you update the question, seems you need something like this.
var result = ClassroomList
.Select(x=> new
{
StudentListCount = x.StudentList.Count(),
MaleCount = x.StudentList.Count(c=>c.IsMale),
FemaleCount = x.StudentList.Count(c=>!c.IsMale),
};

Related

Delete records with multiple ids based on condition

Below is my class :
public partial class Ads
{
public int Id { get; set; }
public int RegionId { get; set; }
public string Name { get; set; }
public int Group { get; set; }
}
Records :
Id Name Group
1 abc 1
2 xyz 1
3 lmn 1
4 xxx 2
5 ppp 2
6 ttt 3
7 ggg 3
Now I want to remove all records/only that record with particular id of same group for some ids.
Code :
public void Delete(int[] ids,bool flag = false)
{
using (var context = new MyEntities())
{
context.Ads.RemoveRange(
context.Ads.Where(t => (flag ?
(context.Ads.Any(x => ids.Contains(x.Id) && x.Group == t.Group)) : false)));
context.SaveChanges();
}
}
What I am trying to do is something like below :
If flag is false with ids=3,5 then
I want to delete only records with Id=3,5
Else if flag is true with ids=3,5 then
I want to delete records with Id=3,5 but all other records too of the group to which ids=3,5 belong to.
Here id=3 belongs to group 1 so I want to delete all records of group1 i.e id=1,2 like wise ids=5 belongs to
group 2 so I want to delete all records of group 2 i.e id=4.
Expected output for this last case(flag=true) :
Id Name Group
6 ttt 3
7 ggg 3
But I think that I haven't done this is a proper way, and there is some source of improvement in the query.
Note : ids[] will always contains ids from different group and that too highest ids from different group.
How can I to improve my query for both the cases(flag=true and false)?
What about
var removeRecs=context.Ads.where(t => ids.contains(t.id))
if(flag)
removeRecs.AddRange(context.Ads.where(t=> removeRecs.Any(r =>t.groupId==r.Id)))
Ads.RemoveRange(removeRecs);
Do not make it too hard for your self, not everything must/can be done in the where statement of a query. Also a general rule of thumb in a loop try to factor out all the constant values and checks. So try this:
public static void Delete(int[] ids, bool flag = false)
{
using (var context = new MyEntities())
{
var query = context.Ads.AsQueryable();
query = flag
? query.Where(x => context.Ads
.Where(i => ids.Contains(i.Id))
.Select(i => i.Group)
.Contains(x.Group))
: query.Where(x => ids.Contains(x.Id));
context.Ads.RemoveRange(query);
context.SaveChanges();
}
}
public void Delete(int[] ids, bool flag = false)
{
using (var context = new MyEntities())
{
var items = context.Ads.Where(x => ids.Any(a => x.Id == a));
if (!flag)
{
//flag=false --> delete items with Id in ids[]
context.Ads.RemoveRange(items);
}
else
{
var groups = items.GroupBy(a => a.Group).Select(a => a.Key);
//flag=true --> delete all items in selected groups
context.Ads.RemoveRange(context.Ads.Where(x => groups.Any(a => x.Group == a)));
}
context.SaveChanges();
}
You should separate your tasks...
if (flag)
{
groupIds = db.Ads.Where(x => ids.Contains(x.Id)).Select(x => x.Group).ToList();
db.Ads.RemoveRange(db.Ads.Where(x => groupIds.Contains(x.Group)).ToList());
}
else
{
db.Ads.RemoveRange(db.Ads.Where(x => ids.Contains(x.Id)).ToList());
}
To me it looks like you have two different deletes here.
In first case you are only deleting the ads with given ID and this is pretty straight forward.
In second case you are deleting the ads with given ID and all other ads that contain the group of the recently deleted Ads. So in this case instead of deleting the ads with given Id first why not actualy get distinct groups for these ID-s and than just delete the groups.
EDIT
You can do it like this.
using (var context = new TestEntities())
{
if (!flag)
context.Ads.RemoveRange(context.Ads.Where(a => ids.Contains(a.Id)));
else
context.Ads.RemoveRange(context.Ads.Where(a => context.Ads.Where(g => ids.Contains(g.Id)).Select(x => x.Group).Distinct().Contains(a.Group)));
context.SaveChanges();
}
For the more complicated case I am trying to get distinct groups for given id-s. So for ID-s 3 and 5 I am selecting the groups and than I am doing distinct on the groups since it might happen that the id-s have the same group. Than I am fetching all the ads that have these groups. So for passed values of 3 and 5 I would get groups 1 and 2 which I would than use to get all the ads that have that group. That in turn would yield id-s 1, 2, 3, 4, and 5 which I would than delete.
EDIT 2
If the complexity of second Linq query bothers you than write a SQL query.
context.Database.ExecuteSqlCommand(
"DELETE Ads WHERE Group IN (SELECT Group FROM Ads WHERE Id IN(#p1, #p2))", new SqlParameter("#p1", ids[0]), new SqlParameter("#p2", ids[1]));
This should be extra performant rather than rely on EF which will delete it one by one.

C# where and select

Can someone please help to explain what's happening in the following code? Many thanks! The result is meo but I don't understand how the two 'where' work in this context.
public class Cat {
public string Text { get; set; }
public Cat Where(Func<Cat,bool> cond) {
return new Cat {
Text = cond(this)? this.Text.ToUpper(): this.Text.ToLower()
}; }
}
public static class CatExtensions {
public static T Select<T>(this Cat cat, Func<Cat,T> proj)
{
return proj(cat);
}
}
var moggy = new Cat { Text = "Meo" };
var result = from m in moggy
where true
where false
select m.Text;
It's easier to understand if you look at the method-chaining syntax version of that expression:
moggy
.Where(m => true) // returns new Cat { Text = "MEO" }
.Where(m => false) // returns new Cat { Text = "meo" }
.Select(m => m.Text); // returns "meo"
from a in B where E is considered the same as B.Where(a => E).
Because of the fact that the class defines its own Where methods, these are used instead of those defined by Linq, as instance methods are always chosen over extension methods if available. Also those methods aren't applicable anyway.
The first returns an all uppercase MEO cat, then the second acts on that and returns an all lowers case meo cat.
The select is an extension method and applies the delegate to that last object.
These are multple Where clauses in LINQ.
Usually this is like a simple And conditon
For example say we have a Customer Entity and we have all customers stored in a variable results and we want to get the Customer whose FirstName is John and LastName is Doe, then we would do
results.Where(x => (x.FirstName == "John") && (x.LasttName == "Doe"));
Now this piece of code can be written with 2 Where clauses like
results.Where(x => (x.FirstName == "John"))
.Where(x => (x.LasttName == "Doe"));

Parent and Child sorting using Lambda expression in C#

I'm currently working on a project and working some lambda expressions (basics). And I have this requirement that I need to display the data based on hierarchy (parent and child).
Current Class:
public class Phones
{
public int ID { get; set; }
public string Phone { get; set; }
public int ChildOf { get; set; }
}
Current Data
ID Phone ChildOf
1 Samsung 0
2 Apple 0
3 GalaxyS3 1
4 GalaxyS4 1
5 5S 2
6 iPhone 6+ 2
I'm expecting a result of:
Results
**Samsung**
GalaxyS3
GalaxyS4
**Apple**
5S
iPhone6+
You can do this using the linq as below.
var result = phoneList.Where(x=>x.ChildOf!=0).GroupBy(x=>x.ChildOf).Select(g=> new {Key = phoneList.FirstOrDefault(x=>x.ChildOf==0 && x.ID ==g.Key).Phone,Value = g.Select(x => x.Phone)});
But mind you phoneList.FirstOrDefault(x=>x.ChildOf==0) this is happening just because you want the Phone Name for the grouping Key.
You can do the separate dictionary to get the values and use it. But if you have the parent ID then why use the name. You can use the parent ID whenever you required from the group key. IF you are to do this,
var result = phoneList.Where(x=>x.ChildOf!=0).GroupBy(x=>x.ChildOf).Select(g=> g.Key,g.ToList());
And when you need to show that on the UI Just replace the Key with the matching parent ID. That would reduce the lot of performance cost.
Would something like this suffice:
var lookup = phoneData.Where(e => e.ChildOf == 0)
.ToDictionary(id => id.Id, value => value.Phone);
var result = phoneData.Where(e => e.ChildOf > 0)
.GroupBy(e => lookup[e.ChildOf])
.Select(g => new { Parent = g.Key, Children = g.Select(x => x.Phone) });
Where phoneData is an enumerable of your raw data.

How to use Group by of two elements in an object and get the count in List?

I have a List(Student) where Student class is like
public class Student
{
string Name;
string Class;
string Division;
}
I have an other class like StudentCount.
public class StudentCount
{
string Class;
string Division;
string Count;
}
I have to populate the List(StudentCount) from List(Student).
SQL statement for above result
select Class,division,count(*)
from Student
group by Class, Division
Students Data
Name - Class - Division
Aa - 1 - A
Bc - 1 - A
Cc - 1 - B
Dd - 2 - A
I am looking for a result like
Expected Result
Class - Division - Count
1 - A - 2
1 - B - 1
2 - A - 1
I have the student data in a List (Student).
I have to get the result in List (StudentCount)
Can anyone help to achieve the result through in C#?
Assuming you promote your internal fields to public properties, I believe the following Linq query fulfills your requirements.
students
.GroupBy(s => new{s.Class, s.Division})
.Select(g => new StudentCount{Count = g.Count(),
Class = g.Key.Class,
Divison = g.Key.Division})
You can use an anonymous object for grouping:
var groups = data.GroupBy(item => new { item.Class, item.Division });
.Select(item => new StudentCount
{
Class = item.Key.Class,
Division = item.Key.Divison,
Count = item.Count()
});
First make your class members public (However its not recommended to have public fields in the class). Like this:
public class Student
{
public string Name;
public string Class;
public string Division;
}
Then use this:
var groups = data.GroupBy(item => new { item.Class, item.Division })
.Select(item => new StudentCount()
{
Class = item.Key.Class,
Division = item.Key.Division,
Count = item.Count().ToString() //the Count is of type string, so don't forget to make the proper conversion
});

How to execute nested query in Linq

I have a LINQ class of this format in C#:
class Vehicle
{
int _VehicleID
int _ModelID
EntetySet<Cars> _AllCars
EntetySet<Bus> _AllBus
EntetyRef<Driver> _Person
}
Cars table
CarsID | Manufacturer | Type
1000 | Honda | Diesel
1001 | Mitsubishi | Petrol
1002 | Maruti | Diesel
Bus table
BusID | Manufacturer | Type
2000 | Volvo | Diesel
2001 | TATA | Petrol
2002 | layland | Petrol
From the UI, I will get a Vehicle ID as a parameter. Based on that, all the cars, Bus and its associated tables will get copied to a variable. That is:
Vehicle v1 = new Vehicle();
v1 = dc.vehicle.where(v => v.vehicleID == param.vehicleID).findfirst();
My v1 has all the table and its contents which satisfies the above condition.
Now, I want to filter some more based on the table content present in table cars and bus; i.e based on type of vehicle petrol or diesel.
If I want to copy all the petrol cars and Buses using a query statement in a single line, please tell me the way.
Thanks in advance.
** Edit based upon comments **
The logic of your question somehow slipped by me, because I was taking a logical Human approach on vehicle. Whenever I look at this Vehicle class I instantly see cars and busses as vehicles to.
This indicates more that the naming is poorly chosen.
But no more judging from me.
I understand you want one vehicle by id and you want the result to only contain cars and busses riding on let's say Petrol, in a single line.
If it is translated to SQL than I don't know how far the tree can go within a select, but if it is in memory than you can go a long way inside a subselect.
You could use a 'Model' class to achieve that or you could go with an anonymous type, but I will give an example for a 'Model' class:
Example:
public class VehicleModel
{
public int VehicleID { get; set; }
public int ModelID { get; set; }
public List<Cars> Cars { get; set; }
public List<Bus> Buses { get; set; }
}
var vehicleID = 1; // just for the example of course.
var fuelType = "Petrol";
var vehicle = (from v in dc.vehicle
where v._VehicleID == vehicleID
select new VehicleModel
{
VehicleID = v._VehicleID,
ModelID = v._ModelID,
Cars = v._AllCars.Where(car => car.Type == fuelType).ToList(),
Buses = v._AllBus.Where(bus => bus.Type == fuelType).ToList()
}).SingleOrDefault();
// use FirstOrDefault() when _VehicleID is NOT unique to get TOP 1
Do not modify the EntitySets within the Entity itself, always use a model for these things, because if you would do that and call save changes on your EntityContainer all sorts of things could go wrong.
If you want to know about anonymous types take a look here:
http://msdn.microsoft.com/en-us/library/bb397696(v=vs.100).aspx
Also take a look here for Linq examples:
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
- before edit
Do you mean this:
var query = dc.vehicle.Where(v =>
v._AllCars.Any(c => c.Manufacturer == "Honda") ||
v._AllBuss.Any(b => b.Manufacturer == "Volvo"));
This will give you all vehicles where the cars are Honda Or the Busses are Volvo.
Which will be:
1000 | Honda | Diesel
2000 | Volvo | Diesel
the result is an IEnumerable containing all or no items satisfying the criteria.
If you only want the first hit you can do:
//if it must exist, otherwise this throws an exception
var firstVehicle = query.First();
//if it may exist, otherwise null
var firstVehicle = query.FirstOrDefault();
It seems that you want something like this:
var v1 = dc.vehicle.Where(v => v.vehicleID == param.vehicleID
&& v.Cars.All(c => c.Type == "Petrol")
&& v.Buses.All(c => c.Type == "Petrol")).First();
)
to get a "Vehicle" of which all cars and buses have petrol. (Note that I use "Cars" and not the unconventional name "_AllCars", etc.).
Edit
Or:
var v1 = dc.vehicle.Where(v => v.vehicleID == param.vehicleID)
.Select(v => new {
Id = v.vehicleID,
Cars = v.Cars.Where(c => c.Type == "Petrol"),
Buses = v.Buses.Where(c => c.Type == "Petrol")
}).First();
to get an anonymous type with filtered collections.

Categories