2

I'm doing some work on an old Winforms grid and i have two Models that i am trying to flatten and assign to a DataGridView.

Here are my sample models.

public class StockItem
{
     public string StockName { get; set; }
     public int Id { get; set; }
     public List<Warehouse> Warehouses { get; set; }
}

public class Warehouse
{
     public string WarehouseName { get; set; }
     public int Id { get; set; }
}

The data works in a way that a warehouse must first be created and then assigned to each StockItem. A StockItem may have all the warehouses or may only have one.

I need to flatten the data so that the grid shows the StockName and then all the associated warehouses for the stock item.

Example

StockCode1      Warehouse1   Warehouse2   Warehouse3
StockCode2      Warehouse1   Warehouse2   
StockCode2      Warehouse1                Warehouse3   

I've attempted to do this via a Linq query but can only get a record per StockItem\Warehouse.

PiotrWolkowski
  • 8,408
  • 6
  • 48
  • 68
user2859298
  • 1,373
  • 3
  • 13
  • 28
  • Perhaps this will help? http://stackoverflow.com/questions/6428940/how-to-flatten-nested-objects-with-linq-expression – Scott Perham Sep 13 '16 at 16:06
  • 2
    This is not really flattening, but crosstab / pivoting – Ivan Stoev Sep 13 '16 at 16:17
  • 1
    What would the resulting data type be? Is there a maximum number of possible warehouses that can be associated with a `StockItem`? I am not familiar with the WinForms `DataGridView` so there may be something I am missing but wouldn't you have to bind it to a collection of strongly typed objects? It seems that with a variable number of warehouses you could not do that. – Jason Boyd Sep 13 '16 at 16:49
  • Can you create a piece of code and share how you are constructing the data? But generally speaking you should be able to get the data using LINQ query. – Versatile Sep 13 '16 at 21:25

4 Answers4

1

You can achieve it by creating a DataTable that yon can easily use as a source for the gridview. First add all columns and then for each stock add the warehouses:

var warehouseNames = 
    stocks
    .SelectMany(x => x.Warehouses.Select(y => y.WarehouseName)).Distinct();

var dt = new DataTable();
dt.Columns.Add("StockCode");

foreach (var name in warehouseNames)
{
    dt.Columns.Add(name);
}

foreach (var stock in stocks)
{
    var row = dt.NewRow();
    row["StockCode"] = stock.Id;
    foreach (var warehouse in stock.Warehouses)
    {
        row[warehouse.WarehouseName] = warehouse.Id;
    }
    dt.Rows.Add(row);
}

Warehouses

t3chb0t
  • 16,340
  • 13
  • 78
  • 118
0

I do not recommend it, but you can use dynamic objects to create objects with the shape you want. Doing this is not a common C# pattern. This is more common in languages like Python or Javascript.

C# is a strongly typed language and venturing into the world of dynamic objects should only be considered when absolutely necessary (think parsing a json blob). I strongly consider you reevaluate what you need to do and approach it from a different angle.

David Garwin
  • 401
  • 3
  • 6
0

Something like this:

var availableWarehouses = new [] { 
    new Warehouse { 
        WarehouseName = "Warehouse1",
        Id = 1
    },
    new Warehouse {
        WarehouseName = "Warehouse2",
        Id = 2
    },
    new Warehouse {
        WarehouseName = "Warehouse3",
        Id = 3
    }
};

var stocks = new [] {
    new StockItem {
        StockName = "StockCode1",
        Id = 1,
        Warehouses = new List<Warehouse> { availableWarehouses[0], availableWarehouses[1], availableWarehouses[2] }
    },
    new StockItem {
        StockName = "StockCode2",
        Id = 2,
        Warehouses = new List<Warehouse> { availableWarehouses[0], availableWarehouses[1] }
    },
    new StockItem {
        StockName = "StockCode3",
        Id = 3,
        Warehouses = new List<Warehouse> { availableWarehouses[0], availableWarehouses[2] }
    }
};

var flatten = stocks.Select(item => new {
        StockName = item.StockName,
        WarehousesNames = availableWarehouses.Select(warehouse => item.Warehouses.Contains(warehouse) ? warehouse.WarehouseName : "          ")
            .Aggregate((current, next) => current + "\t" + next)
    });

foreach(var item in flatten) {
    Console.WriteLine("{0}\t{1}", item.StockName, item.WarehousesNames);
}
-1

That should give you what you need:

var flattened = stockItems
    .Select(x => new { 
                 StockName = x.StockName, 
                 WarehouseNames = x.Warehouses
                                  .Select(y => y.WarehouseName)
                                  .ToList() })
    .ToList();

It will result in a collection of items that contain StockName and a list of WarehouseName strings. ToList added to enumerate the query.

For these sample data:

List<StockItem> stockItems = new List<StockItem>
{
    new StockItem
    {
        StockName ="A",
        Id = 1,
        Warehouses = new List<Warehouse>
        {
            new Warehouse { Id = 1, WarehouseName = "x" },
            new Warehouse { Id = 2, WarehouseName = "y" }
        }
    },
    new StockItem
    {
        StockName = "B",
        Id = 2,
        Warehouses = new List<Warehouse>
        {
            new Warehouse { Id = 3, WarehouseName = "z" },
            new Warehouse { Id = 4, WarehouseName = "w" }
        }
    }
};

I've got the following result:

enter image description here

PiotrWolkowski
  • 8,408
  • 6
  • 48
  • 68