How to do a grouped distinct in linq - c#

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.

Related

how to sort a list of object value in an object

This is the query that i use to fetch items and item detail of each item with specific id
public class ItemModel
{
public int ItemSeq{get;set}
public string ItemName{get;set;}
public double? Amount{get;set;}
}
public class SampleModel
{
public string Id{ get; set; }
public string Code{ get; set; }
public List<ItemModel> Items{ get; set; }
}
public List<SampleModel> GetItems(string id)
{
var items= _context.ItemTable.Where(t => t.Id== id).ToList<SampleModel>();
return list;
}
To make the requirement clear there are two table ItemTable and ItemDetailTable And Id being ItemTable primary key and foreign key in ItamDetailTableand the ItemDetailTable may have multiple values for a single ID and the above query returns the item and the details from ItemDetailTable with the specific id what i want is to have a sorted item detail value by the value of Amount. Is there any way to do that ?
Sample Data
ItemTable
Id Code
1 Code1
2 Code3
ItemDetailTable
Id ItemSeq ItemName Amount
1 1 ABC1 200
1 2 ABC2 129
1 3 ABC3 549
2 1 DEF1 265
2 2 DEf2 970
what i want is when the value of Id is 1 the return value to be
Id: 1
Code: Code 1
Items:[
0:{
ItemSeq: 2
ItemName: ABC2
Amount: 129
}
1:{
ItemSeq: 1
ItemName: ABC1
Amount: 200
},
1:{
ItemSeq: 3
ItemName: ABC3
Amount: 549
}
]
You need to return sorted list of ItemModel by passing id parameter to GetItems method.
public List<ItemModel> GetItems(string id)
{
var items = _context.SampleTable.Where(t => t.Id == id).SelectMany(x => x.Items).Where(p => p.Amount != null).OrderBy(x => x.Amount).ToList();
return items.ToList();
}
Edit:
the above query returns the item and the details from ItemDetailTable with the specific id
You also need to add foreign key property to ItemDetailTable like
public class ItemDetailTable
{
public int ItemSeq { get; set; }
public string ItemName { get; set; }
public double? Amount { get; set; }
public string Id { get; set; } //Foreign key of ItemTable
}
public ItemTable GetItems(string id)
{
//Get "ItemTable" record from database
var item = _context.ItemTable.Where(x => x.Id == id).SingleOrDefault();
//Retrieve its "Items" and sort by ascending order
var itemDetails = item.Items.Where(x => x.Id == id).Where(p => p.Amount != null).OrderBy(x => x.Amount).ToList();
//Preare a new "ItemTable" object to return
ItemTable itemTable = new ItemTable
{
Code = item.Code,
Id = item.Id,
Items = itemDetails
};
//Return new "itemTable" with sorted list of "ItemDetailTable"
return itemTable;
}
Edit 2:
public ItemTable GetItems(string id)
{
var result = (from i in _context.ItemTable
where i.Id == id
let sorting = i.Items.Where(x => x.Id == id).Where(x => x.Amount != null).OrderBy(x => x.Amount).ToList()
select new ItemTable
{
Id = i.Id,
Code = i.Code,
Items = sorting
}).FirstOrDefault(); //Or => SingleOrDefault
return result;
}

RavenDB count index including zero values

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!

EF get list of parent entities that have list of child

I have to next 2 entities in my project
public class Product
{
public Product()
{
this.ProductImages = new HashSet<ProductImage>();
this.ProductParams = new HashSet<ProductParam>();
}
public int ID { get; set; }
public int BrandID { get; set; }
public int CodeProductTypeID { get; set; }
public string SeriaNumber { get; set; }
public string ModelNumber { get; set; }
public decimal Price { get; set; }
public bool AvailableInStock { get; set; }
public virtual Brand Brand { get; set; }
public virtual CodeProductType CodeProductType { get; set; }
public virtual ICollection<ProductImage> ProductImages { get; set; }
public virtual ICollection<ProductParam> ProductParams { get; set; }
}
public class ProductParam
{
public int Id { get; set; }
public int ProductId { get; set; }
public int CodeProductParamId { get; set; }
public string Value { get; set; }
public virtual Product Product { get; set; }
public virtual CodeProductParam CodeProductParam { get; set; }
}
and I want to get list of Products which has list of specified parameters
var prodParamCritria = new List<ProductParam>()
{
new ProductParam(){CodeProductParamId =1, Value="Black" },
new ProductParam(){CodeProductParamId =2, Value="Steal"}
};
in sql I can do it by using EXISTS clause twise
SELECT *
FROM Products p
WHERE EXISTS (
SELECT *
FROM ProductParams pp
WHERE pp.ProductId = p.ID
AND (pp.CodeProductParamId = 1 AND pp.[Value] = N'Black')
)
AND EXISTS (
SELECT *
FROM ProductParams pp
WHERE pp.ProductId = p.ID
AND pp.CodeProductParamId = 2
AND pp.[Value] = N'Steal'
)
How can i get same result by EF methods or linq
Try this:
var products= db.Products.Where(p=>p.ProductParams.Any(pp=>pp.CodeProductParamId == 1 && pp.Value == "Black") &&
p.ProductParams.Any(pp=>pp.CodeProductParamId == 2 && pp.Value == "Steal"));
Update
The problem in work with that list of ProductParam to use it as a filter is that EF doesn't know how to translate a PodructParam object to SQL, that's way if you execute a query like this:
var products2 = db.Products.Where(p => prodParamCritria.All(pp => p.ProductParams.Any(e => pp.CodeProductParamId == e.CodeProductParamId && pp.Value == e.Value)));
You will get an NotSupportedException as you comment in the answer of #BostjanKodre.
I have a solution for you but probably you will not like it. To resolve that issue you could call the ToList method before call the Where. This way you will bring all products to memory and you would work with Linq to Object instead Linq to Entities, but this is extremely inefficient because you are filtering in memory and not in DB.
var products3 = db.Products.ToList().Where(p => prodParamCritria.All(pp => p.ProductParams.Any(e => pp.CodeProductParamId == e.CodeProductParamId && pp.Value == e.Value)));
If you want filter by one criteria then this could be more simple and you are going to be able filtering using a list of a particular primitive type. If you, for example, want to filter the products only by CodeProductParamId, then you could do this:
var ids = new List<int> {1, 2};
var products = db.Products.Where(p => ids.All(i=>p.ProductParams.Any(pp=>pp.CodeProductParamId==i))).ToList();
This is because you are working with a primitive type and not with a custom object.
I suppose something like that should work
db.Product.Where(x => x.ProductParams.FirstOrDefault(y => y.CodeProductParamId == 1) != null && x.ProductParams.FirstOrDefault(y => y.CodeProductParamId == 2) != null).ToList();
or better
db.Product.Where(x => x.ProductParams.Any(y => y.CodeProductParamId == 1) && x.ProductParams.Any(y => y.CodeProductParamId == 2)).ToList();
Ok, if you need to make query on parameters in list prodParamCriteria it will look like this:
db.Product.Where(x => prodParamCritria.All(c=> x.ProductParams.Any(p=>p.CodeProductParamId == c.CodeProductParamId && p.Value== c.Value))).ToList();
I forgot that complex types cannot be used in query database, so i propose you to convert your prodParamCriteria to dictionary and use it in query
Dictionary<int, string> dctParams = prodParamCritria.ToDictionary(x => x.CodeProductParamId , y=>y.Value);
db.Product.Where(x => dctParams.All(c => x.ProductParams.Any(p=> p.CodeProductParamId == c.Key && p.Value== c.Value))).ToList();
another variation:
IEnumerable<Int32> lis = prodParamCritria.Select(x => x.CodeProductParamId).ToList();
var q = Products.Select(
x => new {
p = x,
cs = x.ProductParams.Where(y => lis.Contains(y.Id))
}
).Where(y => y.cs.Count() == lis.Count()).
ToList();
with a named class like (or maybe without, but not in linqpad)
public class daoClass {
public Product p {get; set;}
public Int32 cs {get; set;}
}
IEnumerable<Int32> lis = prodParamCritria.Select(x => x.CodeProductParamId).ToList();
var q = Products.Select(
x => new daoClass {
p = x,
cs = x.ProductParams.Where(y => lis.Contains(y.Id))
}
).Where(y => y.cs.Count() == lis.Count()).
SelectMany(y => y.p).
ToList();

Entityframework select all childs related data when call parent

I have categories table which contains :
public partial class C_Categories
{
public int CatId { get; set; }
public Nullable<int> ParentId { get; set; }
public string ImageUrl { get; set; }
public virtual ICollection<C_Node> C_Node { get; set; }
}
And i have node table which contains :
public partial class C_Node
{
public int NodeId{ get; set; }
public Nullable<int> CatId { get; set; }
public System.DateTime PostDate { get; set; }
public virtual C_Categories C_Categories { get; set; }
}
And my controller :
public ActionResult Index(int? catId)
{
IQueryable<C_Node> moduleItems = db.C_Node;
if (catId != null)
{
//here i want to check if category is parent , get all node related to his child categories
moduleItems = moduleItems.Where(x => x.CatId == catId);
}
return View(moduleItems.ToList());
}
At my controller i want to check if category is parent , get all node table related to his child categories,
I tried to use any , but it failed .
to explain my question : i have category : electronics and electronics have childs computers, mobiles . i have products on node table under computers and mobiles , if catId is electronics i want all products under its childs computers, mobiles
You first need to find all the categories under the parent; if there are only 2 levels this is simple:
...
if (catId != null)
{
// Find the child categories for which this is the parent
var childCatIds = db.C_Categories
.Where(cat => cat.ParentId == catId)
.Select(cat => cat.CatId)
.ToList();
if (childCatIds.Count == 0)
// Not a parent category: Just find the items for the category as before
moduleItems = moduleItems.Where(x => x.CatId == catId);
else
// Parent category: Find the items for the child categories
moduleItems = moduleItems.Where(x => childCatIds.Contains(x.CatId));
}
If there are more than 2 levels, you will need to find the child ids recursively.
private List<int> GetChildCatIds(List<int> parentCatIds)
{
var childCatIds = db.C_Categories
.Where(cat => cat.ParentId.HasValue && parentCatIds.Contains(cat.ParentId.Value))
.Select(cat => cat.CatId)
.ToList();
if (childCatIds.Count == 0)
// Reached the end of the tree: no more children
return parentCatIds;
else
// Recursive call to find the next child level:
return GetChildCatIds(childCatIds);
}
...
if (catId != null)
{
var childCatIds = GetChildCatIds(new List<int>{catId.Value});
moduleItems = moduleItems.Where(x => childCatIds.Contains(x.CatId));
}
How about this:
moduleItems = dbcontext.C_Nodes.Where(n => n.CatId == catId);

Issue Related to SelectMany function in LINQ

I have two tables in Database:
PostCalculationLine
PostCaluclationLineProduct
PostCalculationLineProduct(table2) contains Foriegn key of PostCalucationLineId(table1)
In C# code I have two different Models for these two tables as follows:
public class PostCalculationLine : BaseModel
{
public long Id{ get; set; }
public string Position { get; set; }
public virtual Order Order { get; set; }
public virtual Task Task { get; set; }
//some other properties go here
public virtual IList<PostCalculationLineProduct> PostCalculationLineProducts { get; set; }
}
and
public class PostCalculationLineProduct : BaseModel
{
public long Id {get;set;}
public string Description { get; set; }
//some other properties go here
}
Now in Entityframework code, I fetch data from PostCalculationLineProduct as follows:
PostCalculationLineRepository pclr = new PostCalculationLineRepository();
DataSourceResult dsrResult = pclr.Get()
.SelectMany(p => p.PostCalculationLineProducts)
.Where(c => c.Product.ProductType.Id == 1 && c.DeletedOn == null)
.Select(c => new HourGridViewModel()
{
Id = c.Id,
Date = c.From,
EmployeeName = c.Employee != null ?c.Employee.Name:string.Empty,
Description= c.Description,
ProductName = c.Product != null?c.Product.Name :string.Empty,
From = c.From,
To = c.Till,
Quantity = c.Amount,
LinkedTo = "OrderName",
Customer ="Customer"
PostCalculationLineId = ____________
})
.ToDataSourceResult(request);
In the above query I want to get PostCalculationLineId(from Table1) marked with underLine. How can I achieve this?
Thanks
You can use this overload of SelectMany to achieve this:-
DataSourceResult dsrResult = pclr.Get()
.SelectMany(p => p.PostCalculationLineProducts,
(PostCalculationLineProductObj,PostCalculationLineObj) =>
new { PostCalculationLineProductObj,PostCalculationLineObj })
.Where(c => c.PostCalculationLineProductObj.Product.ProductType.Id == 1
&& c.PostCalculationLineProductObj.DeletedOn == null)
.Select(c => new HourGridViewModel()
{
Id = c.PostCalculationLineProductObj.Id,
Date = c.PostCalculationLineProductObj.From,
//Other Columns here
PostCalculationLineId = c.PostCalculationLineObj.Id
};
This will flatten the PostCalculationLineProducts list and returns the flattened list combined with each PostCalculationLine element.

Categories

Resources