LINQ query with group by and ascending ordering not working - c#

I have a class Ingredients which has a property Items of the type List<Ingredient>.
In one of my pages I am using a GridView to display all the ingredients, grouped by first letter:
<Page.Resources>
<CollectionViewSource x:Name="IngredientsViewSource" IsSourceGrouped="True" ItemsPath="Items"/>
</Page.Resources>
when the page is loaded, the CollectionViewSource's Source property is set like this:
this.IngredientsViewSource.Source = CurrentData.Ingredients.GroupedItems;
GroupedItems is a property of the Ingredients class, which takes the property Items and orders and groups everything in order to be ready to use:
public object GroupedItems
{
get
{
if (this.Items != null)
{
return from IngredientIteration in this.Items
//orderby IngredientIteration.Name ascending
group IngredientIteration
by IngredientIteration.FirstLetter
into IngredientGroup
//orderby IngredientGroup.Key ascending
select new {
FirstLetter = IngredientGroup.Key,
Items = IngredientGroup
};
}
else
{
return null;
}
}
private set { }
}
this is working quite well. Now I would like to sort the result, because now the first letters' order is all messed up. However, when I remove the comment marks in front of the two orderby clauses, it get's totally weird. Leaving the orderby clauses like it is now results in correctly ordered groups, but only the first item per group is showed.
When I change ascending to descending, though, everything works as expected: groups are sorted descending, all items are shown, and the items are sorted descending inside each group.
This makes no sense to me, why is descending working but ascending not? Am I missing something here?

Although as what you described (descending works but ascending doesn't), it's so strange. Your query has nothing complicated, I would use the method syntax for it and it should work as you expected:
public object GroupedItems {
get {
if (this.Items != null)
{
return Items.OrderBy(item=>item.Name)
.GroupBy(item=>item.FirstLetter)
.OrderBy(g=>g.Key)
.Select(g=> new {
FirstLetter = g.Key,
Items = g.ToList()
}).ToList();
}
else {
return null;
}
}
private set { }
}

Related

LINQ: select specific value in a datatable column

In table I have 4 Columns GroupName, Display, Value and ID
How can I just show a specific data in display. I only want to show some of the groupNames Data
for example I only want to show Groupname = company and display = Forbes
Here's my linq
sample = (from c in smsDashboardDBContext.CodeDefinitions
orderby c.Display ascending
select new CodeDefinitionDTO
{
GroupName = c.GroupName,
Display = c.Display,
Value = c.Value,
Id = c.Id
}).ToList();
You can add a where statement in the query.
where c.GroupName == "company" && c.Display == "Forbes"
I only want to show some of the groupNames Data for example I only want to show Groupname = company and display = Forbes
Before the ToList, use a Where to keep only those items that you want to show:
var company = ...
var forbes = ...
var result = smsDashboardDBContext.CodeDefinitions
.OrderBy(codeDefinition => codeDefintion.Display)
.Select(codeDefinition => new CodeDefinitionDTO
{
Id = codeDefinition.Id,
GroupName = codeDefinition.GroupName,
Display = codeDefinition.Display,
Value = codeDefinition.Value,
})
.Where(codeDefinition => codeDefition.GroupName == company
&& codeDefintion.Display == forbes);
In words:
Order all codeDefinitions that are in the table of CodeDefintions by ascending value of property codeDefintion.Display.
From every codeDefinition in this ordered sequence make one new CodeDefinitionDTO with the following properties filled: Id, GroupName, Display, Value
Frome every codeDefintion in this sequence of CodeDefinitionDTOs, keep only those codeDefinitions that have a value for property GroupName that equals company and a value for property Display that equals forbes.
There is room for improvement!
Suppose your table has one million elements, and after the Where, only five elements are left. Then you will have sorted almost one million elements for nothing. Consider to first do the Where, then the Order and finally a Select.
In LINQ, try to do aWhere as soon as possible: all following statements will have to work on less items
In LINQ, try to do a Select as late as possible, preferrably just before the ToList / FirstOrDefault / ... This way the Select has to be done for as few elements as possible
So first the Where, then the OrderBy, then the Select, and finally the ToList / FirstOrDefault, etc:
var result = smsDashboardDBContext.CodeDefinitions
.Where(codeDefinition => ...);
.OrderBy(codeDefinition => codeDefintion.Display)
.Select(codeDefinition => new CodeDefinitionDTO
{
...
});

LINQ sentence, check if remain elements

We're new in LINQ and we want to know if the are any method to check if there are elements remaining in the sentence like the ResultSet.next() from Java.
In Java the resultSet return true if there are elements remaining and false if not. We want to know if the is a method like that in LINQ.
public List<Product> FindProductsByKeyword(string productName, Category category, int page, int size)
{
DbSet<Product> products = Context.Set<Product>();
List<Product> result;
if (category == null)
{
result = (from p in products
where p.productName.Contains(productName) //, StringComparison.OrdinalIgnoreCase)
orderby p.productName descending
select p).Skip(page).Take(size).Include("Category").ToList();
}
else
{
result = (from p in products
where p.productName.Contains(productName)
&& p.categoryId == category.id
orderby p.productName descending
select p).Skip(page).Take(size).ToList();
}
return result;
}
This our code( I dont know if this will help) we take elements with a number of Size but we dont know if there are more elements.
Using the terminating method ToList() initiates a DB query and parses the returned table as a simple in-memory List<T> (same as ArrayList<T> in Java).
If you want to check if there are any entries in the result, you can just check the list's Count property:
if (result.Count > 0)
{
// Do something
}
Or use LINQ's Any() extension method, which is a bit more readable in this context:
if (result.Any())
{
// Do something
}
If you returned an Enumerable, then you could use Enumerable.MoveNext()
For example:
var p = products.AsEnumerable();
var i = p.GetEnumerator();
while( i.MoveNext())
i.Current.productfieldname.Dump() // LinqPad .Dump() shows value

Select top N elements and remember occurence's order

I have a requirement to select top N elements of related products from a big list of products.
So far, I have below code and it works perfectly.
class Product
{
public string Name;
public double Rating;
public List<Product> RelatedProducts;
public List<Product> GetTopRelatedProducts(int N)
{
var relatedSet = new HashSet<Product>();
var relatedListQueue = new Queue<List<Product>>();
if (RelatedProducts != null && RelatedProducts.Count > 0)
relatedListQueue.Enqueue(RelatedProducts);
while (relatedListQueue.Count > 0)
{
var relatedList = relatedListQueue.Dequeue();
foreach (var product in relatedList)
{
if (product != this && relatedSet.Add(product) && product.RelatedProducts != null && product.RelatedProducts.Count > 0)
relatedListQueue.Enqueue(product.RelatedProducts);
}
}
return relatedSet.OrderByDescending(x => x.Rating).Take(N).OrderBy(/*How to order by occurrence here? */).ToList();
}
}
Now, I want GetTopRelatedProducts method to remember the occurrence order of top N products. First added product to the HashSet will be at the begining of the returned List.
For example, if I have this scenario:
//...
relatedSet.Add(new Product(){Name="A", Rating=3});
relatedSet.Add(new Product(){Name="B", Rating=4});
relatedSet.Add(new Product(){Name="C", Rating=5});
//...
and if N = 2, the method should return : B,C instead of C,B because B was added first to the HashSet.
So I changed the return statement in the method to:
var relatedSetCopy = relatedSet.ToList();
return (from p in relatedSet.OrderByDescending(x => x.Rate).Take(N)
join c in relatedSetCopy on p.Name equals c.Name
let index = relatedSetCopy.IndexOf(c)
orderby index
select p).ToList();
Basically, I use LINQ Join to re-order the list in the same way it was before the ordering on Rating.
I want to do it this way because first added product has more similarity with selected product than others.
I have two questions here:
Is there a better way to re-order the returned list?
Is there a better design to handle relation between products? (I was thinking about implementing a tree structure. So object navigation and retrieval will be faster)
Is there a better way to re-order the returned list?
You can simply Intersect the relatedSet with the top N related reordered set because Intersect yields the items based on their order in the first sequence.
So instead of
return relatedSet.OrderByDescending(x => x.Rating).Take(N).ToList();
you would use
return relatedSet.Intersect(relatedSet.OrderByDescending(x => x.Rating).Take(N)).ToList();

Keeping one item in a list that should be disabled

I have a page that contains a list a companies. Each company is appart of a group, like such:
But here's the catch, groups can be disabled, if they are, that would not change the display of the list but it does have an impact on my edition page.
As you can see, there is a DropDownList containing my groups. But if a group is disabled it does not show up on the list because I retrieve said list like such:
public IEnumerable<SelectListItem> ListGroupEnabled()
{
List<SelectListItem> X = _entities.Groups.Where(p => p.IsEnabled).ToList().Select(c => new SelectListItem { Value = c.GroupId.ToString(), Text = c.Name }).ToList();
return X;
}
But here's what I wish to achieve:
If I were to edit a company that was appart of a disabled group, I still want that group (and only that group) to appear in the DDList among enabled groups.
This is for the sake of logic, it wouldn't make sense to simply not have the group a company is appart off in its list.
How may I change the code I showed up above in order to keep the group the company is appart off in my list?
Hello SelectListItem have Properties such as Disabled you can set it before send. referrence
fix your code at below.
public IEnumerable<SelectListItem> ListGroupEnabled()
{
List<SelectListItem> X = _entities.Groups.Select(c => new SelectListItem { Value = c.GroupId.ToString(), Text = c.Name , Disabled = c.IsEnabled }).ToList();
return X;
}
You need to pass in the current group id and include that in your where clause:
public IEnumerable<SelectListItem> ListGroupEnabled(int? currentGroupId = null)
{
return _entities.Groups.Where(
p =>
p.IsEnabled ||
(currentGroupId.HasValue && p.GroupId == currentGroupId.Value)
).Select(c => new SelectListItem { Value = c.GroupId.ToString(), Text = c.Name }).ToList();
}
You also don't need to call ToList before selecting. That actually makes the query less optimized as without that, Entity Framework can just select the columns it needs (GroupId and Name).

How to use LINQ-to-Entity to query by contained objects

Let's say I have a list of Boxes and in a box you can have multiple items.
Box (id)
Items (id, boxId)
I'm trying to build a linq to entity query that can return all the boxes that contains ALL specified items.
List<Box> FindBoxContainingAllSpecifiedItems(List<int> itemIds)
{
var q = from box in ctx.Boxes
where ???
}
Thanks for the help
It depends on the implementation of boxes. But lets for the moment say it has a property Items with the type IEnumerable<int>. In that case you could use the Intersect extension method to see if the items are all accounted for
var q = from box in ctx.Boxes
where box.Items.Intersect(itemIds).Count() == itemIds.Count;
Here is what I have found thanks to JaredPar contribution.
List<Location> FindLocationContainingAllItems(List<int> itemIds)
{
var itemQuery = from item in ctx.Items
select item;
// Workaround the Where In Clause (http://social.msdn.microsoft.com/Forums/en/adodotnetentityframework/thread/095745fe-dcf0-4142-b684-b7e4a1ab59f0)
itemQuery = itemQuery.Where(BuildContainExpression<Items, int>(i=> i.Id, itemIds));
int itemCount = itemIds.Count();
var locQuery = from loc in ctx.Locations
from box in loc.Boxes
where (from items in box.Items select items).Intersect(itemQuery).Count == itemCount
select loc;
return locQuery.ToList();
}

Categories