1

I would like to retrieve a List of elements as part of the compound key of the group by. But so far I have been unable to write the query to do it.

We are showing the total stock per RawMaterial (bellow more details for the model), and we would like to list alongside the suppliers for each RawMaterial. I tried this query:

var list = this.Stocks.Where(x => x.StockType == StockTypeEnum.RawMaterialEntered)
.GroupBy(i => new { RawMaterialId = i.RawMaterial.Id, RawMaterial = i.RawMaterial.Name, 
                    Suppliers = i.RawMaterial.Suppliers.Select(j=> new { Id= j.Supplier.Id, Name = j.Supplier.Name}) })
.Select(g => new StockSummary
{
   TotalAmount = g.Sum(i => i.Amount),
   ComponentId = g.Key.RawMaterialId,                   
   Component = g.Key.RawMaterial,
   Suppliers = g.Key.Suppliers.Select(h=> new StockItemModel(){ Id = h.Id, Name = h.Name} ).ToList()
})
.OrderByDescending(g => g.Component).ToList();

It builds but when I try to run it, I get this exception:

A first chance exception of type 'System.NotImplementedException' occurred in NHibernate.dll

We tried different approaches, but nothing worked. Is it possible to obtain the desired result with just one query?

I have this model:

public class Stock
{
    public RawMaterial RawMaterial{ get; set; }
    public int Amount { get; set; }
}

public class RawMaterial
{
    public int Id{ get;set; }
    public string Name { get;set; }
    public IList<SupplierInfo> SupplierInfos{ get;set; }
}

public class SupplierInfo
{
    public int Id{ get;set; }   
    public Supplier Supplier { get;set; }
}

public class Supplier
{
    public int Id { get;set; }
    public string Name { get; set; } 
}

Originally we had a grid where we show each the total stock available for a raw material. We retrieve that information with this query:

var list = this.Stocks.Where(x => x.StockType == StockTypeEnum.RawMaterialEntered)
  .GroupBy(i => new { RawMaterialId = i.RawMaterial.Id, RawMaterial = i.RawMaterial.Name })
  .Select(g => new StockSummary
               {
                   TotalAmount = g.Sum(i => i.Amount),
                   ComponentId = g.Key.RawMaterialId,                   
                   Component = g.Key.RawMaterial,                   
               })
               .OrderByDescending(g => g.Component).ToList();

This data is shown in a table similar to this:

Current Stocks Total

And now, as mentioned above, we would like to show a list of the suppliers that provided said components. Is it possible to do it all in one query?

Desired table

Dzyann
  • 5,062
  • 11
  • 63
  • 95
  • Are you looking for a hibernate solution or a sql server solution? I couldn't begin to help you with hibernate but in sql server you can use stuff and for xml this easily. Take a look here. http://stackoverflow.com/questions/451415/simulating-group-concat-mysql-function-in-microsoft-sql-server-2005 – Sean Lange Feb 17 '17 at 16:04
  • Are you grouping the data by a list in the `GroupBy` call? As far as I know, neither NHibernate nor EF support such operation. – Zoran Horvat Feb 17 '17 at 16:06
  • I'd say no, you can't do it in one query. Get all the results ou need on a first query, load them all from the database and group them in your application. I'm not sure what you want to do is even possible in SQL. – Irwene Feb 17 '17 at 16:07
  • @Sidewinder94 Grouping in the application would rely on manual selection of group members, because it would ask for collection comparison. I never saw an attempt like in this question. – Zoran Horvat Feb 17 '17 at 16:08
  • @ZoranHorvat Nope, it is not present in the question, but that might be a solution to look at if the OP really need his list grouping. – Irwene Feb 17 '17 at 16:10
  • @ZoranHorvat I dont really want to group by the list, I just want to be able to retrieve the suppliers for the RawMaterials. – Dzyann Feb 17 '17 at 16:25
  • But in the `GroupBy` call you have this key selector: `Suppliers = i.RawMaterial.Suppliers.Select(j=> new { Id= j.Supplier.Id, Name = j.Supplier.Name})` - one of the selectors is the list of objects. As far as I know, this won't work under any circumstances. – Zoran Horvat Feb 17 '17 at 16:31
  • @Dzyann Your second query, which shows original code, does not try to group by a list of subobjects and it works, right? So this selector has caused the failure when it was added. Am I right, or I'm getting something wrong? – Zoran Horvat Feb 17 '17 at 16:34
  • @ZoranHorvat yes you are right, the query where I add the collection is an example. Even with the collection the groups are unique, because there will be only one result for RawMaterial, Suppliers List. – Dzyann Feb 17 '17 at 16:43

2 Answers2

1

You may try:

var list = this.Stocks
    .Where(x => x.StockType == StockTypeEnum.RawMaterialEntered)
    .GroupBy(i => 
        new
        {
            RawMaterialId = i.RawMaterial.Id,
            RawMaterial = i.RawMaterial.Name
        })
    .Select(g => new StockSummary
    {
        TotalAmount = g.Sum(i => i.Amount),
        ComponentId = g.Key.RawMaterialId,                   
        Component = g.Key.RawMaterial,
        Suppliers = g
            .SelectMany(s => 
                s.RawMaterial.SupplierInfos.Select(j => 
                    new { Id = j.Supplier.Id, Name = j.Supplier.Name }))
            .Distinct()
            .Select(h => new StockItemModel() { Id = h.Id, Name = h.Name })
            .ToList()
    })
    .OrderByDescending(g => g.Component).ToList();

I think it will still match your requirements, and it avoids trying to group by a list. But I am not sure it can be executed by NHibernate.

You may have it easier by avoiding the group by: why not use RawMaterial as root of your query? This requires to add the Stocks collection on RawMaterial (or use sub-queries).

Guessing this.RawMaterials exists and is a IQueryable<RawMaterial>, it would be something like:

var list = this.RawMaterials
    .Where(rm =>
        rm.Stocks.Any(s => s.StockType == StockTypeEnum.RawMaterialEntered))
    .Select(rm => new StockSummary
    {
        TotalAmount = rm.Stocks
            .Where(s => s.StockType == StockTypeEnum.RawMaterialEntered)
            .Sum(s => s.Amount),
        ComponentId = rm.Id,                   
        Component = rm.Name,
        Suppliers = rm.SupplierInfos
            .Select(si =>
                new StockItemModel()
                {
                    Id = si.Supplier.Id,
                    Name = si.Supplier.Name
                })
            .ToList()
    })
    .OrderByDescending(ss => ss.Component).ToList();

I know this kind of queries are handled by EF, but I have not tried such queries with NHibernate. (NHibernate is much better at loading children collection than EF thanks to lazy-loading batching (if enabled). So I do not use with NHibernate the kind of projection you are trying.)

Frédéric
  • 9,364
  • 3
  • 62
  • 112
0

So first of all, your code is incomplete or you did not paste everything here. For example StockType and StockEnumType are missing and I removed those from my answer for simplicity purpose rather than trying to replicate what you did not show. Anyways, it works fine for me if I use string.Join(",", ...) although it does show Id and Name, but I think you shouldn't have no problem adjusting output by modifying your LINQ statement:

        var list = stocks
            .GroupBy(i => new { RawMaterialId = i.RawMaterial.Id, RawMaterial = i.RawMaterial.Name,
                Suppliers = i.RawMaterial.SupplierInfos.Select(j => new { Id = j.Supplier.Id, Name = j.Supplier.Name })
            })
            .Select(g => new
            {
                TotalAmount = g.Sum(i => i.Amount),
                ComponentId = g.Key.RawMaterialId,
                Component = g.Key.RawMaterial,
                Suppliers = string.Join(",", g.Key.Suppliers.Select(h => new { Id = h.Id, Name = h.Name }).ToList())
            })
            .OrderByDescending(g => g.Component).ToList();

And the output is with my test data:

1 Name3 30.00 ( Id = 1, Name = Sup1),( Id = 3, Name = Sup3)

2 Name2 20.00 ( Id = 1, Name = Sup1) ...

Community
  • 1
  • 1
Renats Stozkovs
  • 2,549
  • 10
  • 22
  • 26
  • Yes, I forgot to add the StockType, but those are really not relevant to the question. Also are you using Nhibernate? Or is completely an in memory test? – Dzyann Feb 17 '17 at 17:25
  • This is in-memory, but it doesn't matter if your data model is right. LINQ is an abstraction over your data source that allows you to manipulate the data regardless of whether it is in-memory, SQL, no-SQL etc – Renats Stozkovs Feb 17 '17 at 17:36
  • It does matter because the Linq To SQL is translated to SQL, and I am getting a Nhibernate Exception. I want to write the Linq so It generates the least amount of queries in SQL. – Dzyann Feb 17 '17 at 18:55
  • @Dzyann So did you try the code above? Also please provide some stack trace. This could also be solved by doing `this.Stocks.ToList().Where...` – Renats Stozkovs Feb 17 '17 at 19:57
  • Your code is essentially the same as mine, I did try it though. If you do Stocks.ToList() it essentially brings everything to memory. – Dzyann Feb 17 '17 at 20:03
  • @Dzyann I see. If you want minimal DB impact I would suggest creating a stored proc and having NHibernate work against SP. It will still be multi-step process with SP because you can't group by both MaterialID and a list of Suppliers in T-SQL. – Renats Stozkovs Feb 17 '17 at 20:12