Nest foreach cycle and adding items to collection into linq methods - c#

I have the following classes:
class Person
{
public string Name { get; set; }
}
class Student
{
public string Name { get; set; }
}
I have a list of persons:
IList<Person> persons = new List<Person>
{
new Person() { Name = "Bill" },
new Person() { Name = "Bob" },
new Person() { Name = "Henry" },
};
And I adding items to new collection in foreach loop:
IList<Student> students = new List<Student>();
//Is it possible to nest the following rows in linq method?
foreach (var person in persons)
{
students.Add(new Student() { Name = person.Name });
}
Is it possible to nest foreach and adding items into linq method?

If you mean replacing the foreach loop with a LINQ query (not quite sure why you are talking about nesting loops in your question) then you could try this:
IList<Student> students = persons.Select(p => new Student { Name = p.Name }).ToList();
or if you prefer:
IList<Student> students = (from p in persons select new Student { Name = p.Name }).ToList();

Related

Retrieve data from the nested list

How can I get all data from a nested list in C#?
Each person can have multiple cars.
Now I have a list of people and each item has a list of cars.
public class Person
{
public string FullName { get; set; }
public List<Car> Cars { get; set; }
}
public class Car
{
public string Name { get; set; }
}
And values:
var people = new List<Person>()
{
new Person()
{
FullName = "Jack Lee",
Cars = new List<Car>()
{
new Car()
{
Name = "BMW"
},
new Car()
{
Name = "Tesla"
}
}
},
new Person
{
FullName = "Micheal doug",
Cars = new List<Car>()
{
new Car()
{
Name = "Ferrari"
}
}
}
};
What is the best way to get all car names with one query?
Is it possible to get all cars with one query?
With System.Linq, you can use .SelectMany() to flatten the Cars lists from the people list and get the Name.
using System.Linq;
var result = people.SelectMany(x => x.Cars)
.Select(x => x.Name)
.ToList();
Demo # .NET Fiddle

Add element to a List when delcaring new object

Is there a way to add elements to a List when doing this:
var Foo = new MyClass() {
PropertyList = MyList,
Id = Id,
}
I would like to add elements to PropertyList. For example would be the same as: MyList.Add()
The problem is that i do not have a list called MyList but i rather have elements that i want to append to PropertyList
Updating code based on comments:
var result1 = await query
.GroupBy(c => new {
c.CommissionId, c.ActivityId
})
.Select(grp => new RegistrationStatisticViewModel() {
CommissionId = grp.Key.CommissionId,
CommissionCode = grp.First().Commission.Code,
CommissionDescription = grp.First().Commission.Description,
MinuteWorked = grp.Sum(c => c.MinuteWorked),
ActivityId = grp.Key.ActivityId,
ActivityCode = grp.First().Activity.Code,
ActivityDescription = grp.First().Activity.Description,
})
.ToListAsync();
var grps = from d in result1
group d by d.CommissionId
into grp
select new RegistrationStatisticViewModel() {
CommissionId = grp.Key,
ActivityList = new List < Activity > {
new Activity {
//ActivityId = grp.Select(d => d.ActivityId),
//Code = grp.Select(d => d.ActivityCode),
//Description = grp.Select(d => d.ActivityDescription),
}
},
CommissionCode = grp.First().CommissionCode,
CommissionDescription = grp.First().CommissionDescription,
MinuteWorked = grp.First().MinuteWorked
};
return grps;
To give context:
forget the result1 is just some data i retrieve from my database
Commission is one class and contains:
CommissionId
Code
Description
Activity is one class and contains:
ActivityId ==> type GUID
Code ==> type string
Description ==> type string
Now the var = grps is a LINQ that gets the data and then instatiates a new object (class) new RegistrationStatisticViewModel()
So the tricky part we were discussing before is when i populate ActivityList with multiple activities.
When populating the list if i use .First() or .Select() i would only get one instance and therfore the list would only have one activity.
It worked when using .ToArray() for example if i replace ActivityList with just the ActivityId of type string (so a new property on RegistrationStatisticViewModel that is not a list anymore):
I can do this ActivityId = grp.Select(d2 => d2.ActivityId).ToArray()
And it will give me an array of all the ActivityId linked to that commissionId
I am sorry if this is confusing but it is for me as well. I would thank you if you could help me. No worries if you can't you have already give me very helpful answers, so i thank you for that!
Based on your remarks, I believe this is what you are trying to achieve:
public class PersonContainer
{
public IList<Person> Persons { get; set; }
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
var personContainer = new PersonContainer
{
Persons = new List<Person>
{
new Person
{
Name = "John Doe",
Age = 28,
},
new Person
{
Name = "Jane Doe",
Age = 27,
},
}
};
Here, the Persons property of PersonContainer is initialized and populated with Person elements during instantiation.

How to iterate over all entries in a single GridView's column?

Consider the following grid view:
Its fairly empty at the moment but will be more populated.
peronID column will always be in the same place. How can I iterate over the first column, counting all personID's equal to a given amount? Something like this (pseudocode) :
foreach element in personID
if element == 25
count += 1
Code that populates the GridView:
private void DisplayTable()
{
LibraryEntities1 myDB = new LibraryEntities1();
var people = myDB.People;
gridViewPeople.DataSource = people.ToList();
gridViewPeople.DataBind();
}
I suggest you operate on the data, not on the grid, so assuming the List below is what you might have bound to your grid:
Given class Person:
class Person
{
public int PersonID { get; set; }
public string PersonName { get; set; }
}
You can group by and sum just like you might do in SQL:
List<Person> persons = new List<Person>{
new Person{ PersonID = 13, PersonName = "Foo" },
new Person{ PersonID = 13, PersonName = "Foo" },
new Person{ PersonID = 15, PersonName = "Foo" }
};
var grp = persons.GroupBy(p => p.PersonID)
.Select(p => new {pid = p.Key, count = p.Count()});
foreach (var element in grp)
{
Console.WriteLine($"PersonID = {element.pid}, Count of Person = {element.count}");
}
Output:
PersonID = 13, Count of Person = 2
PersonID = 15, Count of Person = 1

Getting repeated data from a CSV file

I have a person class like so:
class Person
{
string Id { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
}
There is a CSV file which has person data like
"123","ABC","DEF"
"456","GHI","JKL"
"123","MNO","PQR"
...
A person is unique based on the Id.
The CSV is read like this:
using (StreamReader sr = new StreamReader(inputFile))
{
string[] arrCsvData;
string strLine;
while ((strLine = sr.ReadLine()) != null)
{
arrCsvData = strLine.Split(',');
this.LoadPersonData(arrCsvData);
}
}
In LoadPersonData a new Person object is created and assigned the values from CSV:
Person objPerson = new Person();
for (int i = 1; i <= arrCsvData.Length - 1; i++)
{
// Assign person property values from arrCsvData
}
I have a dictionary object in which the key is the ID and the value is the Person object.
if(!this.PersonDataCollection.ContainsKey(personKey))
{
this.PersonDataCollection.Add(objPerson);
}
This gives me all the unique Person objects from the CSV file.
I want to create a list of those Person objects which are repeated based on Id in the CSV.
So the list DuplicatePersons will have:
"123","ABC","DEF"
"123","MNO","PQR"
in it.
The bare bones way is to first read all the person objects into a list and then do a LINQ query to get all the duplicates in a separate list. This way I have to create an extra collection just to get the duplicates.
There should be a better way than creating a separate list.
Any pointers?
First of all, I would use LINQToCSV. Parsing CSV files is more complicated than just splitting by ,. You don't need to code anything, just create your class, and place attributes on it:
class Person
{
[CsvColumn(Name = "ID", ...)]
string Id { get; set; }
[CsvColumn(Name = "First Name", ...)]
string FirstName { get; set; }
[CsvColumn(Name = "Last Name", ...)]
string LastName { get; set; }
}
Then when you read the file using LINQToCSV, you get an IEnumerable<Person>... and then you can do:
IEnumerable<Person> people = ... //read here using LINQToCSV
var grouped = people.GroupBy(p => p.Id);
If you will know the the unique column at runtime, you can do something like this:
string columnName = "Id";
persons.GroupBy(x => x.GetType().GetProperty(columnName).GetValue(x, null));
although you will have to see how much it affects you in performance. Another way that doesn't require Reflection could be:
Dictionary<string, Func<Person, object>> selectors = new Dictionary <string, Func<Person, object>>
{
{"Id", x => x.Id},
{"FirstName", x => x.FirstName},
{"LastName", x => x.LastName},
};
string columnName = "Id";
var grouped = people.GroupBy(selectors[columnName]);
Now, using your approach... what's wrong with creating another dictionary?
You could have something like:
//Here you will store the duplicated person
//Key: The person Id
//Value: The list of person sharing that same Id
Dictionary<string, IList<Person>> duplicatedPeople;
if(!this.PersonDataCollection.ContainsKey(personKey))
{
this.PersonDataCollection.Add(objPerson);
}
else
{
//Here we store all the people with this already existing ID
IList<Person> duplicatedPeople;
//If we already have this ID in the dictionary of repeated people
if (this.duplicatedPeople.TryGetValue(personKey, out duplicatedPeople)) {
//Just add this new person
duplicatedPeople.Add(objPerson);
}
//If this is the 1st time we found a duplicated person with this ID
else {
//We add two persons to the list: this one, and the one from PersonDataCollection.
duplicatedPeople = new List<Person> { this.PersonDataCollection[personKey], objPerson };
//Add it to the dictionary
this.duplicatedPeople.Add(personKey, duplicatedPeople);
}
}
Why Don't you check whether the values are already exist at this point.
Person objPerson = new Person();
for (int i = 1; i <= arrCsvData.Length - 1; i++)
{
// Assign person property values from arrCsvData
}
Check your condition here and do what ever you want with the duplicate values at that point.
Whatever you do.. there will always be a separate list. It is up to you on how you want them to come about though.
Option 1 - Temporary lists
Each time you query your existing dictionary, an in-memory result will be returned. Depending on how big your dataset is.. this may not be what you're after.
Option 2 - Static list
Why not just maintain your own list at this point?:
if(!this.PersonDataCollection.ContainsKey(personKey))
{
this.PersonDataCollection.Add(objPerson);
}
else
{
// Create a new dictionary for the duplicates
this.DuplicatePersonDataCollection.Add(objPerson);
}
Create a single list for all the persons and rather query it with LINQ to get your results:
ie:
var persons = new List<Person>();
persons.Add(new Person { Id = "123", FirstName = "AAA", LastName = "XXX" });
persons.Add(new Person { Id = "123", FirstName = "BBB", LastName = "WWW" });
persons.Add(new Person { Id = "456", FirstName = "CCC", LastName = "XXX" });
persons.Add(new Person { Id = "456", FirstName = "DDD", LastName = "YYY" });
persons.Add(new Person { Id = "789", FirstName = "EEE", LastName = "ZZZ" });
var duplicateKeys = persons.GroupBy(p => p.Id).Select(g => new { g.Key, Count = g.Count() }).Where(x => x.Count > 1).ToList().Select(d => d.Key);
var duplicatePersons = persons.Where(p => duplicateKeys.Contains(p.Id)).ToList();
var unique = persons.GroupBy(p => p.Id).ToList();

Updating list's element's properties

I have a class
class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
}
Now, I have created a List of Student object as:
List<Student> lst = new List<Student>();
Three properties of the class will come from three different Data Sources. So I cannot add all properties of object at a time. I am adding the ID property as following:
lst.Add(new Student { ID = 1 }, new Student { ID = 2 })
Now I want to set the values of the "FirstName" or "LastName" property in the existing list. How I can do this?
List<Student> lst = new List<Student>();
lst.Add(new Student { ID = 1 });
lst.Add(new Student { ID = 2 });
//Get the student you want by id then use that to populate the remaining properties
var temp = lst.Single(l => l.ID == 1);
temp.FirstName = "fname";
temp.LastName = "lastname";
Don't mix method Add() call:
list.Add(new Student());
and collection initialization syntax:
List<Student> list = new List<Student>
{
new Student(),
new Student()
};
Then
int index = 1;
Student student = list[index]; // may cause IndexOutOfRangeException
student.Name = "Me"; // may cause NullReferenceException
or shorter:
list[i].Name = "Me"; // may cause them both too
You can get the index from an element that matches with the element to update:
int index= MyObjectList.FindIndex(x=>x.Id==object.Id);
and after updating it:
MyObjectList[index]=object;
Im only guessing here... if , doesnt work try ;
lst.Add(new Student{ID=2, FirstName="The", LastName="Stig"});
Or you can define a constructor for the Student object...
public Student(int mID, string mFirstName, string mLastName)
{
ID = mID;
FirstName = mFirstName;
LastName = mLastName;
}
Then use
lst.Add(new Student(2, "The", "Stig"));
Assuming the other data sources contains the studentId and all students has a last and first name.
Join the lastNameList and firstNameList collections using linq. The studentId list you dont need since the other two has that value also.
var list =
(from lastName in lastNameList
join firstName in firstNameList on lastName.studentId equals firstName.studentId
select new Student()
{
.ID = lastName.studentId,
.FirstName = firstName.Name,
.LastName = lastName.Name
}).ToList();
You can use list.count-1 to get the last item in the list. Then you can update the properties you want.
List<Student> lst=new List<Student>();
lst.Add(new Student { ID = 1, FirstName = "FirstName", LastName = "LastName" });
//Then get props from other datasource
lst[lst.Count-1].FirstName="FirstName";
lst[lst.Count-1].LastName="LastName";

Categories