Group by and count on list lambda c# - c#

I have 2 tables in the database Department and SubDepartment. Each department can or cannot have multiple sub departments. I am fetching the data from both the tables combined in the form of following class.
This class have more properties as well from both the tables.
public class Department
{
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
public int SubDepartmentId { get; set; }
public string SubDepartmentName { get; set; }
}
For example data can be Department as Science which will have multiple Sub Departments as Physics, Chemistry. Also Department can be English which will have one sub department as English only.
Now from the data in the form of List of Departments collection fetched from the DB I have to fetch those departments which have multiple sub departments.
Below is the code:
List<Department> departments = new List<Department>();
// Here fetching deptids which are having multi sub deptids
var multidepartmentIds = departments .GroupBy(x => x.DepartmentId)
.Where(x => x.Count() > 1)
.Select(x => new { DepartmentId = x.Key }).ToList();
// Here getting the entire data/item for each dept and subdept
var finalDeptData = departments.Where(x => multidepartmentIds.Any(y => y.DepartmentId == x.DepartmentId)).ToList();
Is there a better way to implement this?
I have thought of one more way as well where in a single lambda statement after Count we can have an anonymous type with all the class properties but the problem is that I have to return this collection to the caller method and then in that case I have to return the anonymous collection as type object which will require changes in the caller method as well.
Any help??

Looking at your code, IMO you are trying to get a list of all sub-departments which their department has multiple sub-department. In this case, you can do this:
List<Department> departments = new List<Department>();
// Here fetching deptids which are having multi sub deptids
var multidepartmentIds = departments
.GroupBy(x => x.DepartmentId)
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.ToList();

There is no need to create an anonymous type to hold a single value. Since you are looking for matches in the collection of IDs, use a HashSet to hold them:
// Here fetching deptids which are having multi sub deptids
var multidepartmentIds = departments.GroupBy(d => d.DepartmentId)
.Where(dg => dg.Count() > 1)
.Select(dg => dg.Key)
.ToHashSet();
// Here getting the entire data/item for each dept and subdept
var finalDeptData = departments.Where(d => multidepartmentIds.Contains(d.DepartmentId)).ToList();
However, since all you really want is all the departments where there are multiple occurrences, you can do that in one statement:
// find all departments with multiple sub deptids and return them
var finalDeptData2 = departments.GroupBy(d => d.DepartmentId)
.Where(dg => dg.Count() > 1)
.SelectMany(dg => dg)
.ToList();
I like to have an extension method for this:
public static class IEnumerableExt {
public static IEnumerable<T> DuplicatesBy<T,TKey>(this IEnumerable<T> src, Func<T,TKey> keyFn, IEqualityComparer<TKey> comparer = null)
=> src.GroupBy(s => keyFn(s), comparer).Where(sg => sg.Count() > 1).SelectMany(sg => sg);
}
Which you can use like:
var findDeptData3 = departments.DuplicatesBy(d => d.DepartmentId).ToList();

Related

Only primitive types do I need to cast?

I am trying to use anonymous types in Entity Framework, but I am getting an error about
Unable to create a constant value
MinQty and MaxQty are int so I don't know if I need to add to Convert.ToInt32?
Unable to create a constant value of type 'Anonymous type'. Only primitive types or enumeration types are supported in this context.
This builds a list object
var listOfLicense = (from l in db.License
select new
{
l.ProductId,
l.MinLicense,
l.MaxLicense
}).tolist();
This is the larger EF object where I am getting the error am I missing a casting?
var ShoppingCart = (from sc in db.ShoppingCarts
Select new model.Shoppingchart{
ShoppingCartId= sc.Id,
MinQty = (int)listOfLicense
.Where(mt => (int)mt.ProductId == sc.ProductId)
.Select(mt => (int)mt.MinLicense)
.Min(mt => mt.Value),
MaxQty = (int)listOfLicense
.Where(mt => (int)mt.ProductId == p.ProductId)
.Select(mt =>(int) mt.MaxQty)
.Max(mt => mt.Value)}.tolist();
This builds a list object
var listOfLicense = (from l in db.License
select new
{
l.ProductId,
l.MinLicense,
l.MaxLicense
})
The above example does not build a list of objects. It builds a query to return objects of that anonymous type.
This builds an in-memory list of objects of that type:
var listOfLicense = (from l in db.License
select new
{
l.ProductId,
l.MinLicense,
l.MaxLicense
}).ToList();
Using .ToList() here will execute the query and return a materialized list of the anonymous types. From there, your code may work as expected without the exception. However, this is effectively loading the 3 columns from all rows in your database table, which may be a problem as the system matures and rows are added.
The error you are getting isn't a casting issue, it is a translation issue. Because your initial query is still just an EF Query, (IQueryable) any further querying against it will need to conform to EF limitations. EF has to be able to translate what your expressions are trying to select back into SQL. In your case, what your real code is trying to do is breaking those rules.
Generally it is better to let EF work with the IQueryable rather than materializing an entire list to memory. Though to accomplish that we'd need to either see the real code, or a minimum reproducible example.
This code:
MinQty = (int)listOfLicense
.Where(mt => (int)mt.ParentProductId == p.ProductId)
.Select(mt => (int)mt.MinLicense)
.Min(mt => mt.Value),
... does not fit with the above anonymous type as there is no correlation between what mt.ParentProductId is in relation to the anonymous type. (p seems to be associated with that type, not mt so there looks to be a lot of Query code missing from your example.)
Edit: based on your updated example:
var ShoppingCart = (from sc in db.ShoppingCarts
Select new model.Shoppingchart{
ShoppingCartId= sc.Id,
MinQty = (int)listOfLicense
.Where(mt => (int)mt.ProductId == sc.ProductId)
.Select(mt => (int)mt.MinLicense)
.Min(mt => mt.Value),
MaxQty = (int)listOfLicense
.Where(mt => (int)mt.ProductId == p.ProductId)
.Select(mt =>(int) mt.MaxQty)
.Max(mt => mt.Value)}.ToList();
It may be possible to build something like this into a single query expression depending on the relationships between ShoppingCart, Product, and Licence. It almost looks like "Licence" really refers to a "Product" which contains a min and max quantity that you're interested in.
Assuming a structure like:
public class Product
{
[Key]
public int ProductId { get; set; }
public int MinQuantity { get; set; }
public int MaxQuantity { get; set; }
// ...
}
// Here lies a question on how your shopping cart to product relationship is mapped. I've laid out a many-to-many relationship using ShoppingCartItems
public class ShoppingCart
{
[Key]
public int ShoppingCartId { get; set; }
// ...
public virtual ICollection<ShoppingCartItem> ShoppingCartItems { get; set; } = new List<ShoppingCartItem>();
}
public class ShoppingCartItem
{
[Key, Column(0), ForeignKey("ShoppingCart")]
public int ShoppingCartId { get; set; }
public virtual ShoppingCart ShoppingCart{ get; set; }
[Key, Column(1), ForeignKey("Product")]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
With something like this, to get shopping carts with their product min and max quantities:
var shoppingCarts = db.ShoppingCarts
.Select(sc => new model.ShoppingCart
{
ShoppingCartId = sc.Id,
Products = sc.ShoppingCartItems
.Select(sci => new model.Product
{
ProductId = sci.ProductId,
MinQuantity = sci.MinQuantity,
MaxQuantity = sci.MaxQuantity
}).ToList()
}).ToList();
This would provide a list of Shopping Carts with each containing a list of products with their respective min/max quantities.
If you also wanted a Lowest min quantity and highest max quantity across all products in a cart:
var shoppingCarts = db.ShoppingCarts
.Select(sc => new model.ShoppingCart
{
ShoppingCartId = sc.Id,
Products = sc.ShoppingCartItems
.Select(sci => new model.Product
{
ProductId = sci.ProductId,
MinQuantity = sci.MinQuantity,
MaxQuantity = sci.MaxQuantity
}).ToList(),
OverallMinQuantity = sc.ShoppingCartItems
.Min(sci => sci.MinQuantity),
OverallMaxQuantity = sc.ShoppingCartItems
.Max(sci => sci.MaxQuantity),
}).ToList();
Though I'm not sure how practical a figure like that might be in relation to a shopping cart structure. In any case, with navigation properties set up for the relationship between your entities, EF should be perfectly capable of building an IQueryable query for the data you want to retrieve without resorting to pre-fetching lists. One issue with pre-fetching and re-introducing those lists into further queries is that there will be a maximum # of rows that EF can handle. Like with SQL IN clauses, there is a maximum # of items that can be parsed from a set.
In any case it sounds like it's provided you with some ideas to try and get to the figures you want.

loading related entities

We are using EF .NET core for our architecture and want to do a basic query. So all we are after is using both LINQ & EF with lazy loading switched off to select the parent, in this case stick item and some of the fields in the child objects. Then return them back into our strongly typed item.
Something like this.
var qry = _context.Set<stock>()
.Include(p => p.stockitem)
.Where(q => q.customer == accountNo)
.Select(r => new stock() {
customer = r.customer,
r.stockitem.select(s => new {id, s.id, s.name }})
.ToList();
So is it possible to do this? basically get hold of say just a couple of columns from our child object. Then have everything returned in the strongly typed object.
First create a model in which the selected data will be stored (This model is just an example):
public class MyNewCustomer
{
public string CustomerAccount { get; set; }
public Dictionary<int, string> Items { get; set; }
}
After that you can create a new object from your select:
var data = _context.Stocks
.Include(p => p.stockitem)
.Where(p => p.customer == accountNo)
.Select(p => new MyNewCustomer
{
CustomerAccount = p.customer,
Items = p.stockitem.ToDictionary(s => s.id, s => s.name)
}).ToList();
PS: I used Dictionary because you didn't provide the actual model. You can choose whatever kind of List<TObject> you want.

How can I make a more efficient Entity Framework LINQ call than a nested loop

Is there a more efficent way to write this LINQ statement?
Feature uses the same model as Product (as they are almost identical).
var products = db.Products.Where(q => q.IsFeature == false).ToList();
foreach (Product pr in products)
{
var features = db.Products.Where(q => q.ParentID == pr.ID).ToList();
foreach(Product feature in features)
{
pr.Features.Add(feature);
}
}
And the model:
public class Product
{
//START
public Product()
{
Features = new HashSet<Product>();
}
public int ID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsFeature { get; set; }
public int ParentID { get; set; }// If IsFeature is true we need to know its parent
//Nav Props
public ICollection<Product> Features { get; set; }// Features can be represented with the same model as product as they are almost identical
}
This does what I need ie a List of Product (Products), each with its own List of Feature (Features) but are there more elegant/efficient alternatives?
What you need is a join:
var productFeatures =
db.Products.Where(q => !q.IsFeature)
.Join(db.Products, p => p.ID, f => f.ParentId, (p, f) => new { product: p, features: f.ToList() })
.ToList();
For each object in db.Products.Where(q => !q.IsFeature) this will find each of the db.Products that have a ParentId equal to its ID field.
The results will be projected into a List() of anonymous types with the fields product (Product) and features (List<Product>)
If this operation has a complexity which rises to the point where the generated Linq's SQL becomes a hindrance or this business logic is done in multiple places; consider placing the logic in a stored procedure.
Once in the database simply map the results in EF which later can easily be consumed by the code. Hence the complexity is moved off of the client and directly on the database.
Steps (Database First)
Create Stored procedure.
Update the model from the database and choose the stored procedure.
Verify the resultant mappings in EF's Model Browser.
Consume the named stored procedure off of the data context.
I discuss the ins-and-outs of mapping stored procedures on my blog article Entity Framework Stored Procedure Instructions.
You're almost there you can use Addrange method of List collection:
var products = db.Products.Where(q => !q.IsFeature).ToList();
foreach(Product pr in db.Products)
if(!pr.IsFeature) pr.Features.AddRange(db.Products.Where(q => q.ParentID == pr.ID));
P.S. other idea is to extract features sort them, convert to array and use for loop :
var features = db.Products.Where(q => q.IsFeature).OrderBy(f => f.ParentID).ToArray();
p = db.Products.First(p => p.ID == features[0].ParentID);
for(int i = 0; i < features.Length; i++ )
{
if(i > 0 && features[i - 1].ParentID != features[i].ParentID)
p = db.Products.First(p => p.ID == features[i].ParentID);
p.Features.Add(features[i]);
}
You need to use GroupJoin for this. This is exactly what it's designed for.
var products = db.Products.Where(p => !p.IsFeature)
.GroupJoin(db.Products, p => p.ID, q => q.ParentID, (p, eq) => {
p.Features = eq.ToList();
return p;
})
.ToList();
This code is untested, but should work.

Orchard CMS ContentQuery with N-to-N relation

In my project I've implemented N-to-N relation between records using this tutorial on OrchardProject web-site. I have 2 parts: MaterialPart & CategoryPart and association record.
Material part
public class MaterialPartRecord : ContentPartRecord {
public MaterialPartRecord() {
Categories = new List<ContentMaterialCategoryRecord>();
}
}
public class MaterialPart : ContentPart<MaterialPartRecord> {
public IEnumerable<CategoryPartRecord> Categories {
get { return Record.Categories.Select(cmcr => cmcr.CategoryPartRecord); }
}
}
CategoryPartRecord
public class CategoryPartRecord : ContentPartRecord {
...
}
public class CategoryPart : ContentPart<CategoryPartRecord> {
...
}
association record:
public class ContentMaterialCategoryRecord {
public virtual int Id { get; set; }
public virtual MaterialPartRecord MaterialPartRecord { get; set; }
public virtual CategoryPartRecord CategoryPartRecord { get; set; }
}
Now I need to select MaterialItems which are linked to certain category. So far I have this method to extract them. It works but I'm not sure that it is correct way to do this.
public IEnumerable<MaterialPart> GetMaterialsByCategory(int catId) {
var cs = new CategoriesService(_oServices);
CategoryPartRecord cat = cs.GetItem(catId).Record;
return _oServices.ContentManager
.Query(VersionOptions.Latest, _contentType)
.Join<CommonPartRecord>()
.OrderByDescending(cpr => cpr.PublishedUtc);
.List()
.Where(ci => ci.IsPublished())
.Select(ci => ci.As<MaterialPart>())
.Where(mp => mp.Categories.Contains(cat)); // < ---- ?
}
So my question is: what is correct way to select materials for required category, which produces optimal SQL query, as we simply need to inner join associated record table with required CategoryPartRecord_Id field value.
thaks!
In case, of M : N with pairing object, we can use QueryOver and subquery. The biggest benefit would be, that we recieve the plain set of material Items, which we can use for paging (Take(), Skip())
var session = ... // get curretn session
CategoryPartRecord category = null;
ContentMaterialCategoryRecord pair = null;
MaterialPartRecord material = null;
var subquery = QueryOver.Of<ContentMaterialCategoryRecord>(() => pair)
// now we will join Categories to be able to filter whatever property
.JoinQueryOver(() => pair.CategoryPartRecord, () => category)
// here is the filter
// there could be IN, >= <= ...
.Where(() => category.ID == 1)
// or
.WhereRestrictionOn(c => c.category.ID).IsIn(new[] {1, 2, 3})
...
// now we will return IDs of the Material we are interested in
.Select(x => pair.MaterialPartRecord.Id);
// finally the clean query over the Materials...
var listOfUsers = session.QueryOver<MaterialPartRecord>(() => material )
.WithSubquery
.WhereProperty(() => material.Id)
.In(subquery)
// paging
.Take(10)
.Skip(10)
.List<MaterialPartRecord>();
So, this will produce the most effective SQL Script, with one subselect, and clean select from material table
NOTE: similar stuff could be done even with LINQ. But QueryOver is NHibernate most native way I'd say. Anyhow, the principe - subquery to filter by category, and main query to load materials will remain the same. Only ONE SQL Select call

Nhibernate QueryOver - Performing a Has All for a Many-to-Many relationship

To explain my problem more easily I will create the following fictional example, illustrating a very basic many-to-many relationship. A Car can have many Parts, and a Part can belong to many Cars.
DB SCHEMA:
CAR_TABLE
---------
CarId
ModelName
CAR_PARTS_TABLE
---------------
CarId
PartId
PARTS_TABLE
-----------
PartId
PartName
CLASSES:
public class Car
{
public int CarId {get;set;}
public string Name {get;set;}
public IEnumerable<Part> Parts {get;set;}
}
public class Part
{
public int PartId {get;set;}
public string Name {get;set}
}
Using this very simple model I would like to get any cars that have all the parts assigned to them from a list of parts I am searching on.
So say I have an array of PartIds:
var partIds = new [] { 1, 3, 10};
I want to mimic the following c# code in terms of a database call:
var allCars = /* code to retrieve all cars */
var results = new List<Car>();
foreach (var car in allCars)
{
var containsAllParts = true;
foreach (var carPart in car.Parts)
{
if (false == partIds.Contains(carPart.PartId))
{
containsAllParts = false;
break;
}
}
if (containsAllParts)
{
results.Add(car);
}
}
return results;
To be clear: I want to get the Cars that have ALL of the Parts specified from the partIds array.
I have the following query, which is REALLY inefficient as it creates a subquery for each id within the partIds array and then does an IsIn query on each of their results. I am desperate to find a much more efficient manner to execute this query.
Car carAlias = null;
Part partAlias = null;
var searchCriteria = session.QueryOver<Car>(() => carAlias);
foreach (var partId in partIds)
{
var carsWithPartCriteria = QueryOver.Of<Car>(() => carAlias)
.JoinAlias(() => carAlias.Parts, () => partAlias)
.Where(() => partAlias.PartId == partId)
.Select(Projections.Distinct(Projections.Id()));
searchCriteria = searchCriteria
.And(Subqueries.WhereProperty(() => carAlias.Id).In(carsWithPartCriteria));
}
var results = searchCriteria.List<Car>();
Is there a decent way to execute this sort of query using NHibernate?
This should be exactly what you want...
Part part = null;
Car car = null;
var qoParts = QueryOver.Of<Part>(() => part)
.WhereRestrictionOn(x => x.PartId).IsIn(partIds)
.JoinQueryOver(x => x.Cars, () => car)
.Where(Restrictions.Eq(Projections.Count(() => car.CarId), partIds.Length))
.Select(Projections.Group(() => car.CarId));
Part partAlias=null;
Session.QueryOver<Car>().JoinQueryOver(x=>x.Parts,()=>partAlias)
.WhereRestrictionOn(()=>partAlias.Id).IsIn(partIds) //partIds should be implement an ICollection
.List<Car>();
Hope that helps.
modified version of this answer
var cars = session.CreateQuery("SELECT c.id FROM Car c JOIN c.Parts p WHERE p.PartId IN(:parts) GROUP BY c HAVING COUNT(DISTINCT p) = :partsCount")
.SetParameterList("parts", partIds)
.SetInt32("partsCount", partIds.Count)
.List();
AFAIK the having clause is only available in HQL. You could modify the select/group by list if you want more columns. Another possible way would be too generate an HQL query that inner joins to each specific part id. I don't think that is possible with ICritiria either as it only lets you join to a property once.

Categories