I have a list of items {Id, Name, CategoryId} and a list of categories {Id, Name, IsActive}.
How to get a list {CategoryId, Count} including categories that have zero items.
Currently I have such index:
public class CategoryCountIndex : AbstractIndexCreationTask<Item, CategoryCountIndex.Result>
{
public class Result
{
public string CategoryId { get; set; }
public int Count { get; set; }
}
public CategoryCountIndex()
{
Map = items => from item in items
select new Result
{
CategoryId = item.CategoryId,
Count = 1
};
Reduce = results => from result in results
group result by result.CategoryId
into c
select new Result
{
CategoryId = c.Key,
Count = c.Sum(x => x.Count)
};
}
}
What is the best way to improve/change my solution in order to have categories with no items?
I removed my earlier answer as it proved to be incorrect. In this case you can actually use a MultiMap/Reduce index to solve your problem.
Try using the following index:
public class Category_Items : AbstractMultiMapIndexCreationTask<Category_Items.ReduceResult>
{
public class ReduceResult
{
public string CategoryId { get; set; }
public int Count { get; set; }
}
public Category_Items()
{
AddMap<Item>(items =>
from item in items
select new
{
CategoryId = item.CategoryId,
Count = 1
});
AddMap<Category>(categories =>
from category in categories
select new
{
CategoryId = category.Id,
Count = 0
});
Reduce = results =>
from result in results
group result by result.CategoryId into g
select new ReduceResult
{
CategoryId = g.Key,
Count = g.Sum(x => x.Count)
};
}
}
This will result in the following (three categories, but one without items):
Now you can use a Result Transformer if you want to display the Category Name.
Hope this helps!
Related
I have a list of items on an order, along with the order totals. I'm trying to find a way to add up all of the quantities that have shipped and compare that against the total order quantity, to see if there are any "backordered".
I get a list of PartInfo back, which includes all of the shipments of this product for an order.
public class PartInfo
{
public int OrderId { get; set; }
public string Name { get; set; }
public string TrackingId { get; set; }
public int ShippedQty { get; set; }
public int OrderTotal { get; set; }
}
If I use the following data:
List<PartInfo> PartList = new List<PartInfo>();
PartList.Add(new PartInfo() { OrderId = "1031",
Name = "Watch",
TrackingId = "1Z204E380338943508",
ShippedQty = 1,
OrderTotal = 4});
PartList.Add(new PartInfo() { OrderId = "1031",
Name = "Watch",
TrackingId = "1Z51062E6893884735",
ShippedQty = 2,
OrderTotal = 4});
How can I use LINQ to compare the total ShippedQty to the OrderTotal?
A direct answer could be something like this:
var backOrdered = partList.GroupBy(p => new { p.OrderId, p.OrderTotal })
.Select(g => new
{
g.Key.OrderId,
g.Key.OrderTotal,
TotalShipped = g.Sum(pi => pi.ShippedQty)
})
.Where(x => x.TotalShipped < x.OrderTotal);
Assuming that OrderId and OrderTotal are always linked, so you can group by them and always have one group per OrderId.
But as I said in a comment, if the data comes from a database there may be better ways to get the data, esp. when there is an Order with a collection navigation property containing PartInfos.
My reading is that the ShippedQty is for the single item (Name) for the order, so you want to group the items by the OrderId and count the quantity shipped. In that case, you can use group by LINQ:
var groupResults = PartList.GroupBy(
// Group by OrderId
x => x.OrderId,
// Select collection of PartInfo based on the OrderId
x => x,
// For each group based on the OrderId, create a new anonymous type
// and sum the total number of items shipped.
(x,y) => new {
OrderId = x,
ShippedTotal = y.Sum(z => z.ShippedQty),
OrderTotal = y.First().OrderTotal
});
For the sample data, this gives a single result, an anonymous type with three int properties (from C# interactive console):
f__AnonymousType0#3<int, int, int>> { { OrderId = 1031, ShippedTotal = 3, OrderTotal = 4 } }
You can then filter out the results to see where the order quantity is less than the order total
groupResults.Where(x => x.ShippedTotal < x.OrderTotal) ...
I have the following query in linq, which takes 2 lists as a data source. The first contains a list of ProductID and its description
public class Venta
{
public string ProductoId { get; set; }
public string clienteRut { get; set; }
}
public class Ventas
{
public List<Venta> lstVentas { get; set; }
}
and the other list has the products sold
public class Productos
{
public List<Producto> lstProductos { get; set; }
}
public class Producto
{
public string id { get; set; }
public string name { get; set; }
}
I need to consult the 5 most sold products, ordered by quantity from the most sold, to the least sold.
So far I have the following linq query, but I do not know how to do it so that I am given the list of the first 5, ordered from highest to lowest based on the quantity (cont)
Venta vta1 = new Venta();
vta1.ProductoId = "1";
vta1.clienteRut = "121370654";
Venta vta2 = new Venta();
vta2.ProductoId = "2";
vta2.clienteRut = "121370654";
Venta vta3 = new Venta();
vta3.ProductoId = "3";
vta3.clienteRut = "121370654";
List<Venta> lstVentasDia = new List<Venta>();
lstVentasDia.Add(vta1);
lstVentasDia.Add(vta2);
lstVentasDia.Add(vta3);
VentasDia vtas = new VentasDia();
vtas.date = "2018-05-01";
vtas.lstVentas = lstVentasDia;
var Lista5Top = from vendidos in vtas.lstVentas
orderby vendidos.ProductoId
group vendidos by vendidos.ProductoId into Grupo
select new { key = Grupo.Key, cont = Grupo.Count() };
I need in addition to that group of result, add the name of the product that is in the list Products, and order it by quantity sold of greater to less only the first 5
Thankful in advance
Gloria
Try following :
Productos productos = new Productos();
var Lista5Top = (from vendidos in vtas.lstVentas
join prod in productos.lstProductos on vendidos.ProductoId equals prod.id
select new { id = vendidos.ProductoId, rut = vendidos.clienteRut, name = prod.name })
.OrderBy(x => x.id)
.GroupBy(x => x.id)
.Select(x => new { id = x.Key, cont = x.Count(), name = x.FirstOrDefault().name })
.OrderByDescending(x => x.cont)
.Take(5).ToList();
I have something working in plain simpel loops but I want to know how to do it with LINQ, if possible.
I don't even know how to describe what i want to do. But stack overflow wants me to do so in words instead of with an example, so this is me tricking it...
The classes (stripped down)
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public List<CategoryGroup> CategoryGroups { get; set; }
}
public class CategoryGroup
{
public int CategoryGroupId { get; set; }
public string Name { get; set; }
public List<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
}
What I want to do
From
Product 1
CategoryGroup 1
Category 11
Category 12
Category 13
CategoryGroup 2
Category 21
Category 22
Product 2
Category Group 1
Category 11
Category 14
Category Group 2
Category 21
Category Group 3
Category 31
To
Category Group 1
Category 11
Category 12
Category 13
Category 14
Category Group 2
Category 21
Category 22
Category Group 3
Category 31
The working code
var categoryGroups = new List<CategoryGroup>();
foreach (var product in products)
{
foreach (var categoryGroup in product.CategoryGroups)
{
var group = categoryGroups.SingleOrDefault(cg => cg.CategoryGroupId == categoryGroup.CategoryGroupId);
if (group == null)
{
group = new CategoryGroup {
Categories = new List<Category>(),
CategoryGroupId = categoryGroup.CategoryGroupId,
Name = categoryGroup.Name
};
categoryGroups.Add(group);
}
foreach (var category in categoryGroup.Categories)
{
if (group.Categories.Any(c => c.CategoryId == category.CategoryId))
{
continue;
}
group.Categories.Add(category);
}
}
}
You can do many subselections through SelectMany:
var result = products
.SelectMany(x => x.CategoryGroups)
.GroupBy(x => x.CategoryGroupId)
.Select(x => new CategoryGroup
{
Categories = x.SelectMany(y => y.Categories)
.Distinct(y => y.CategoryId)
.OrderBy(y => y.CategoryId)
.ToList(),
CategoryGroupId = x.Key,
Name = x.Values.First().Name
})
.OrderBy(x => x.CategoryGroupId)
.ToList();
Here is the query to get all the different category groups
var categoryGroups = products.SelectMany(g => g.CategoryGroups)
.GroupBy(x => x.CategoryGroupId)
.Select(y => new { CategoryGroupId = y.Key,
Categories = y.SelectMany(category => category.Categories).Distinct().ToList() });
To eliminate duplicate categories you will have to modify your Category object to implement the IEquatable interface as follows
public class Category : IEquatable<Category>
{
public int CategoryId { get; set; }
public string Name { get; set; }
public bool Equals(Category other)
{
//Check whether the compared object is null.
if (Object.ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (Object.ReferenceEquals(this, other)) return true;
//Check whether the products' properties are equal.
return CategoryId.Equals(other.CategoryId) && Name.Equals(other.Name);
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public override int GetHashCode()
{
//Get hash code for the Name field if it is not null.
int hashProductName = Name == null ? 0 : Name.GetHashCode();
//Get hash code for the Code field.
int hashProductCode = CategoryId.GetHashCode();
//Calculate the hash code for the product.
return hashProductName ^ hashProductCode;
}
}
Implementing the interface allows the Distinct() call in LINQ to compare the list of objects and eliminate duplicates.
Hope that helps.
I'm using RavenDB 2 Client.
I want my users to be able to search by Category and return products. For a category name of High Heels I want the user to be able to search heels and get back products with category High Heels.
I set up my CategoryName field in the index as follows:
Analyzers.Add(x => x.CategoryName, "SimpleAnalyzer");
The problem comes when I want to show the facets for CategoryName. In stead of returning high heels hits 1, it returns high hits 1 and heels hits 1.
I understand why it's doing this and I've tried using:
Stores.Add(x => x.CategoryName, FieldStorage.Yes);
but with no success when using it with facetes.
So my question is, how do I get the facet to return high heels instead of high and heels on a field that usses SimpleAnalyzer?
My Code is below:
My Index
public class ProductIndex : AbstractIndexCreationTask<Product,ProductIndex.ProductIndexItem>
{
public class ProductIndexItem
{
public string Name { get; set; }
public string CategoryName { get; set; }
}
public ProductIndex()
{
Map = products => from product in products
from category in product.Categories
select new
{
product.Name,
CategoryName = category.Name
};
Stores.Add(x => x.CategoryName, FieldStorage.Yes);
Analyzers.Add(x => x.CategoryName, "SimpleAnalyzer");
}
}
My Test
[Test]
public void MultiTermCategoryTest()
{
var product = new Product
{
Name = "MyProductName",
Categories = new List<Category>
{
new Category
{
Name = "High Heels",
}
}
};
_session.Store(product);
_session.SaveChanges();
var query = _session.Advanced.LuceneQuery<Product>("ProductIndex")
.WaitForNonStaleResults()
.Search("CategoryName", "heels");
var products = query.ToList();
var facets = query.SelectFields<Facet>("CategoryName").ToFacets("facets/ProdctFacets");
// Check that product has been returned
Assert.That(products.Count, Is.EqualTo(1), "Product count is incorrect.");
// Check that facet has been returned
Assert.That(facets.Results.Count, Is.EqualTo(1), "Facet Results count is incorrect");
var facetResult = facets.Results.FirstOrDefault();
// Check that factes are what I want
Assert.That(facetResult.Key, Is.EqualTo("CategoryName"));
// ** Fails here returning a count of 2**
Assert.That(facetResult.Value.Values.Count, Is.EqualTo(1), "Facet.Value.Values count is incorrect");
}
Answer thanks to Ayende Rahien's advice.
Turns out you can't do the above the way I was trying.
I had to have two fields in the index, one for indexing and one for storing the original value. So I changed my index to the following:
public class ProductIndex : AbstractIndexCreationTask<Product,ProductIndex.ProductIndexItem>
{
public class ProductIndexItem
{
public string Name { get; set; }
public string CategoryName { get; set; }
public string CategoryNameAnalyzed { get; set; }
}
public ProductIndex()
{
Map = products => from product in products
from category in product.Categories
select new
{
product.Name,
CategoryName = category.Name,
CategoryNameAnalyzed = category.Name,
};
Stores.Add(x => x.CategoryName, FieldStorage.Yes);
Analyzers.Add(x => x.CategoryNameAnalyzed, "SimpleAnalyzer");
}
}
And my query changed to:
var query = _session.Advanced.LuceneQuery<Product>("ProductIndex")
.WaitForNonStaleResults()
.Search("CategoryNameAnalyzed", "heels");
var products = query.ToList();
var facets = query.ToFacets("facets/ProdctFacets");
I hope this helps someone else.
I have an object which has properties ID, brandID, brandName, NumPages, and Type.
i need to show the top 5 brands by numPage size, a brand may have multiple IDs, so I need to group by brand
listing.OrderByDescending(o => o.numPage).GroupBy(o=> o.brandName).Take(5).ToList();
is alone the lines of what im looking for but this is not valid code.
It sounds like a given brand name may have several ID's and that you want the top 5 brand's sorted by numPage. Is that correct
If so try the following
var query = listing
.GroupBy(x => x.brandName)
.OrderByDescending(brands => brands.Sum(x => x.numPage))
.Select(x => x.Key)
.Take(5);
Note: After the GroupBy operation you're now passing around a collection of the brand objects instead of single ones. Hence to order by the numPage we need to sum it for all of the brand objects in the group. The .Select(x => x.Key) will select back out the original brandName on which the group is based
just tried and it works:
public class Listing
{
public int ID { get; set; }
public int BrandID { get; set; }
public string BrandName { get; set; }
public int NumPages { get; set; }
public Type Type { get; set; }
}
Here the filtering
Listing listing1 = new Listing() { NumPages = 2, BrandName = "xx" };
Listing listing2 = new Listing() { NumPages = 2, BrandName = "xx" };
Listing listing3 = new Listing() { NumPages = 2, BrandName = "xx" };
Listing listing4 = new Listing() { NumPages = 3, BrandName = "xxxxx" };
List<Listing> allListings = new List<Listing>() { listing1, listing2, listing3, listing4 };
var result = allListings.OrderByDescending(x => x.NumPages).GroupBy(x => x.BrandName).Take(5);