I have a complex LINQ query like
var results = from obj1 in context.ProcessBases
join obj2 in context.InspectorArticles
on obj1.ID equals obj2.ProcessBaseID
join obj3 in context.InspectorSamples
on obj2.ID equals obj3.InspectorArticleID
where obj1.ID == _processBaseID
select new {obj1, obj2, obj3,};
Now this result set will have only ONE ProcessBases, each ProcessBase will have MULTIPLE InspectorArticles and each InspectorArticle will have MULTIPLE InspectorSamples. So when I loop through the result set, I want to loop through each InspectorArticle, and then loop through each InspectorSample that belongs to that InspectorArticle, something like:
foreach (InspectorArticle _iart in results.First().obj2)
{
...
foreach (InspectorSample _isamp in results.First().obj3)
{
...
}
}
But since I've called a .First() on my result set obviously I get this exception:
foreach statement cannot operate on variables of type x because x does
not contain a public definition for 'GetEnumerator'
So how can I loop through each instance of InspectorArticle, and then loop through the number of InspectorSamples for that article?
Thanks.
Since you are joining the records together, this statement is not correct:
Now this result set will have only ONE ProcessBases, each ProcessBase
will have MULTIPLE InspectorArticles and each InspectorArticle will
have MULTIPLE InspectorSamples.
What you will actually have after you execute your query is an IEnumerable where each object in the IEnumerable contains a reference to a ProcessBase, an InspectorArticle and an InspectorSample. For example, using the code below in LinqPad will yield an IEnumerable with the following contents:
Code:
void Main()
{
var processBases = new List<ProcessBase>();
var inspectorArticles = new List<InspectorArticle>();
var inspectorSamples = new List<InspectorSample>();
processBases.Add(new ProcessBase { ID = 1 });
processBases.Add(new ProcessBase { ID = 2 });
inspectorArticles.Add(new InspectorArticle { ID = 3, ProcessBaseID = 1 });
inspectorArticles.Add(new InspectorArticle { ID = 4, ProcessBaseID = 1 });
inspectorArticles.Add(new InspectorArticle { ID = 5, ProcessBaseID = 2 });
inspectorSamples.Add(new InspectorSample { ID = 6, InspectorArticleID = 3 });
inspectorSamples.Add(new InspectorSample { ID = 7, InspectorArticleID = 3 });
inspectorSamples.Add(new InspectorSample { ID = 8, InspectorArticleID = 3 });
inspectorSamples.Add(new InspectorSample { ID = 9, InspectorArticleID = 4 });
inspectorSamples.Add(new InspectorSample { ID = 10, InspectorArticleID = 5 });
inspectorSamples.Add(new InspectorSample { ID = 11, InspectorArticleID = 5 });
var processBaseID = 1;
var results = from obj1 in processBases
join obj2 in inspectorArticles on obj1.ID equals obj2.ProcessBaseID
join obj3 in inspectorSamples on obj2.ID equals obj3.InspectorArticleID
where obj1.ID == processBaseID
select new { obj1, obj2, obj3 };
Console.WriteLine(results);
}
public class ProcessBase
{
public int ID { get; set; }
}
public class InspectorArticle
{
public int ID { get; set; }
public int ProcessBaseID { get; set; }
}
public class InspectorSample
{
public int ID { get; set; }
public int InspectorArticleID { get; set; }
}
Results:
So, if you want to keep the Linq statement as is and loop through it with multiple foreach statements, you'll need to use something like the code below:
foreach(var article in results.GroupBy(g => g.obj2.ID))
{
Console.WriteLine("Article ID: #{0}", article.Key);
foreach(var sample in results
.Where(s => s.obj3.InspectorArticleID == article.Key)
.Select(s => s.obj3))
{
Console.WriteLine("\tSample ID: #{0}", sample.ID);
}
}
Using this code (and continuing the example from above) should get you the output:
Article ID: #3
Sample ID: #6
Sample ID: #7
Sample ID: #8
Article ID: #4
Sample ID: #9
The disadvantage of this approach is that you're having to enumerate the results list several times. If you know this list will always be small, then that's not such a big deal. If the list will be very large, then you might want to come up with a better way to return the data.
EDIT
To make the code more efficient, you could group by InspectorArticle.ID and then create a Dictionary keyed by the ID, and containing the original InspectorArticle and the associated InspectorSamples.
var articles = results.GroupBy(g => g.obj2.ID)
.ToDictionary(k => k.Key, v => new {
InspectorArticle = v.Select(s => s.obj2).First(),
InspectorSamples = v.Select(s => s.obj3) });
foreach(var article in articles.OrderBy(a => a.Key).Select(kv => kv.Value))
{
Console.WriteLine("Article ID: #{0}", article.InspectorArticle.ID);
foreach(var sample in article.InspectorSamples)
{
Console.WriteLine("\tSample ID: #{0}", sample.ID);
}
}
The code above will yield the same results as my first example, but for longer lists of articles and samples will be more efficient since it will only enumerate the entire list once when building the Dictionary.
Please note that I keyed the Dictionary off of the ID property because I didn't supply an IEqualityComparer to the GroupBy method. If you would rather key by the InspectorArticle object itself, you would need to make sure that two different InspectorArticle instances with the same ID are viewed as equal, which can be done if you create an IEqualityComparer and pass it into the GroupBy method.
Related
i'd like to figure out if it's possible (or, if it's already being done) to ensure the items from the first IEnumerable are kept - while duplicates from a union of another IEnumerable are discarded.
For example:
using System.Collections.Generic;
using System.Linq;
namespace MyApp.ExampleStuff
{
public class SomeDto
{
string name {get; set;}
int classId {get; set;}
int notComparedObject {get; set;}
}
public class test {
public void DoSomething()
{
IEnumerable<SomeDto> firstDto = new List<SomeDto>() { new SomeDto() {name = "Dave", classId = 1, notComparedObject = 12}};
IEnumerable<SomeDto> secondDto = new List<SomeDto>() { new SomeDto() {name = "Dave", classId = 1, notComparedObject = 16}, new SomeDto() {name = "Brad", classId = 1, notComparedObject = 77}};
var result = GetUnionedLists(firstDto, secondDto);
}
public ILookup<SomeDto> GetUnionedLists (IEnumerable<SomeDto> dtoA, IEnumerable<SomeDto> dtoB)
{
return dtoA.Union(dtoB, new SomeDtoComparer()).ToLookUp(x => x.classId);
}
}
public class SomeDtoComparer : IEqualityComparer<SomeDto>
{
public bool Equals(SomeDto SomeDtoA, SomeDto SomeDtoB)
{
if (SomeDtoA == null && SomeDtoB == null)
{
return true;
} else if (SomeDtoA == null || SomeDtoB == null)
{
return false;
}
return (SomeDtoA.Name == SomeDtoB.Name && SomeDtoA.classId == SomeDtoB.classId);
}
public int GetHashCode(SomeDto SomeDtoX)
{
int hashName = SomeDtoX.Name == null ? 0 : SomeDtoX.Name.GetHashCode();
int hashClassId = SomeDtoX.classId == null ? 0 : SomeDtoX.classId.GetHashCode();
return hashName ^ hashClassId;
}
}
}
If this is run - i would hope that the value of result in DoSomething() is a Lookup containing only the following someDto's under classId "1":
SomeDto() {name = "Dave", classId = 1, notComparedObject = 12}
SomeDto() {name = "Brad", classId = 1, notComparedObject = 77}
As you can see, if "Name" and "classId" are the same - the results are considered Equal, and i'd then like to keep the item from the original IEnumerable, and discard the "duplicate" - in this case that was:
SomeDto() {name = "Dave", id = 1, notComparedObject = 16}
If the result were to come out like this - it would be considered wrong (as the items from the second Enumerable were placed first in the result):
SomeDto() {name = "Brad", classId = 1, notComparedObject = 77}
SomeDto() {name = "Dave", classId = 1, notComparedObject = 12}
Enumerable.Union method already yields items in order that you've described. It's written in the docs that
When the object returned by this method is enumerated, Union enumerates first and second in that order and yields each element that has not already been yielded.
On the other side, Lookup type as well as IGrouping interface does not give any guarantees on elements order (looks like current implementation of ToLookup keeps the original order, but this could change). So if it really matters, you should add some additional logic — like using custom type instead of Lookup, adding custom property for index and ordering by it or, probably, using GroupBy, which does guarantees the order as it's stated in the docs.
The IGrouping objects are yielded in an order based on the order of the elements in source that produced the first key of each IGrouping. Elements in a grouping are yielded in the order that the elements that produced them appear in source.
I think you could do this using the FullJoin function available within the MoreLinq library (available on NuGet).
https://morelinq.github.io/3.0/ref/api/html/M_MoreLinq_MoreEnumerable_FullJoin__3_1.htm
Example:
public ILookup<SomeDto> GetUnionedLists (IEnumerable<SomeDto> dtoA, IEnumerable<SomeDto> dtoB)
{
return dtoA
.FullJoin(dtoB,
e => e,
first => first,
second => second,
(first, second) => first,
new SomeDtoComparer())
.ToLookUp(x => x.classId);
}
First question :)
I have a List<Materiau> (where Materiau implements IComparable<Materiau>), and I would like to remove all duplicates and add them together
(if two Materiau is the same (using the comparator), merge it to the first and remove the second from the list)
A Materiau contains an ID and a quantity, when I merge two Materiau using += or +, it keeps the same ID, and the quantity is added
I cannot control the input of the list.
I would like something like this:
List<Materiau> materiaux = getList().mergeDuplicates();
Thank you for your time :)
Check out Linq! Specifically the GroupBy method.
I don't know how familiar you are with sql, but Linq lets you query collections similarly to how sql works.
It's a bit in depth to explain of you are totally unfamiliar, but Code Project has a wonderful example
To sum it up:
Imagine we have this
List<Product> prodList = new List<Product>
{
new Product
{
ID = 1,
Quantity = 1
},
new Product
{
ID = 2,
Quantity = 2
},
new Product
{
ID = 3,
Quantity = 7
},
new Product
{
ID = 4,
Quantity = 3
}
};
and we wanted to group all the duplicate products, and sum their quantities.
We can do this:
var groupedProducts = prodList.GroupBy(item => item.ID)
and then select the values out of the grouping, with the aggregates as needed
var results = groupedProducts.Select( i => new Product
{
ID = i.Key, // this is what we Grouped By above
Quantity = i.Sum(prod => prod.Quantity) // we want to sum up all the quantities in this grouping
});
and boom! we have a list of aggregated products
Lets say you have a class
class Foo
{
public int Id { get; set; }
public int Value { get; set; }
}
and a bunch of them inside a list
var foocollection = new List<Foo> {
new Foo { Id = 1, Value = 1, },
new Foo { Id = 2, Value = 1, },
new Foo { Id = 2, Value = 1, },
};
then you can group them and build the aggregate on each group
var foogrouped = foocollection
.GroupBy( f => f.Id )
.Select( g => new Foo { Id = g.Key, Value = g.Aggregate( 0, ( a, f ) => a + f.Value ) } )
.ToList();
List<Materiau> distinctList = getList().Distinct(EqualityComparer<Materiau>.Default).ToList();
I have an array for which I want to group the items based on a property. I tried the below code, but it is not grouping correctyly. MyArray is the array and Id is the property on which I want to do the grouping.
var docGroup = (from x in MyArray
group x by x.Id).Select(grp => new
{
Id = grp.Key,
Results = grp.ToList(),
})
.Results
.ToList());
To keep it simple if I just make it
var docGroup = from x in MyArray group x by x.Id;
where Id is a string "123" in the array and MyArray[2] has both the same Id. When I check the docGroup it has two entries and both have the 123 key instead of just one entry with the 123 key.
Here's a very simple example:
class Program
{
static void Main(string[] args)
{
Test[] tArray = new Test[3];
Test t = new Test() { Id = "123", Val="First" };
Test t1 = new Test() { Id = "123", Val="Second" };
Test t2 = new Test() { Id = "1234", Val="Third" };
tArray[0] = t;
tArray[1] = t1;
tArray[2] = t2;
var g = from x in tArray group x by x.Id;
}
}
class Test
{
public string Id { get; set; }
public string Val { get; set; }
}
Now if I look at g it has count 2 of which one is the Id 123 and the second is the Id 1234. I am not sure what is going wrong with my array. So this seems to work, but I am not sure what is going on with my array. I'll do some research on it.
Sorry guys, I found the issue. The Id was in a value property in MyArray which I was not using and so it was not grouping correctly. Thanks for the help everyone.
Everything works as expected.
GroupBy produces an enumerable of IGrouping. Since you have two distinct keys ("123" and "1234") you will get an enumerable of two elements. These grouping have a uniqe key and they're by themself enumerables.
So
g.Where(x => x.Key == "123").ToList();
will contain two elements (First, Second) and
g.Where(x => x.Key == "1233").ToList();
will contain one element (Third).
I'm trying to get a count of parents with no children plus parents children. As I write this I realize it is better explained with code.. So, here it goes:
With these example types:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public string Description { get; set; }
}
And this data:
var customers = new List<Customer>
{
new Customer
{
Id = 2,
Name = "Jane Doe"
},
new Customer
{
Id = 1,
Name = "John Doe",
Orders = new List<Order>
{
new Order { Id = 342, Description = "Ordered a ball" },
new Order { Id = 345, Description = "Ordered a bat" }
}
}
};
// I'm trying to get a count of customer orders added with customers with no orders
// In the above data, I would expect a count of 3 as detailed below
//
// CId Name OId
// ---- -------- ----
// 2 Jane Doe
// 1 John Doe 342
// 1 John Doe 345
int customerAndOrdersCount = {linq call here}; // equals 3
I am trying to get a count of 3 back.
Thank you in advance for your help.
-Jessy Houle
ADDED AFTER:
I was truly impressed with all the great (and quick) answers. For others coming to this question, looking for a few options, here is a Unit Test with a few of the working examples from below.
[TestMethod]
public void TestSolutions()
{
var customers = GetCustomers(); // data from above
var count1 = customers.Select(customer => customer.Orders).Sum(orders => (orders != null) ? orders.Count() : 1);
var count2 = (from c in customers from o in (c.Orders ?? Enumerable.Empty<Order>() ).DefaultIfEmpty() select c).Count();
var count3 = customers.Sum(c => c.Orders == null ? 1 : c.Orders.Count());
var count4 = customers.Sum(c => c.Orders==null ? 1 : Math.Max(1, c.Orders.Count()));
Assert.AreEqual(3, count1);
Assert.AreEqual(3, count2);
Assert.AreEqual(3, count3);
Assert.AreEqual(3, count4);
}
Again, thank you all for your help!
How about
int customerAndOrdersCount = customers.Sum(c => c.Orders==null ? 1 : Math.Max(1, c.Orders.Count()));
If you would initialize that Order property with an empty list instead of a null, you could do:
int count =
(
from c in customers
from o in c.Orders.DefaultIfEmpty()
select c
).Count();
If you decide to keep the uninitialized property around, then instead do:
int count =
(
from c in customers
from o in (c.Orders ?? Enumerable.Empty<Order>() ).DefaultIfEmpty()
select c
).Count();
customers
.Select(customer => customer.Order)
.Sum(orders => (orders != null) ? orders.Count() : 1)
This works if you want to count "no orders" as 1 and count the orders otherwise:
int customerOrders = customers.Sum(c => c.Orders == null ? 1 : c.Orders.Count());
By the way, the question is very exemplary.
You probabbly searching for something like this:
customers.GroupBy(customer=>customer). //group by object iyself
Select(c=> //select
new
{
ID = c.Key.Id,
Name = c.Key.Name,
Count = (c.Key.Orders!=null)? c.Key.Orders.Count():0
}
);
var orderFreeCustomers = customers.Where(c=>c.Orders== null || c.Orders.Any()==false);
var totalOrders = customers.Where (c => c.Orders !=null).
Aggregate (0,(v,e)=>(v+e.Orders.Count) );
Result is the sum of those two values
say I have this data
1 757f27a2-e997-44f8-b2c2-6c0fd6ee2c2f 2 3
2 757f27a2-e997-44f8-b2c2-6c0fd6ee2c2f 3 1
3 757f27a2-e997-44f8-b2c2-6c0fd6ee2c2f 2 2
column 1 // pk
column 2 // userId
column 3 // courseId
column 4 // permissionId
I have this class
class CoursePermissions
{
public string Prefix { get; set; }
public bool OwnerPermission { get; set; } // permissionId 1
public bool AddPermission { get; set; } // permissionId 2
public bool EditPermission { get; set; } // permissionId 3
}
I want to group all the 3 rows by courseId(or Prefix) and then take that information and make a class out Of it
So the end result would be
List<CoursePermissions> permissions = new List<CoursePermissions>();
CoursePermissions a = new CoursePermissions
{
Prefix = "comp101";
OwnerPermission = false,
AddPermission = true,
EditPermission = true
};
CoursePermissions b = new CoursePermissions
{
Prefix = "comp102";
OwnerPermission = true,
AddPermission = false,
EditPermission = false
};
permissions.Add(a);
permissions.Add(b);
So the above is how the object would look if I took all the row data from the db and manually made it the way I wanted it too look. Of course I need to do it somehow as a query.
In my example I have 2 students. They both belong to the same course. Student 1has edit and Add permission for Comp101 but only owner permissions for comp102.
I want to get all the rows back for Comp101 and put it into CoursePermissions. Then I want to get all the rows back for Comp102 and put it into CoursePermissions. Then store all these in a collection and use them.
The only thing I can do is something like this
var list = session.Query<PermissionLevel>().Where(u => u.Student.StudentId == studentId).ToList();
IEnumerable<IGrouping<string, PermissionLevel>> test = list.GroupBy(x => x.Course.Prefix);
foreach (var t in test)
{
CoursePermissions c = new CoursePermissions();
foreach (var permissionLevel in t)
{
if (permissionLevel.PermissionLevelId == 1)
{
c.OwnerPermission = true;
}
}
}
It would nice if I could get rid of the nest for each loop and do it all when the data comes from the query.
Here's an approach that I think is quite functional.
First set up a dictionary of actions that will set the appropriate course permission given a permission level id.
var setPermission = new Dictionary<int, Action<CoursePermissions>>()
{
{ 1, cps => cps.OwnerPermission = true },
{ 2, cps => cps.AddPermission = true },
{ 3, cps => cps.EditPermission = true },
};
Now create a function that will turn the course prefix and a list of permission level ids into a new CoursePermissions object.
Func<string, IEnumerable<int>, CoursePermissions>
buildCoursePermission = (prefix, permissionLevelIds) =>
{
var cps = new CoursePermissions() { Prefix = prefix };
foreach (var permissionLevelId in permissionLevelIds)
{
setPermission[permissionLevelId](cps);
}
return cps;
};
Now all you have left is a simple query that turns your list of permission levels into a list of course permissions.
var coursePermissionsList =
(from pl in list
group pl.PermissionLevelId by pl.Course.Prefix into gcpls
select buildCoursePermission(gcpls.Key, gcpls)).ToList();
How does that work for you?