LinQ nested lists and nested selects - c#

Consider these two tables:
ClassID Name
1 C1
2 C2
ClassID List<CourseSession>
1 [Object that has value "A"], [Object that has value "B"]
2 [Object that has value "B"], [Object that has value "C"]
When I join these two tables in Linq, I get:
ID Name List
1 C1 [A, B]
2 C2 [A, B]
Wheras I need to expand them:
ID Name List
1 C1 A
1 C1 B
2 C2 A
2 C2 B
Linq code:
var classes = from row in t.AsEnumerable()
select new
{
ClassID = row.Field<Guid>("ClassID"),
ClassName = row.Field<string>("Name"),
};
var classCourses = from row in classes.AsEnumerable()
select new
{
ID = row.ID,
CourseSessionList = GetAllCoursesByID(row.ID).AsEnumerable()
};
//Attempt to join
var expandedClassCourse = from classRow in classes
join ccRow in classCourses
on classRow.ID equals ccRow.ID
into filteredExpandedClasses
select filteredExpandedClasses;
I'm not sure how to achieve this. Any ideas?

Something like (not sure what your model looks like):
context.CouseSessions.Where(cs => /* condition goes here */)
.Select(cs =>
new
{
Name = cs.Name,
Class = cs.Class.Name
});
or
context.Classes.Where(c => /* condition goes here */)
.SelectMany(c => c.Courses)
.Select(cs =>
new
{
Name = cs.Name,
Class = cs.Class.Name
});

I created two models based on assumption. I hope this helps.
class Info
{
public int Id { get; set; }
public string Name { get; set; }
public List<string> List { get; set; }
}
class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public string s { get; set; }
}
static void Main(string[] args)
{
var infos = new List<Info> { new Info { Id = 1, Name = "c1", List = new List<string> { "A", "B" } }, new Info { Id = 2, Name = "c2", List = new List<string> { "A", "B" } } };
var myClasses = new List<MyClass>();
foreach (var info in infos)
{
myClasses.AddRange(info.List.Select(a => new MyClass { Id = info.Id, Name = info.Name, s = a }));
}
}

(from c in classList
join s in sessionList on c.ClassID equals s.ClassID
select new
{
ID = c.ClassID,
Name = c.Name,
SessionList = s.SessionList
})
.SelectMany(e => e.SessionList.Select(s => new
{
ID = e.ClassID,
Name = e.Name,
Session = s
}))

Related

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));
}

How to query and sort a complex type consisting of IEnumerable<T> using LINQ?

Trying to figure out how to query an IEnumerable<T> using LINQ. The following simple example without IEnumerable works fine:
class Category
{
public string Title { get; set; }
public NameValue SubCategory { get; set; }
}
class NameValue
{
public string Name { get; set; }
public string Value { get; set; }
}
private static void testLinq()
{
Category[] categories = {
new Category { Title ="Abc", SubCategory = new NameValue { Name = "A", Value = "5"} },
new Category { Title ="Xyz", SubCategory = new NameValue { Name = "B", Value = "10" } }
};
IEnumerable<Category> q = categories.OrderBy(c => c.Title).ThenBy(c => c.SubCategory.Name);
foreach (Category c in q)
{
Console.WriteLine("{0} - {1}", c.Title, c.SubCategory.Name);
}
}
When I change the signature to have an IENumerable<NameValue> instead then I cannot access c.SubCategory.Name:
class Category
{
public string Title { get; set; }
public IEnumerable<NameValue> SubCategory { get; set; }
}
// For example, below does not compile:
IEnumerable<Category> q = categories.OrderBy(c => c.Title).ThenBy(c => c.SubCategory.Name);
// Also, this initialization of course won't work either
Category[] categories = {
new Category { Title ="Abc", SubCategory = new NameValue { Name = "A", Value = "5"} },
new Category { Title ="Xyz", SubCategory = new NameValue { Name = "B", Value = "10" } }
};
The error is:
IEnumerable' does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type 'IEnumerable' could be found (are you missing a using directive or an assembly reference?)
Do I need to do a cast of some sort?
Update:
Output should be something like:
Abc (category)
A (sub)
B (sub)
C (...)
Xyz
B
K
M
Xyz2
A
Q
Z
In SQL I would do like something like this:
SELECT c.Title, s.Name, s.Value FROM Categories c
INNER JOIN SubCategory s ON
c.CategoryID = s.CategoryID
ORDER BY c.Title, s.Name -- sorting first on Category.Title, then on SubCategory.Name
Your SubCategory will be a collection so you cannot do it using ThenBy. You need to order the Category(s) and then order their SubCategory(s) like this. Note I added two SubCategory(s) to the first Category but their order is not correct. After we order them, then they will be correct:
Category[] categories = {
new Category { Title ="Abc", SubCategory = new List<NameValue>
{ new NameValue { Name = "B", Value = "5"},
new NameValue { Name = "A", Value = "5"} } },
new Category { Title ="Xyz", SubCategory = new List<NameValue>
{ new NameValue { Name = "A", Value = "10" } } }};
// First order by categories
var cats = categories.OrderBy(c => c.Title)
// Then create a new category and order by sub categories
.Select(x => new Category { Title = x.Title,
SubCategory = x.SubCategory.OrderBy(y => y.Name) });
If you can get away with only sorting the children when you need to use them, sorting by the parent and then sorting the children upon use like this would be fairly efficient:
public void DisplayA(A value)
{
Console.WriteLine(value.Name);
foreach (var child in value.Children.OrderBy(c => c.Name))
{
Console.WriteLine(string.Format("- {0}", child.Name));
}
}
Or if you want to avoid that, you could add a sorted property to the class. Since it's Linq, it will only be evaluated when you iterate through the list.
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
public IEnumerable<B> SortedChildren { get { return Children.OrderBy(ca => ca.Name); } }
}
public class B
{
public string Name { get; set; }
}
If they don't work for you, you could try these, but they won't be so efficient since you're creating new objects.
// This will flatten it into a single object, sorted by one field and the the other. Since this is Linq, it will create these new flattened objects each time you iterate through the IEnumerable.
public IEnumerable<FlattenedA> GetSortedFlattened(IEnumerable<A> collection)
{
var flattened = collection.SelectMany(a => a.Children.Select(ca => new FlattenedA() { Name = a.Name, SubName = ca.Name }));
var sorted = flattened.OrderBy(f => f.Name).ThenBy(f => f.SubName);
return sorted;
}
// This will return objects of A, where the child enumerable has been replaced with an OrderBy. Again this will return new objects each time you iterate through. Only when you iterate through the children will they be sorted.
public IEnumerable<A> GetSortedNonFlattened(IEnumerable<A> collection)
{
var withSortedChildren = collection.Select(a => new A() { Name = a.Name, Children = a.Children.OrderBy(ca => ca.Name) });
var sorted = withSortedChildren.OrderBy(a => a.Name);
return sorted;
}
public class FlattenedA
{
public string Name { get;set }
public string SubName { get; set; }
}
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
}
when you are setting it as IEnumerable you can't do this
SubCategory = new NameValue { Name = "A", Value = "5"}
you should use some implementation of IEnumerable,
like List<>
so it should be something like this
SubCategory = new List<NameValue>{new NameValue { Name = "A", Value = "5"}, addmore here};
and for your order linq, i would do this,
var OrderedCategories = categories.select(g =>
new Category{ Name = g.Name, subcategories = g.subcategories.orderby(h => h.Name) });
That's because your SubCategory now is no longer a simple single instance of NameValue, but rather an enumeration of those. So now you need to specify how to .ThenBy over a collection of .Names.

C# select values of a property from a list inside a list

I have following objects
class A
{
public List<B> listB { get; set; }
}
class B
{
public int id { get; set; }
}
and in my application I have a scenario like below..
public void main()
{
var lstA = new List<A>();
var lstA = new List<A>();
var a1 = new A();
a1.listB = new List<B>
{
new B() { id = 1 },
new B() { id = 2 }
};
lstA.Add(a1);
a1 = new A();
a1.listB = new List<B>
{
new B() { id = 3 },
new B() { id = 4 }
};
lstA.Add(a1);
}
And I need to select all id's of B objects from lstA
Here is what I've tried so far
var ids = lst.Select(x=>x.listB.Select(y=>y.id)).ToList();
But It gives me a compilation error.
How can I do this?
You have to use SelectMany which flattens the lists:
var ids = lst.SelectMany(x => x.listB.Select(y => y.id)).ToList();
You are almost there, use SelectMany
var ids = lst.SelectMany(x=>x.listB.Select(y=>y.id)).ToList();
Check your Working Code
Here's what i did and it works perfectly
All i did was make the classes public and when you initialise List<B>, you add new List<B> because even though intellisense doesn't show you any error, when you run the application, you get object not referenced error
class Program
{
static void Main(string[] args)
{
var lstA = new List<A>();
var a1 = new A()
{
listB = new List<B>()
{
new B
{
id = 3
},
new B
{
id = 5
}
}
};
var a2 = new A()
{
listB = new List<B>()
{
new B
{
id = 1
},
new B
{
id = 8
}
}
};
lstA.Add(a1);
lstA.Add(a2);
var ids = lstA.SelectMany(r => r.listB.Select(x => x.id));
foreach (var id in ids)
{
Console.WriteLine(id);
}
Console.ReadKey();
}
}
public class A
{
public List<B> listB { get; set; }
}
public class B
{
public int id { get; set; }
}
try this to ignore duplicate id
var ids = lstA.SelectMany(x => x.listB.Select(y => y.id)).Distinct().ToList();

.Net List ForEach Item

I have 2 objects:
public class ClassA
{
public int Id
public string name;
public ClassB myObjectB;
}
public class ClassB
{
public int Id
public string name
}
Having 2 Lists for <ClassA> <ClassB>
Some items from List1 match by Id with an item on List2... I want to set the objectB foreach item...
foreach(ClassA item in List1)
{
ClassB obj = (from b in List2 where b.Id == item.Id select b).SingleOrDefault()
if(obj != null)
{
item.myObjectB = obj;
////////break; <- ignore this
}
}
This solution works for me, but I'm just wondering if there is a better way to do this, instead of Foreach
Thanks everyone for your help!!!
I think that a foreach, in this case, is actually the appropriate approach, as you're mutating your list. You could potentially simplify your code a bit, however:
foreach(ClassA item in List1)
{
item.myObjectB = List2.FirstOrDefault(b => b.Id == item.Id);
}
This will set the item every time, though it will be set to null if there is no match. If you already have items in myObjectB and setting them to null is inappropriate, you could use:
foreach(ClassA item in List1)
{
item.myObjectB = List2.FirstOrDefault(b => b.Id == item.Id) ?? item.myObjectB;
}
Expanding on Reed's answer.. You can actually do this in a one-liner, because a list has a ForEach method.
List1.ForEach(item => item.myObjectB = List2.FirstOrDefault(b => b.Id == item.Id) ?? item.myObjectB);
List<ClassA> list1 = new List<ClassA>();
List<ClassB> list2 = new List<ClassB>();
list1.Add(new ClassA { Id = 2, name = "a2" });
list1.Add(new ClassA { Id = 3, name = "a3" });
list1.Add(new ClassA { Id = 4, name = "a4" });
list1.Add(new ClassA { Id = 5, name = "a5" });
list2.Add(new ClassB { Id = 1, name = "b1" });
list2.Add(new ClassB { Id = 2, name = "b2" });
list2.Add(new ClassB { Id = 4, name = "b4" });
list2.Add(new ClassB { Id = 5, name = "b5" });
// Goal is to set ClassA::myObjectB from List1 to
// matching instance (if any) of ClassB from List2
var query =
from a in list1
from b in list2
where a.Id == b.Id
select Tuple.Create(a, b);
foreach (var element in query)
element.Item1.myObjectB = element.Item2;
Update:
Or if you really don't want a for loop, I just realized you can use the fact that assignments return a value and at the same time make an entry in an obfuscated code contest :)
(from a in list1
from b in list2
where a.Id == b.Id
select a.myObjectB = b).ToList();
Update2:
I just thought of an alternate approach - depending on your scenario, a lazy mechanism might work for you?
public class ClassA
{
public int Id
public string name;
private ClassB myObjectB;
public ClassB MyObjectB {
get { return myObjectB ?? (myObjectB = list2.FirstOrDefault(x => this.Id == x.Id)); }
}
}
With classes defined as follows:
class ClassA {
public int Id { get; private set; }
public string name { get; private set; }
public ClassB myObjectB { get; set; }
public ClassA(int pId, string pName) {
Id = pId;
name = pName;
}
}
class ClassB {
public int Id { get; private set; }
public string name { get; private set; }
public ClassB(int pId, string pName) {
Id = pId;
name = pName;
}
}
You can do the following using the LINQ Join method:
var listA = new List<ClassA> {
new ClassA(1, "OneA"),
new ClassA(2, "TwoA"),
new ClassA(3, "ThreeA")
};
var listB = new List<ClassB> {
new ClassB(1, "OneB"),
new ClassB(2, "TwoB"),
new ClassB(4, "FourB")
};
listA
.Join(
listB,
itemA => itemA.Id,
itemB => itemB.Id,
(itemA, itemB) => new { ItemA = itemA, ItemB = itemB }
).ForEach(pair => pair.ItemA.myObjectB = pair.ItemB);
listA.ForEach(itemA => Console.WriteLine(
"{0} maps to {1}",
itemA == null
? "null"
: itemA.name,
(itemA == null || itemA.myObjectB == null)
? "null"
: itemA.myObjectB.name
));
Output is:
OneA maps to OneB
TwoA maps to TwoB
ThreeA maps to null

Group by with multiple columns using lambda

How can I group by with multiple columns using lambda?
I saw examples of how to do it using linq to entities, but I am looking for lambda form.
var query = source.GroupBy(x => new { x.Column1, x.Column2 });
I came up with a mix of defining a class like David's answer, but not requiring a Where class to go with it. It looks something like:
var resultsGroupings = resultsRecords.GroupBy(r => new { r.IdObj1, r.IdObj2, r.IdObj3})
.Select(r => new ResultGrouping {
IdObj1= r.Key.IdObj1,
IdObj2= r.Key.IdObj2,
IdObj3= r.Key.IdObj3,
Results = r.ToArray(),
Count = r.Count()
});
private class ResultGrouping
{
public short IdObj1{ get; set; }
public short IdObj2{ get; set; }
public int IdObj3{ get; set; }
public ResultCsvImport[] Results { get; set; }
public int Count { get; set; }
}
Where resultRecords is my initial list I'm grouping, and its a List<ResultCsvImport>. Note that the idea here to is that, I'm grouping by 3 columns, IdObj1 and IdObj2 and IdObj3
if your table is like this
rowId col1 col2 col3 col4
1 a e 12 2
2 b f 42 5
3 a e 32 2
4 b f 44 5
var grouped = myTable.AsEnumerable().GroupBy(r=> new {pp1 = r.Field<int>("col1"), pp2 = r.Field<int>("col2")});
Further to aduchis answer above - if you then need to filter based on those group by keys, you can define a class to wrap the many keys.
return customers.GroupBy(a => new CustomerGroupingKey(a.Country, a.Gender))
.Where(a => a.Key.Country == "Ireland" && a.Key.Gender == "M")
.SelectMany(a => a)
.ToList();
Where CustomerGroupingKey takes the group keys:
private class CustomerGroupingKey
{
public CustomerGroupingKey(string country, string gender)
{
Country = country;
Gender = gender;
}
public string Country { get; }
public string Gender { get; }
}
class Element
{
public string Company;
public string TypeOfInvestment;
public decimal Worth;
}
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>()
{
new Element { Company = "JPMORGAN CHASE",TypeOfInvestment = "Stocks", Worth = 96983 },
new Element { Company = "AMER TOWER CORP",TypeOfInvestment = "Securities", Worth = 17141 },
new Element { Company = "ORACLE CORP",TypeOfInvestment = "Assets", Worth = 59372 },
new Element { Company = "PEPSICO INC",TypeOfInvestment = "Assets", Worth = 26516 },
new Element { Company = "PROCTER & GAMBL",TypeOfInvestment = "Stocks", Worth = 387050 },
new Element { Company = "QUASLCOMM INC",TypeOfInvestment = "Bonds", Worth = 196811 },
new Element { Company = "UTD TECHS CORP",TypeOfInvestment = "Bonds", Worth = 257429 },
new Element { Company = "WELLS FARGO-NEW",TypeOfInvestment = "Bank Account", Worth = 106600 },
new Element { Company = "FEDEX CORP",TypeOfInvestment = "Stocks", Worth = 103955 },
new Element { Company = "CVS CAREMARK CP",TypeOfInvestment = "Securities", Worth = 171048 },
};
//Group by on multiple column in LINQ (Query Method)
var query = from e in elements
group e by new{e.TypeOfInvestment,e.Company} into eg
select new {eg.Key.TypeOfInvestment, eg.Key.Company, Points = eg.Sum(rl => rl.Worth)};
foreach (var item in query)
{
Console.WriteLine(item.TypeOfInvestment.PadRight(20) + " " + item.Points.ToString());
}
//Group by on multiple column in LINQ (Lambda Method)
var CompanyDetails =elements.GroupBy(s => new { s.Company, s.TypeOfInvestment})
.Select(g =>
new
{
company = g.Key.Company,
TypeOfInvestment = g.Key.TypeOfInvestment,
Balance = g.Sum(x => Math.Round(Convert.ToDecimal(x.Worth), 2)),
}
);
foreach (var item in CompanyDetails)
{
Console.WriteLine(item.TypeOfInvestment.PadRight(20) + " " + item.Balance.ToString());
}
Console.ReadLine();
}
}

Categories

Resources