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();
}
Related
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
{
...
});
The Series object contains a property called Skus and it is IEnumerable
If this sku is in the allowed list of skus then I need that series.
In my example below, I'm joining on s.SeriesId which is not correct.
I believe it needs to be the collection s.Skus
I only want to return a series that has the contained sku in the collection.
IEnumerable<Data.Models.Series> series = await _seriesRepository.GetSeriesAsync(Properties.Settings.Default.Channel, page, limit);
string[] skusInSeries = series?.SelectMany(x => x.Skus).Distinct().ToArray();
IEnumerable<string> itemNumbers = GetAllowedSkus(Customer, Shipto, EnvironmentCode, AcceptLanguage, skusInSeries, Warehouse);
var selected = from s in series
join i in itemNumbers
on s.SeriesId equals i //s.Skus IEnumerable<string>
select s;
var selected = from s in series
where itemNumbers.Any(i => s.Skus.Contains(i))
select s;
Or the other way:
var selected = from s in series
where s.Skus.Any(sku => itemNumbers.Contains(sku))
select s;
I am guessing there are more Skus than itemNumbers typically and the the first choice is better. It may also be better to change itemNumbers to a list that can be passed to the database:
var itemNumbers = GetAllowedSkus(Customer, Shipto, EnvironmentCode, AcceptLanguage, skusInSeries, Warehouse).ToList();
var selected = from s in series
where itemNumbers.Any(i => s.Skus.Contains(i))
select s;
If a SQL (or other) database isn't involved, you would convert itemNumbers to a HashSet for efficient lookup:
var itemNumbers = new HashSet<string>(GetAllowedSkus(Customer, Shipto, EnvironmentCode, AcceptLanguage, skusInSeries, Warehouse));
var selected = from s in series
where s.Skus.Any(sku => itemNumbers.Contains(sku))
select s;
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();
I am trying to subtract the field "QtyOnHand" in the table "Inventory" from the quantity in List. But I get this error:
Unable to cast object of type 'System.Collections.Generic.List`1[System.Int32]' to type 'System.IConvertible'.
It shows that the error occurs at:
var cartQty = (from i in items where i.ProductId == Convert.ToInt32(productId) select i.Qty).SingleOrDefault();
My code is the following:
protected void btnCheckout_Click(object sender, EventArgs e)
{
int inventoryQty;
List<Item> items = Session["Cart"] as List<Item>;
using (ProjectEntities myEntities = new ProjectEntities())
{
var productId = (from i in items select i.ProductId).ToList();
var cartQty = (from i in items where i.ProductId == Convert.ToInt32(productId) select i.Qty).SingleOrDefault();
var inventory = (from q in myEntities.Inventories
where q.ProductId == Convert.ToInt32(productId)
select q).SingleOrDefault();
inventoryQty = inventory.QtyOnHand - cartQty;
myEntities.SaveChanges();
Response.Redirect("~/Home.aspx");
}
}
Thanks in advance!
var productId = (from i in items select i.ProductId).ToList();
productId variable contains a list of items and you are trying to pass that to Convert.ToInt32 to method which is not expecting a collection of items!. That is causing the issue.
Since you are cart may have more than one item, you probably need to loop throug the productIds and do your other calculation.
var productIdList = (from i in items select i.ProductId).ToList();
foreach(var productId in productIdList)
{
var cartQty = (from i in items where i.ProductId == Convert.ToInt32(productId)
select i.Qty).SingleOrDefault();
// Your remaining code
}
I am assuming the productId in your cart item is of numeric value, but of string type. Then only the Convert.ToInt32 will work as it is expecting the string representation of some valid numeric value (Ex :"234")
If it is of int type, you do not need the Convert.ToInt32(productId) part in your where clause, just use i.ProductId==productId
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 { }
}