I'm having a hard time with this approach since I am new to Entity Framework. I actually don't know if there is something special related to EF or if the limitation is on me.
I would like to group some records from my database and after that, I'd like to iterate over the groups, then iterate over the elements on each group comparing it with all the other elements in the same group.
I have created two simple classes to illustrate the scenario:
public class MyContext : DbContext
{
public DbSet<MyClass> MyClass { get; set; }
}
And:
public class MyClass
{
public int Id { get; set; }
public int Value { get; set; }
}
What I have so far with a context injected is:
this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.ForEach(grp =>
{
// compare each item with all the other
// items in the current group
});
But I don't know how to iterate over the items and then compare with the others in the same group.
With the following code, the quesiton becomes what type is grp?
this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.ForEach(grp =>
{
// compare each item with all the other
// items in the current group
});
Well the grp variable is of type IGrouping<TKey, TElement>. That type derives from IEnumerable<TElement> so each grp is a list of TElement so you can foreach or do whatever you want to all the items in the grp.
DotNetFiddle Example.
Your variable grp is an IGrouping<int, MyClass>. You can treat it as an IEnumerable<MyClass>. For instance, you can get the item with the biggest Id like this:
this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.ForEach(grp =>
{
MyClass itemWithMaxId = grp.FirstOrDefault();
foreach (MyClass item in grp)
{
if (item.Id > itemWithMaxId.Id)
{
itemWithMaxId = item;
}
}
});
Note, however, that the ForEach method does not return anything, it only performs the specified action on each element of the list. If you want to get something, for instance the item with the biggest Id of each group, I suggest you to use the Select method provided by Linq, like in this example:
var itemsWithMaxIdByGroup = this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.Select(grp =>
{
MyClass itemWithMaxId = grp.First();
foreach (MyClass item in grp.Skip(1))
{
if (item.Id > itemWithMaxId.Id)
{
itemWithMaxId = item;
}
}
return itemWithMaxId;
});
Related
In my data structures I have the following classes:
public partial class Item
{
// stuff
public int QuoteId { get; set; }
public virtual ItemType ItemType { get; set; }
}
public partial class ItemType
{
//stuff
public virtual ICollection<Item> Items { get; set; }
}
What I want to do is get a list of all the ItemTypes, each of which has its Items collection populated according to a QuoteId.
So, for example if there are three item types, only two of which have items with a quote Id of 50:
ItemType1
Item.QuoteId == 50
ItemType2
ItemType3
Item.QuoteId == 50
I've managed to get something close with this query:
r.ItemTypes.Select(x => x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId));
But what this gives you (as you might expect, since I'm Selecting on Item) is an IEnumerable<IEnumerable<Item>>. This has the structure that I'm after but doesn't have the ItemType data.
I realise this is a dumb question, but I'm frustrated by my inability to get the answer.
r.ItemTypes.Where(x => x.Items.Any(i => i.QuoteId == CurrentQuote.QuoteId));
If you need to get all ItemTypes and only specific Items for every, you can do this:
r.ItemTypes.Select(x => new
{
x,
FilteredItems = x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId)
});
After that you need to assign x.Items to FilteredItems for every ItemType
You have to select the Item.ItemType property if you want the all ItemTypes of a given QuoteId. You also have to use SelectMany to flatten the "nested" collections:
IEnumerable<ItemType> types = r.ItemTypes
.SelectMany(x => x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId)
.Select(i => i.ItemType));
If you are not interested in the nested ItemType(don't know the logic) you can use Backs' approach:
IEnumerable<ItemType> types = r.ItemTypes
.Where(x => x.Items.Any(i => i.QuoteId == CurrentQuote.QuoteId));
var result = from itemTypes in r.ItemTypes
where itemTypes.QuoteId equals CurrentQuote.QuoteId
select new {itemType = itemTypes}
I am using c# framework 3.5 ..
my class here
public class KonumBilgisi
{
public string Enlem { get; set; }
public string Boylam { get; set; }
public string KonumAdi { get; set; }
public DateTime Tarih { get; set; }
public byte SucTuruId { get; set; }
}
I have a list
List konumlar;
well, I want to get items that equal their enlem and boylam variables each other..
As you see on the photo below
I want to compate enlem and boylam and if its the equal I want to get them to different list..
I can do that with a loop but want to use LINQ but i couldnt do that. I used groupby but it doesnt wrong..
var distinctList = konumlar.GroupBy(x => x.Enlem)
.Select(g => g.First())
.ToList().GroupBy(s=>s.Boylam).Select(g => g.First())
.ToList();
EDIT
Actually I couldnt explain my quesion well..
maybe distinct is not right word.. I want to seperate items which are equals each other..
such as:
I will take pendik items in one list
and others will be in konumlar but pendik items will be removed from konumlar list
EDIT 2
Okay I want to seperate the list like that
You are almost there - rather than using two separate GroupBy calls, use a single one, with a two-part key:
var distinctList = konumlar
.GroupBy(s => new {s.Enlem, s.Boylam})
.Select(g => g.First())
.ToList();
EDIT : To get all items except the ones with duplicates, modify the query as follows:
var noPendiks = konumlar
.GroupBy(s => new {s.Enlem, s.Boylam})
.Where(g => g.Count() == 1)
.Select(g => g.Single()) // You know there's only one
.ToList();
The above will give you all items except the "pendik" ones. To get only the "pendik"s, use the query below:
var pendiks = konumlar
.GroupBy(s => new {s.Enlem, s.Boylam})
.Where(g => g.Count() > 1)
.SelectMany(g => g)
.ToList();
You can use the Distinct() Linq function, however, that does only work identical items. In case you want a DistinctBy() you can create a LinqExtensions class with a DistinctBy() method.
Here's one which I use quite common:
/// <summary>
/// Provides common extension methods on LINQ members.
/// </summary>
public static class LinqExtensions
{
#region Members
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
#endregion Members
}
Use it as:
var distinctList = konumlar.DistinctBy(x => x.property && x.property2 && ...);
Kind regards
I assume this is what you want:
List<KonumBilgisi> distinctList = konumlar
.GroupBy(k => new { k.Enlem, k.Boylam })
.SelectMany(x => x.GroupBy(k => k.Boylam).First())
.ToList();
It select unique items according those two properties, then it select unique items according Boylam.
Below is my class.
public class MyGroceryListItems
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
}
Now I am fetching from database the values for this class and putting those classes in IList<MyGroceryListItems>. However, there are some duplicate items with same id but different quantity. How can I fetch only distinct entries by id?
I tried list.distinct but that didn't work because not all the entries are same in the record having same product id.
IEnumerable<MyGroceryListItems> items = ...;
var uniqueItemsByProdId =
items.GroupBy(x => x.ProductId).Select(g => g.First());
This will pick off a single (and somewhat arbitrary) item in the case that more than a item shares a ProductId with another.
Alternatively (and slightly faster), you could use a DistinctBy extension:
public static IEnumerable<T>
DistinctBy<T,TKey>(this IEnumerable<T> src, Func<T,TKey> selector)
{
HashSet<TKey> hs = new HashSet<TKey>();
foreach(var item in src)
{
//Add returns false if item is already in set
if(hs.Add(selector(item)))
{
yield return item;
}
}
}
like this:
items.DistinctBy(x => x.ProductId)
More useful, perhaps, is a query that gives the aggregate quantities for each item by ProductId:
items
.GroupBy(x => x.ProductId)
.Select(g => new MyGroceryListItems{
g.Key.ProductId,
g.Key.ProductName,
Quantity = g.Sum(gg => gg.Quantity)
})
You can implement an equality comparer. There is a solid example on msdn: http://msdn.microsoft.com/ru-ru/library/bb338049.aspx
This will give you more control over which items you consider equal. But involves more coding. If you want to select single and somewhat random item from all the items which share an id, then you'll be better off with spender's solution
I have a question about IGrouping and the Select() method.
Let's say I've got an IEnumerable<IGrouping<int, smth>> in this way:
var groups = list.GroupBy(x => x.ID);
where list is a List<smth>.
And now I need to pass values of each IGrouping to another list in some way:
foreach (var v in structure)
{
v.ListOfSmth = groups.Select(...); // <- ???
}
Can anybody suggest how to get the values (List<smth>) from an IGrouping<int, smth> in such a context?
Since IGrouping<TKey, TElement> implements IEnumerable<TElement>, you can use SelectMany to put all the IEnumerables back into one IEnumerable all together:
List<smth> list = new List<smth>();
IEnumerable<IGrouping<int, smth>> groups = list.GroupBy(x => x.id);
IEnumerable<smth> smths = groups.SelectMany(group => group);
List<smth> newList = smths.ToList();
Here's an example that builds/runs: https://dotnetfiddle.net/DyuaaP
Video commentary of this solution: https://youtu.be/6BsU1n1KTdo
foreach (var v in structure)
{
var group = groups.Single(g => g.Key == v. ??? );
v.ListOfSmth = group.ToList();
}
First you need to select the desired group. Then you can use the ToList method of on the group. The IGrouping is a IEnumerable of the values.
More clarified version of above answers:
IEnumerable<IGrouping<int, ClassA>> groups = list.GroupBy(x => x.PropertyIntOfClassA);
foreach (var groupingByClassA in groups)
{
int propertyIntOfClassA = groupingByClassA.Key;
//iterating through values
foreach (var classA in groupingByClassA)
{
int key = classA.PropertyIntOfClassA;
}
}
From definition of IGrouping :
IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
you can just iterate through elements like this:
IEnumerable<IGrouping<int, smth>> groups = list.GroupBy(x => x.ID)
foreach(IEnumerable<smth> element in groups)
{
//do something
}
var groups = list.GroupBy(x => x.ID);
Can anybody suggest how to get the values (List) from an IGrouping<int, smth> in such a context?
"IGrouping<int, smth> group" is actually an IEnumerable with a key, so you either:
iterate on the group or
use group.ToList() to convert it to a List
foreach (IGrouping<int, smth> group in groups)
{
var thisIsYourGroupKey = group.Key;
List<smth> list = group.ToList(); // or use directly group.foreach
}
If you have an IGrouping<GroupItem, ListItem>, and you want to access the items of type ListItem of this group without utilizing a foreach loop, it's very simple. The object of type IGrouping<GroupItem, ListItem> is of type IEnumerable<ListItem> as well, as it is defined as:
public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
So you can simply say:
foreach (IGrouping<GroupItem, ListItem> group in list.GroupBy(x => x.ID))
{
IEnumerable<ListItem> itemsInThisGroup = group;
// ...
}
If for some reason, it has to be a List<T> instead of an IEnumerable<T>, you can of course still call itemsInThisGroup.ToList(). But usually it's better not to if you needn't.
Simply do this:
// this will "split" the list into groups
var groups = list.GroupBy(x => x.ID);
// groups is a "collection of lists"
foreach (var sublist in groups)
{
// now the sublist is only a part of the original list
// to get which is the value of ID, you can use sublist.Key
}
You don't need Select().GroupBy(expr) makes "a list of lists", kind of.
Assume that you have MyPayments class like
public class Mypayment
{
public int year { get; set; }
public string month { get; set; }
public string price { get; set; }
public bool ispaid { get; set; }
}
and you have a list of MyPayments
public List<Mypayment> mypayments { get; set; }
and you want group the list by year. You can use linq like this:
List<List<Mypayment>> mypayments = (from IGrouping<int, Mypayment> item in yearGroup
let mypayments1 = (from _payment in UserProjects.mypayments
where _payment.year == item.Key
select _payment).ToList()
select mypayments1).ToList();
Consider this,
class Item
{
public string ID { get; set;}
public string Description { get; set; }
}
class SaleItem
{
public string ID { get; set;}
public string Discount { get; set; }
}
var itemsToRemoved = (List<Item>)ViewState["ItemsToRemove"];
// get only rows of ID
var query = from i in itemsToRemoved select i.ID;
var saleItems= (List<SaleItem>)ViewState["SaleItems"];
foreach (string s in query.ToArray())
{
saleItems.RemoveItem(s);
}
How can I write this LINQ phrase using IEnumerable/List Extension methods
// get only rows of ID
var query = from i in items select i.ID;
thanks in advance.
That one's easy:
var query = items.Select(i => i.ID);
A select clause always corresponds to a call to Select. Some of the other operators end up with a rather more complex expansion :) If you work hard, you can get the compiler to do some very odd stuff...
You can find all the details of this and other query expression translations in section 7.16 of the C# specification (v3 or v4).
<plug>
You could also buy C# in Depth, 2nd edition and read chapter 11 if you really wanted to :)</plug>
You can use this:
var query = items.Select(i => i.ID);
A couple of other points:
Here you don't need the call to ToArray:
foreach (string s in query.ToArray())
Also if your list is large and you are removing a lot of items you may want to use List.RemoveAll instead of iterating. Every time you remove an item from a list all the other items after it have to be moved to fill the gap. If you use RemoveAll this only has to be done once at the end, instead of once for every removed item.
List<Item> itemsToRemove = (List<Item>)ViewState["ItemsToRemove"];
HashSet<string> itemIds = new HashSet<string>(itemsToRemove.Select(s => s.ID));
saleItems.RemoveAll(c => itemIds.Contains(c.ID));
public static class ItemCollectionExtensions
{
public static IEnumerable<int> GetItemIds(this List<Item> list)
{
return list.Select(i => i.ID);
}
}