How to flatten nested objects (LINQ) - c#

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.

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);
}

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.

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);
}

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:

Related

Converting list from one class to another

I'm trying to convert a group a complex list in C# (with Linq)
public class classA
{
public string Name { get; set; }
public int id { get; set; }
public string phone { get; set; }
public string interest { get; set; }
}
My first class is classA where it contains many list of elements like below.
List<classA> obj = new List<classA>();
obj.Add(new classA { id = 1, Name = "a", phone = "321", interest = "Playing" });
obj.Add(new classA { id = 1, Name = "2", phone = "123", interest="Tv" });
From this I need to group by using the id, So I've used Linq
var item = obj.GroupBy(a => a.id).Select(ac => ac.ToList()).ToList();
I've another class called classB which hold's the values others than id from the classA (where it'd be hold all subset of different attributes)
public class classB
{
public string Name { get; set; }
public string phone { get; set; }
public string interest { get; set; }
}
My Final Class looks likes,
public class Final
{
public int id { get; set; }
public List<classB> details { get; set; }
public Final()
{
details = new List<classB>();
}
}
My requirements are, after grouping the classA based on id, I need to convert that into my final class.
So I did like below,
public static void Main()
{
Console.WriteLine("Hello World");
List<classA> obj = new List<classA>();
obj.Add(new classA { id = 1, Name = "a", phone = "321", interest = "Playing" });
obj.Add(new classA { id = 1, Name = "b", phone = "123", interest = "Tv" });
obj.Add(new classA { id = 2, Name = "c", phone = "12322", interest = "Tv" });
obj.Add(new classA { id = 3, Name = "d", phone = "12333", interest = "Tv" });
var item = obj.GroupBy(a => a.id).Select(ac => ac.ToList()).ToList();
List<Final> finalobjList = new List<Final>();
foreach (var report in item)
{
Final finalObj = new Final();
foreach (var result in report)
{
finalObj.id = result.id;
}
var data = report.Select(x => new classB { Name = x.Name, phone = x.phone, interest = x.interest }).ToList();
finalObj.details = data;
finalobjList.Add(finalObj);
}
Console.WriteLine(finalobjList.Count());
}
I believe there is another easy way to achieve this using linq without using foreach multiple times
Appreciate your help!
You should be able to use your existing code except when you do your Select, select a new Final and use the group's Key for the Id, and convert the ac.ToList to a list of ClassB for the Details:
var item = obj
.GroupBy(a => a.id)
.Select(ac =>
new Final
{
Id = ac.Key,
Details = ac.Select(a =>
new classB {interest = a.interest, phone = a.phone, Name = a.Name})
.ToList()
});
var finalobjList = obj.GroupBy(a => a.id).Select(x => new Final() { id = x.Key, details = x.Select(y => new classB() { Name = y.Name }).ToList() } ).ToList();
(Code only answer - please dont hate me)
var items = (from a in obj
group new classB {Name = a.Name, phone = a.phone, interest = a.interest} by a.id into aa
select new Final { id = aa.Key, B= aa.ToList()}).ToList();

Best approach to compare if one list is subset of another in C#

I have the below two classes:
public class FirstInner
{
public int Id { get; set; }
public string Type { get; set; }
public string RoleId { get; set; }
}
public class SecondInner
{
public int Id { get; set; }
public string Type { get; set; }
}
Again, there are lists of those types inside the below two classes:
public class FirstOuter
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public List<FirstInner> Inners { get; set; }
}
public class SecondOuter
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondInner> Inners { get; set; }
}
Now, I have list of FirstOuter and SecondOuter. I need to check if FirstOuter list is a subset of SecondOuter list.
Please note:
The names of the classes cannot be changed as they are from different systems.
Some additional properties are present in FirstOuter but not in SecondOuter. When comparing subset, we can ignore their presence in SecondOuter.
No.2 is true for FirstInner and SecondInner as well.
List items can be in any order---FirstOuterList[1] could be found in SecondOuterList[3], based on Id, but inside that again need to compare that FirstOuterList[1].FirstInner[3], could be found in SecondOuterList[3].SecondInner[2], based on Id.
I tried Intersect, but that is failing as the property names are mismatching. Another solution I have is doing the crude for each iteration, which I want to avoid.
Should I convert the SecondOuter list to FirstOuter list, ignoring the additional properties?
Basically, here is a test data:
var firstInnerList = new List<FirstInner>();
firstInnerList.Add(new FirstInner
{
Id = 1,
Type = "xx",
RoleId = "5"
});
var secondInnerList = new List<SecondInner>();
secondInner.Add(new SecondInner
{
Id = 1,
Type = "xx"
});
var firstOuter = new FirstOuter
{
Id = 1,
Name = "John",
Title = "Cena",
Inners = firstInnerList
}
var secondOuter = new SecondOuter
{
Id = 1,
Name = "John",
Inners = secondInnerList,
}
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
Need to check if firstOuterList is part of secondOuterList (ignoring the additional properties).
So the foreach way that I have is:
foreach (var item in firstOuterList)
{
var secondItem = secondOuterList.Find(so => so.Id == item.Id);
//if secondItem is null->throw exception
if (item.Name == secondItem.Name)
{
foreach (var firstInnerItem in item.Inners)
{
var secondInnerItem = secondItem.Inners.Find(sI => sI.Id == firstInnerItem.Id);
//if secondInnerItem is null,throw exception
if (firstInnerItem.Type != secondInnerItem.Type)
{
//throw exception
}
}
}
else
{
//throw exception
}
}
//move with normal flow
Please let me know if there is any better approach.
First, do the join of firstOuterList and secondOuterList
bool isSubset = false;
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
var jointOuterList = firstOuterList.Join(
secondOuterList,
p => new { p.Id, p.Name },
m => new { m.Id, m.Name },
(p, m) => new { FOuterList = p, SOuterList = m }
);
if(jointOuterList.Count != firstOuterList.Count)
{
isSubset = false;
return;
}
foreach(var item in jointOuterList)
{
var jointInnerList = item.firstInnerList.Join(
item.firstInnerList,
p => new { p.Id, p.Type },
m => new { m.Id, m.type },
(p, m) => p.Id
);
if(jointInnerList.Count != item.firstInnerList.Count)
{
isSubset = false;
return;
}
}
Note: I am assuming Id is unique in its outer lists. It means there will not be multiple entries with same id in a list. If no, then we need to use group by in above query
I think to break the question down..
We have two sets of Ids, the Inners and the Outers.
We have two instances of those sets, the Firsts and the Seconds.
We want Second's inner Ids to be a subset of First's inner Ids.
We want Second's outer Ids to be a subset of First's outer Ids.
If that's the case, these are a couple of working test cases:
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsTrue(isInnerSubset);
Assert.IsTrue(isOuterSubset);
}
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreNotSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
firstInnerIds.Clear();
firstInnerIds.Add(5);
firstOuterIds.Clear();
firstOuterIds.Add(5);
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsFalse(isInnerSubset);
Assert.IsFalse(isOuterSubset);
}
private List<FirstOuter> GetFirstOuterList() { ... }
private List<SecondOuter> GetSecondOuterList() { ... }

Need help querying through 3 classes

I have 3 classes that are defined like this:
class Customer
{
public string Name;
public string City;
public Order[] Orders;
}
class Order
{
public int Quantity;
public Product Product;
}
class Product
{
public string ProdName;
public decimal Price;
}
And I want to use LINQ in C# to print out the names that bought a specific product which in this case is 'ProdName'. I can't find a solution in order to go through all these 3 classes that could give me the name based on the product name.
I have tried something like this but it seems it doesn;t work:
var query = from c in customers where c.Order[0].Product.ProdName.Contains("Milk")
select c.Name;
foreach(var item in query)
{
Console.WriteLine(item);
}
This is how I set up the values for each class:
static public List<Customer> GetCustomerList()
{
var customerList = new List<Customer>
{
new Customer {Name = "Johny", City = "London", Orders = new Order[3] },
new Customer {Name = "Morgan", City = "Copenhagen", Orders = new Order[4]},
new Customer {Name = "Rasmus", City = "Amsterdam", Orders = new Order[1] }
};
return customerList;
}
static public List<Order> GetOrderList()
{
var orderList = new List<Order>
{
new Order { Quantity = 10, Product = new Product()},
new Order { Quantity = 5, Product = new Product()},
new Order { Quantity = 2, Product = new Product()}
};
return orderList;
}
static public List<Product> GetProductList()
{
var productList = new List<Product>
{
new Product { Name = "Cookie, bread", Price = 50 },
new Product { Name = "Cookie, Bread, Milk", Price = 85},
new Product { Name = "bags", Price = 38}
};
return productList;
}
static void Main(string[] args)
{
List<Customer> customers = GetCustomerList();
List<Order> orders = GetOrderList();
List<Product> products = GetProductList();
}
How can I linq all 3 classes together in order to get right result? any hints, please?
You need to build real, related, test data. Some usable fake data setup might look like:
// Create a single instance of each Product that could be used
var egg = new Product { Name = "Eggs", Price = 2.0 };
var bread = new Product { Name = "Bread", Price = 3.0 };
var fooBars = new Product { Name = "FooBars", Price = 2.5 };
var customerList = new List<Customer>
{
new Customer { Name = "Johny", City = "London", Orders = new List<Order>
{
new Order { Quantity = 3, Product = bread },
new Order { Quantity = 1, Product = egg },
new Order { Quantity = 2, Product = fooBars }
}},
new Customer { Name = "Morgan", City = "Copenhagen", Orders = new List<Order>
{
new Order { Quantity = 30, Product = bread }
}},
new Customer { Name = "Rasmus", City = "Amsterdam", Orders = new List<Order>
{
new Order { Quantity = 12, Product = fooBars }
}}
};
Please note that I used List<Order> instead of Order[], but you could switch it back. I also opted for a Name property in Product as you showed in your example code, but which doesn't match your class definition.
Now you can query. Let's see who bought bread:
var whoBoughtBread = customerList
.Where(c => c.Orders.Any(o => o.Product == bread))
.Select(c => c.Name);
Or
var whoBoughtBread2 = customerList
.Where(c => c.Orders.Any(o => o.Product.Name == "Bread"))
.Select(c => c.Name);
With the data structure you have one query would be:
customerList.Where(c => c.Orders.Any(o => o.Product.ProdName == prodName));
but as mentioned in the comments you have potentials for both a null collection and null values within the collection. I would highly recommend making sure you have non-null collections with non-null elements to avoid having to inject null-checking into your query. Otherwise you'll have to do sometihng like:
customerList.Where(c => c.Orders != null &&
c.Orders.Any(o => o != null &&
o.Product != null &&
o.Product.ProdName == prodName));
With C# 6's null-propagation operator (?.) you can shorten it a little:
customerList.Where(c => c.Orders != null &&
c.Orders.Any(o => o?.Product != null &&
o.Product.ProdName == prodName));

Compare and merge two List<T> into new List<T>

I'm trying to figure out how best to compare and merge two List<T> with a new List<T> being generated that compares multiple properties within each object.
class Account
{
public Account() { }
public string ID { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
List<Account> Mine = new List<Account>();
List<Account> Yours = new List<Account>();
List<Account> Ours = new List<Account>();
Account m1 = new Account(){ ID = null, Name = "C_First", Value = "joe" };
Account m2 = new Account(){ ID = null, Name = "C_Last", Value = "bloggs" };
Account m3 = new Account(){ ID = null, Name = "C_Car", Value = "ford" };
Mine.Add(m1);
Mine.Add(m2);
Mine.Add(m3);
Account y1 = new Account(){ ID = "1", Name = "C_First", Value = "john" };
Account y2 = new Account(){ ID = "2", Name = "C_Last", Value = "public" };
Yours.Add(y1);
Yours.Add(y2);
The resulting List<Account> Ours would have the following List<Account> objects:
{ ID = "1", Name = "C_First", Value = "joe" };
{ ID = "2", Name = "C_Last", Value = "bloggs" };
{ ID = null, Name = "C_Car", Value = "ford" };
I need to figure out how best to compare the ID and Value properties between both List<Account> objects where the List<Account> Yours ID takes precedence over the List<Account> Mine and the List<Account> Mine Value takes precedence over List<Account> Yours along with any object that's not in List<Account> Yours being added as well.
I've tried the following:
Ours = Mine.Except(Yours).ToList();
which results in List<Ours> being empty.
I've read this post Difference between two lists in which Jon Skeet mentions using a custom IEqualityComparer<T> to do what I need but I'm stuck on how to create an IEqualityComparer<T> that compares more than 1 property value.
Not sure if it can be done in "pue" LINQ, but a bit of procedural code would do the trick:
var dict = Yours.ToDictionary(y => y.Name);
foreach (var m in Mine) {
Account y;
if (dict.TryGetValue(m.Name, out y))
Ours.Add(new Account { ID = y.ID, Name = m.Name, Value = m.Value });
else
Ours.Add(m);
}
After that, printing Ours...
foreach (var o in Ours)
Console.WriteLine("{0}\t{1}\t{2}", o.ID, o.Name, o.Value);
...gives the following result:
1 C_First joe
2 C_Last bloggs
C_Car ford
Try this:
var index = Mine.ToDictionary(x => x.Name);
foreach(var account in Yours)
{
if(index.ContainsKey(account.Name))
{
var item = index[account.Name];
if(item.ID == null)
item.ID = account.ID;
}
index.Add(account.Name, account);
}
Ours = index.Values.ToList();
try this code for IEqualityComparer:
public class D : IEqualityComparer<Account>
{
public bool Equals(Account x, Account y)
{
return x.ID == y.ID && x.Value==y.Value;
}
public int GetHashCode(Account obj)
{
return obj.ID.GetHashCode() ^ obj.Value.GetHashCode();
}
}
used like this:
Ours = Mine.Except(Yours, new D()).ToList();

LINQ Query to Filter Items By Criteria From Multiple Lists

I'm having trouble conceptualizing something that should be fairly simple using LINQ. I have a collection that I want to narrow down, or filter, based on the id values of child objects.
My primary collection consists of a List of Spots. This is what a spot looks like:
public class Spot
{
public virtual int? ID { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string TheGood { get; set; }
public virtual string TheBad { get; set; }
public virtual IEnumerable<Season> Seasons { get; set; }
public virtual IEnumerable<PhotographyType> PhotographyTypes { get; set; }
}
I'm trying to filter the list of Spots by PhotographyType and Season. I have a list of ids for PhotographyTypes and Seasons, each in an int[] array. Those lists look like this:
criteria.PhotographyTypeIds //an int[]
criteria.SeasonIds //an int[]
I want to build a collection that only contains Spots with child objects (ids) matching those in the above lists. The goal of this functionality is filtering a set of photography spots by type and season and only displaying those that match. Any suggestions would be greatly appreciated.
Thanks everyone for the suggestions. I ended up solving the problem. It's not the best way I'm sure but it's working now. Because this is a search filter, there are a lot of conditions.
private List<Spot> FilterSpots(List<Spot> spots, SearchCriteriaModel criteria)
{
if (criteria.PhotographyTypeIds != null || criteria.SeasonIds != null)
{
List<Spot> filteredSpots = new List<Spot>();
if (criteria.PhotographyTypeIds != null)
{
foreach (int id in criteria.PhotographyTypeIds)
{
var matchingSpots = spots.Where(x => x.PhotographyTypes.Any(p => p.ID == id));
filteredSpots.AddRange(matchingSpots.ToList());
}
}
if (criteria.SeasonIds != null)
{
foreach (int id in criteria.SeasonIds)
{
if (filteredSpots.Count() > 0)
{
filteredSpots = filteredSpots.Where(x => x.Seasons.Any(p => p.ID == id)).ToList();
}
else
{
var matchingSpots = spots.Where(x => x.Seasons.Any(p => p.ID == id));
filteredSpots.AddRange(matchingSpots.ToList());
}
}
}
return filteredSpots;
}
else
{
return spots;
}
}
You have an array of IDs that has a Contains extension method that will return true when the ID is in the list. Combined with LINQ Where you'll get:
List<Spot> spots; // List of spots
int[] seasonIDs; // List of season IDs
var seasonSpots = from s in spots
where s.ID != null
where seasonIDs.Contains((int)s.ID)
select s;
You can then convert the returned IEnumerable<Spot> into a list if you want:
var seasonSpotsList = seasonSpots.ToList();
This may helps you:
List<Spot> spots = new List<Spot>();
Spot s1 = new Spot();
s1.Seasons = new List<Season>()
{ new Season() { ID = 1 },
new Season() { ID = 2 },
new Season() { ID = 3 }
};
s1.PhotographyTypes = new List<PhotographyType>()
{ new PhotographyType() { ID = 1 },
new PhotographyType() { ID = 2 }
};
Spot s2 = new Spot();
s2.Seasons = new List<Season>()
{ new Season() { ID = 3 },
new Season() { ID = 4 },
new Season() { ID = 5 }
};
s2.PhotographyTypes = new List<PhotographyType>()
{ new PhotographyType() { ID = 2 },
new PhotographyType() { ID = 3 }
};
List<int> PhotographyTypeIds = new List<int>() { 1, 2};
List<int> SeasonIds = new List<int>() { 1, 2, 3, 4 };
spots.Add(s1);
spots.Add(s2);
Then:
var result = spots
.Where(input => input.Seasons.All
(i => SeasonIds.Contains(i.ID))
&& input.PhotographyTypes.All
(j => PhotographyTypeIds.Contains(j.ID))
).ToList();
// it will return 1 value
Assuming:
public class Season
{
public int ID { get; set; }
//some codes
}
public class PhotographyType
{
public int ID { get; set; }
//some codes
}

Categories