Converting list from one class to another - c#

I'm trying to convert a group a complex list in C# (with Linq)
public class classA
{
public string Name { get; set; }
public int id { get; set; }
public string phone { get; set; }
public string interest { get; set; }
}
My first class is classA where it contains many list of elements like below.
List<classA> obj = new List<classA>();
obj.Add(new classA { id = 1, Name = "a", phone = "321", interest = "Playing" });
obj.Add(new classA { id = 1, Name = "2", phone = "123", interest="Tv" });
From this I need to group by using the id, So I've used Linq
var item = obj.GroupBy(a => a.id).Select(ac => ac.ToList()).ToList();
I've another class called classB which hold's the values others than id from the classA (where it'd be hold all subset of different attributes)
public class classB
{
public string Name { get; set; }
public string phone { get; set; }
public string interest { get; set; }
}
My Final Class looks likes,
public class Final
{
public int id { get; set; }
public List<classB> details { get; set; }
public Final()
{
details = new List<classB>();
}
}
My requirements are, after grouping the classA based on id, I need to convert that into my final class.
So I did like below,
public static void Main()
{
Console.WriteLine("Hello World");
List<classA> obj = new List<classA>();
obj.Add(new classA { id = 1, Name = "a", phone = "321", interest = "Playing" });
obj.Add(new classA { id = 1, Name = "b", phone = "123", interest = "Tv" });
obj.Add(new classA { id = 2, Name = "c", phone = "12322", interest = "Tv" });
obj.Add(new classA { id = 3, Name = "d", phone = "12333", interest = "Tv" });
var item = obj.GroupBy(a => a.id).Select(ac => ac.ToList()).ToList();
List<Final> finalobjList = new List<Final>();
foreach (var report in item)
{
Final finalObj = new Final();
foreach (var result in report)
{
finalObj.id = result.id;
}
var data = report.Select(x => new classB { Name = x.Name, phone = x.phone, interest = x.interest }).ToList();
finalObj.details = data;
finalobjList.Add(finalObj);
}
Console.WriteLine(finalobjList.Count());
}
I believe there is another easy way to achieve this using linq without using foreach multiple times
Appreciate your help!

You should be able to use your existing code except when you do your Select, select a new Final and use the group's Key for the Id, and convert the ac.ToList to a list of ClassB for the Details:
var item = obj
.GroupBy(a => a.id)
.Select(ac =>
new Final
{
Id = ac.Key,
Details = ac.Select(a =>
new classB {interest = a.interest, phone = a.phone, Name = a.Name})
.ToList()
});

var finalobjList = obj.GroupBy(a => a.id).Select(x => new Final() { id = x.Key, details = x.Select(y => new classB() { Name = y.Name }).ToList() } ).ToList();
(Code only answer - please dont hate me)

var items = (from a in obj
group new classB {Name = a.Name, phone = a.phone, interest = a.interest} by a.id into aa
select new Final { id = aa.Key, B= aa.ToList()}).ToList();

Related

Using where in LINQ select new statement for specific columns

I'm working on a class assignment and got a bit lost in LINQ.
I have 3 tables, 'oltandok' contains the data of persons, 'preferenciak' contains the preferred vaccine of that person with 3 columns:
an FK for table oltandok
a number indicating the order of preferences (1 is highest, 6 is lowest preferred)
an FK for another table containing the data on the vaccines called 'vakcinak'
I would like to display the data in a DataGridView the following way:
Personal data and the preferred vaccines in different columns:
Pref1 - Name of the vaccine where pref == 1
Pref2 - Name of the vaccine where pref == 2
etc.
This is where I am with my code, but I'm not sure how to select the preferences properly.
manu_rogz.DataSource = ( from x in context.oltandok
join y in context.preferencia on x.TAJ equals y.oltandok_FK
select new
{
TAJ = x.TAJ,
Nev = x.nev,
Szuletesnap = x.birthdate,
Pref1 = ???
Pref2 = ???
}
).ToList();
Because the preferenciak table contains multiple rows per person, you will need to perform some grouping.
Here is some very rough code which illustrates one way to do that.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
var persons = new List<Person> { new Person { ID = 11, PersonName = "Alice" }, new Person { ID = 22, PersonName = "Bob" } };
var vaccines = new List<Vaccine> { new Vaccine(){ ID = 111, VaccineName= "Pfizer" }, new Vaccine(){ ID = 222, VaccineName = "Moderna" } };
var preferences = new List<VaccPref>
{
new VaccPref() { Person_FK = 11, Preference = 1, Vaccine_FK = 111 },
new VaccPref() { Person_FK = 11, Preference = 2, Vaccine_FK = 222 },
new VaccPref() { Person_FK = 22, Preference = 1, Vaccine_FK = 222 },
new VaccPref() { Person_FK = 22, Preference = 2, Vaccine_FK = 111 }
};
var prefsWithVaccNames = preferences.Join(vaccines, p => p.Vaccine_FK, v => v.ID, (pref, vaccine) => new Tuple<VaccPref, string>(pref, vaccine.VaccineName));
var groupedPrefs = prefsWithVaccNames.GroupBy(p => p.Item1.Person_FK);
var personPrefs = new List<PersonPrefs>();
foreach (var group in groupedPrefs)
{
personPrefs.Add(
new PersonPrefs()
{
Person_FK = group.Key,
Pref1 = group.Single(v => v.Item1.Preference == 1).Item2,
Pref2 = group.Single(v => v.Item1.Preference == 2).Item2,
});
}
var personPrefsWithPersonNames =
personPrefs.Join(
persons,
pp => pp.Person_FK,
p => p.ID,
(pp, p) => new NamedPersonPrefs() { Name = p.PersonName, Pref1 = pp.Pref1, Pref2 = pp.Pref2 }).ToArray();
}
}
class Person
{
public int ID { get; set; }
public string PersonName { get; set; }
}
class VaccPref
{
public int Person_FK { get; set; }
public int Preference { get; set; }
public int Vaccine_FK { get; set; }
}
class Vaccine
{
public int ID { get; set; }
public string VaccineName { get; set; }
}
class PersonPrefs
{
public int Person_FK { get; set; }
public string Pref1 { get; set; }
public string Pref2 { get; set; }
}
class NamedPersonPrefs
{
public string Name { get; set; }
public string Pref1 { get; set; }
public string Pref2 { get; set; }
}
This is a self-contained C# program which should produce a result similar to what you're after. You will of course need to adjust the class definitions (and change the table names) to suit your needs.
I've used LINQ's fluent syntax but you can use the SQL-like version if you prefer.

Compare list against other list and modify

Supposed that I have these classes
public class Subject
{
public int Id { get; set; }
public string Category { get; set; }
public string Type { get; set; }
}
public class Student
{
public int Id { get; set; }
public List<MySubject> MySubjects { get; set; }
}
public class MySubject
{
public int Id { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public string Schedule { get; set; }
public string RoomNumber { get; set; }
}
sample data
var subjects = new List<Subject>()
{
new Subject(){ Id = 1, Category = "Mathematics", Type = "Algebra" },
new Subject(){ Id = 2, Category = "Computer Science", Type = "Pascal" }
};
var student = new Student()
{ Id = 1, MySubjects = new List<MySubject>() {
new MySubject() {Id = 1, Category = "Mathematics", Type = "Algebra" },
new MySubject() {Id = 3, Category = "Mathematics", Type = "Trigonometry"},
}
};
//TODO: Update list here
student.MySubjects.ForEach(i => Console.WriteLine("{0}-{1}-{2}\t", i.Id, i.Category, i.Type));
the above line of code returns
1-Mathematics-Algebra
3-Mathematics-Trigonometry
which is incorrect. I need to return this
1-Mathematics-Algebra
2-Computer Science-Pascal
Basically I would like to modify and iterate the student.MySubjects and check its contents against subjects.
I would like to remove the subjects (3-Mathematics-Trigonometry) that are not present in the subjects and also ADD subjects that are missing (2-Computer Science-Pascal).
Can you suggest an efficient way to do this by searching/comparing using Category + Type?
Try like below.
// Remove those subjects which are not present in subjects list
student.MySubjects.RemoveAll(x => !subjects.Any(y => y.Category == x.Category && y.Type == x.Type));
// Retrieve list of subjects which are not added in students.MySubjects
var mySubjectsToAdd = subjects.Where(x => !student.MySubjects.Any(y => y.Category == x.Category && y.Type == x.Type))
.Select(x => new MySubject() {
Id = x.Id,
Category = x.Category,
Type = x.Type
}).ToList();
// If mySubjectsToAdd has any value then add it into student.MySubjects
if (mySubjectsToAdd.Any())
{
student.MySubjects.AddRange(mySubjectsToAdd);
}
student.MySubjects.ForEach(i => Console.WriteLine("{0}-{1}-{2}\t", i.Id, i.Category, i.Type));
// make an inner join based on mutual values to filter out wrong subjects.
var filteredList =
from mySubject in student.MySubjects
join subject in subjects
on new { mySubject.Category, mySubject.Type }
equals new { subject.Category, subject.Type }
select new MySubject { Id = mySubject.Id, Category = mySubject.Category, Type = mySubject.Type };
// make a left outer join to find absent subjects.
var absentList =
from subject in subjects
join mySubject in filteredList
on new { subject.Category, subject.Type }
equals new { mySubject.Category, mySubject.Type } into sm
from s in sm.DefaultIfEmpty()
where s == null
select new MySubject { Id = subject.Id, Category = subject.Category, Type = subject.Type };
student.MySubjects = filteredList.ToList();
student.MySubjects.AddRange(absentList.ToList());

From a one to many situation how do I get common items in Entity Framework

I just started with Entity Framework and I was having difficulty generating a query for the following situation.
I currently have two model classes Student and Sport. A student can play multiple sports. This is what my models look like
public class DbContext : DbContext
{
public DbContext(): base("name=DbContext")
{
}
public DbSet<Student> MyStudents { get; set; }
public DbSet<Sport> MySports { get; set; }
}
public class Student
{
public List<Sport> Actions { get; set; }
public string Name { get; set; }
}
public class Sport
{
public string SportName { get; set; }
}
My question is how do I get a list of all sports played by all the students? In short I am looking for common sports. So basically in the following case
Student A played Sports : Soccer , Tennis , Bowling
Student B played Sports : Soccer , Tennis ,
Student C played Sport : Tennis
Then only Tennis should be returned
Using the DB schema you've provided you can get the common sports checking sports of each student:
var sports = new[]
{
new Sport { SportName = "Tennis" },
new Sport { SportName = "Soccer" },
new Sport { SportName = "Bowling" }
};
var students = new[]
{
new Student
{
Name = "Student 1",
Actions = sports
},
new Student
{
Name = "Student 2",
Actions = new[] { sports[0], sports[1] }
},
new Student
{
Name = "Student 3",
Actions = new[] { sports[0] }
}
};
// Or
var sports = context.Sports;
var students = context.Students;
// In case students' sports are objects (as in this sample) you can use such a query:
var commonSports = sports.Where(sport =>
students.All(student => student.Actions.Contains(sport)));
// In case you're going to check the sports by name, this:
var commonSports = sports.Where(sport =>
students.All(student => student.Actions.Any(studSport =>
studSport.SportName == sport.SportName)));
Console.WriteLine($"Comon sports: {string.Join(",", commonSports.Select(i => i.SportName))}");
// To get only names of common sports:
var sportNames = commonSports.Select(i => i.SportName);
Console.Read();
If you use a relational database it would be easier and (as for me) more logical to implement many-to-many relationship as described here:
var context = new DbContext()
var unique = context.MyStudents.SelectMany(student => student.Actions.Select(sport => sport.SportName)).Distinct();
you just do this :
var commonSports = Context.Students.SelectMany(x=>x.Actions).GroupBy(x => x.SportName).Where(x=>x.Count()==items.Count(c=>c.Actions!=null)).Select(x=>x.Key).ToList();
I hope it been helpful .
To achieve this you might want to first set up some kind of model class, this isn't strictly necessary but might make things clearer for you:
public class StudentWithSports()
{
public string Name {get;set;}
public List<string> Sports {get;set;}
}
You can then populate your model from your context:
using(var context = new DbContext())
{
List<StudentWithSports> list = context
.Students
.Include(stu => stu.Actions)
.Select(stu => new StudenWithSports
{
Name = stu.Name,
Sports = stu.Actions.Select(act => act.SportName).ToList()
}).ToList();
}
If you don't want to create a model you could just do:
var list = context
.Students
.Include(stu => stu.Actions)
.Select(stu => new {
Name = stu.Name,
Sports = stu.Actions.Select(act => act.SportName).ToList()
}).ToList();
Which will give you a list of anonymous objects with the same properties.
The essence of my answer is the linq query, but I created a couple of classes to model your EF classes to show it works.
Student student1 = new Student
{
Name = "John",
Actions = new List<Sport>
{
new Sport { SportName = "Tennis" },
new Sport { SportName = "Soccer" },
new Sport { SportName = "Bowling" }
}
};
Student student2 = new Student
{
Name = "Mary",
Actions = new List<Sport>
{
new Sport { SportName = "Tennis" },
new Sport { SportName = "Soccer" }
}
};
Student student3 = new Student
{
Name = "Jane",
Actions = new List<Sport>
{
new Sport { SportName = "Tennis" }
}
};
IEnumerable<Student> students = new List<Student>
{
student1,
student2,
student3
};
var query = from s in students
select new
{
s.Name,
Sports = from sp in s.Actions
select sp.SportName
};
var result = query.ToList();
for (int i = 0; i < result.Count(); i++)
{
Console.Write(result[i].Name + " played sports: ");
foreach (var sport in result[i].Sports)
Console.Write(" " + sport);
Console.WriteLine();
}
Well your Db design isn't right because you have many to many relation between MyStudents and MySports tables. You have to add joint table between Students and Sports. You can call it StudentsSports
public class DbContext : DbContext
{
public DbContext(): base("name=DbContext")
{
}
public DbSet<Student> MyStudents { get; set; }
public DbSet<StudentsSport> StudentsSports { get; set; }
public DbSet<Sport> MySports { get; set; }
}
public class Student
{
public int ID { get; set; }
public List<StudentsSport> Actions { get; set; }
public string Name { get; set; }
}
public class Sport
{
public int ID { get; set; }
public string SportName { get; set; }
}
public class StudentsSport
{
public int ID { get; set; }
[ForeignKey(Student)]
public int StudentID { get; set; }
[ForeignKey(Sport)]
public int SportID { get; set; }
}
Then you can just do
var listOfActions = MyStudents.Select(s => s.Actions.Select(a => a.SportID));
var intersection = listOfActions
.Skip(1)
.Aggregate(
new HashSet<T>(listOfActions.First()),
(h, e) => { h.IntersectWith(e); return h; }
);
EDIT:
If you have students without sports then you will always get empty intersection list. If you don't want that then you will have to filter them
var listOfActions = MyStudents.Select(s => s.Actions.Select(a => a.SportID)).Where(c => c.Any());

Converting a list of options to a JavaScript arrary in C#

I need to get a JSON string that looks like the following:
{"1":[{"value":"1", "text":"Basketball"}, {"value":"2", "text":"Tennis"}, {"value":"3", "text":"Football"}],"3":[{"value":"4", "text":"futbol"}]}
The C# code responsible for building this looks like the following:
var sportsEntries = new Dictionary<byte, List<KeyValuePair<int, string>>>();
foreach (var department in Departments)
{
var deptOptions = SportList
.Where(x => x.DeptId == department.DeptId)
.ToDictionary(x => x.SportId, x => x.SportNameName).ToList();
sportsEntries .Add(department.DeptId, deptOptions);
}
var json = JsonConvert.SerializeObject(sportsEntries);
Unfortunately, this approach generates the wrong JSON. The JSON looks like this:
{"1":[{"Key":1,"Value":"Basketball"},{"Key":2,"Value":"Tennis"},{"Key":3,"Value":"Football"}],"3":[{"Key":4, "Value":"Futbol"}]}
I feel like I'm so close. Yet, I'm not sure how to update my C# code to make the resulting JSON look like the format I need. How do I update the C# to output the correct JSON?
Thank you!
You could use something like this:
var sportsEntries = new Dictionary<byte, List<object>();
foreach (var department in Departments)
{
var deptOptions = SportList
.Where(x => x.DeptId == department.DeptId)
.Select(x => new { value = x.SportId, text = x.SportNameName}).ToList();
sportsEntries .Add(department.DeptId, deptOptions);
}
var json = JsonConvert.SerializeObject(sportsEntries);
This solution replaces the initial KeyValuePair<int, string> with object and creates a list of anonymous objects, having the desired properties.
This works:
[TestFixture]
public class SoTest
{
[Test]
public void Test1()
{
var departments = new List<Department>
{
new Department
{
DeptId = 1
}
};
var sportList = new List<Sport>
{
new Sport
{
DeptId = 1,
SportId = 1,
SportName = "Basketball"
},
new Sport
{
DeptId = 1,
SportId = 2,
SportName = "Tennis"
}
};
var sportsEntries = new Dictionary<byte, List<Kvp>>();
foreach (var department in departments)
{
var deptOptions = sportList
.Where(x => x.DeptId == department.DeptId)
.Select(x => new Kvp { Value = x.SportId, Text = x.SportName }).ToList();
sportsEntries.Add(department.DeptId, deptOptions);
}
string json = JsonConvert.SerializeObject(sportsEntries);
Assert.IsNotNullOrEmpty(json);
Debug.Print(json);
}
}
public class Department
{
public byte DeptId { get; set; }
}
public class Sport
{
public byte DeptId { get; set; }
public int SportId { get; set; }
public string SportName { get; set; }
}
[DataContract]
public class Kvp
{
[DataMember(Name = "value")]
public int Value { get; set; }
[DataMember(Name = "text")]
public string Text { get; set; }
}

Can I put a conditional check on a string to see if it ends in "00" inside a LINQ expression?

I am trying to populate an Objective and ObjectiveDetail objects. Here are the classes I have:
partial class Objective
{
public Objective() {
this.ObjectiveDetails = new List<ObjectiveDetail>();
}
public int ObjectiveId { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public virtual ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
}
public partial class ObjectiveDetail
{
public int ObjectiveDetailId { get; set; }
public int ObjectiveId { get; set; }
public string Text { get; set; }
public virtual Objective Objective { get; set; }
}
I'm currently populating the only the Objective object from this call:
var objectiveData = GetContent.GetType5();
var objectives = objectiveData.Select(o => new Objective {
Name = o.Name,
Text = o.Text}
);
The data looks like this:
Name Text
0600 header 1
0601 detail abc
0602 detail def
0603 detail ghi
0700 header 2
0701 detail xyz
Is there a way I could modify my LINQ so that only the data where the name field contents end in "00" goes into the Objective object (as it does now) and when the data where the name field contents end in "01" then it creates a new ObjectiveDetail object with "detail abc" etc going into the text field.
This is a picture of what the end result should look like:
A collection of Objectives
new Objective { name = "header 1",
ObjectiveDetails = A collection of ObjectiveDetails
name = "detail abc"
name = "detail def" etc.
Sure you can do that, using [string.EndsWith] method like:1
.Where(r=> r.Name.EndsWith("00"))
Modify your query as:
var objectives = objectiveData
.Where(r => r.Name.EndsWith("00"))
.Select(o => new Objective {
Name = o.Name,
Text = o.Text}
);
It's somewhat unclear what you are asking, but you can put complex logic inside the Select() if you need to:
var objectives = objectiveData.Select(o =>
{
var result = new Objective
{
Name = o.Name,
Text = o.Text
};
if (o.Name != null && o.Name.EndsWith("01"))
{
result.ObjectiveDetails.Add
(
new ObjectiveDetail
{
ObjectiveDetailId = o.ObjectiveId,
Name = o.Name,
Text = o.Text,
Objective = result
}
);
}
return result;
});
(Note that I'm guessing at what you need; you will need to correct the logic to do what you really want.)
Looks like you want to do some sort of conditional mapping. I like Matthew Watson's answer, but it's a bit unclear why he's always creating an Objective instance every time. Here's some LINQ-less code which I believe is more readable, and maps the way I think you'd want:
public class Mapper
{
public List<Objective> Objectives = new List<Objective>();
public class Objective
{
public int ObjectiveId { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
public Objective()
{
ObjectiveDetails = new List<ObjectiveDetail>();
}
}
public class ObjectiveDetail
{
public int ObjectiveDetailId { get; set; }
public int ObjectiveId { get; set; }
public string Text { get; set; }
public virtual Objective Objective { get; set; }
}
public void Assign()
{
var objectiveData = new[] // Hard-coded test data. We don't know what the type of each item in this list is, so I use an anonymous type
{
new {Name = "0600", Text = "Header 06"},
new {Name = "0601", Text = "06 Detail 01"},
new {Name = "0602", Text = "06 Detail 02"},
new {Name = "0603", Text = "06 Detail 03"},
new {Name = "0700", Text = "Header 07"},
new {Name = "0701", Text = "07 Detail 01"},
new {Name = "0702", Text = "07 Detail 02"}
};
// Create Objectives first
var id = 1;
foreach (var item in objectiveData.Where(i => i.Name.EndsWith("00")))
{
Objectives.Add(new Objective { ObjectiveId = id, Name = item.Name, Text = item.Text });
id++;
}
// Create ObjectiveDetails
id = 1;
foreach (var item in objectiveData.Where(i => !i.Name.EndsWith("00")))
{
var itemLocal = item;
var matchingObjective = Objectives.FirstOrDefault(o => o.Name.StartsWith(itemLocal.Name.Substring(0, 2)));
var objectiveDetail = new ObjectiveDetail
{
ObjectiveDetailId = id,
Text = item.Text,
ObjectiveId = matchingObjective != null ? matchingObjective.ObjectiveId : 0,
Objective = matchingObjective
};
if (matchingObjective != null)
{
matchingObjective.ObjectiveDetails.Add(objectiveDetail);
}
id++;
}
// At the end of this method you should have a list of Objectives, each with their ObjectiveDetails children
}
}
Output:
Hope this helps.

Categories