Linq Group By in nested array - c#

I have a List of Incident object (List) with the following way:
Incident
-Title
-Category
-Name
-Subcategory
-Name
Im looking a Linq eficient way, to get the count of incidents related to each category and subcategories
expected obj result -
CategoryName
Count
Subcategories
SubcategoryName
Count
var categoryGroup = incidentsModel.GroupBy(i => i.Category.Name);
var categoryGroupAndSubcategoryGroup = categoryGroup.Select(group =>
{
var subcategories = group.SelectMany(item => item.Category.Subcategories).GroupBy(item => item.Name).Select(item => new IncidentSubcategoriesCount
{
SubcategoryName = item.Key,
SubcategoryCount = item.Count()
});
return new IncidentCategoriesCount
{
CategoryName = group.Key,
catagoryCount = group.Count(),
Subcategories = subcategories
};
});
return categoryGroupAndSubcategoryGroup;
testing #hannan answer the code above return:
[
{
"categoryName": "Alumbrado",
"catagoryCount": 1,
"subcategories": [
{
"subcategoryName": "Lamparas",
"subcategoryCount": 1
}
]
},
{
"categoryName": "Seguridad",
"catagoryCount": 3,
"subcategories": [
{
"subcategoryName": "Narcotráfico",
"subcategoryCount": 3
},
{
"subcategoryName": "Robo",
"subcategoryCount": 3
}
]
}
]
CategoryName and CategoryCount are OK, the error is in SubcategoriesCount, "Narcotrafico" SubcategoryCount must be 2 and "Robo" SubcategoryCount must be 1 so the Total is 3.
Expected result
[
{
"categoryName": "Alumbrado",
"catagoryCount": 1,
"subcategories": [
{
"subcategoryName": "Lamparas",
"subcategoryCount": 1
}
]
},
{
"categoryName": "Seguridad",
"catagoryCount": 3,
"subcategories": [
{
"subcategoryName": "Narcotráfico",
"subcategoryCount": 2
},
{
"subcategoryName": "Robo",
"subcategoryCount": 1
}
]
}
]

Use Select Many then you can group any way you want
class Program
{
static void Main(string[] args)
{
List<Incident> indcidents = new List<Incident>();
var results = indcidents.SelectMany(x => x.catogories
.SelectMany(y => y.subcategories.Select(z =>
new { title = x.Title, CategoryName = y.Name, Subcategory = z.Name }
))).ToList();
}
}
public class Incident
{
public string Title { get; set; }
public List<Category> catogories { get; set; }
}
public class Category
{
public string Name { get; set; }
public List<Subcategory> subcategories { get; set; }
}
public class Subcategory
{
public string Name { get; set; }
}

if u are looking for all categories and subcategories I'm afraid u have to do nested groupBy
public class Subcategory
{
public string Name { get; set; }
}
public class Category
{
public string Name { get; set; }
public List<Subcategory> Categories { get; set; } = new List<Subcategory>()
}
public class Incident {
public List<Category> Categories { get; set; } = new List<Category>()
}
var categoryGroup = new Incident().Categories.GroupBy(category => category.Name);
var categoryGroupAndSubcategoryGroup = categoryGroup.Select(group =>
{
var subcategories = group.SelectMany(item => item.Categories).GroupBy(item => item.Name).Select(item => new
{
Name = item.Key,
count = item.Count()
})
return
new
{
Name = group.Key,
Count = group.Count()
Subcategories = subcategories
}
})

Related

Linq List problems with GroupBy and Count

Given the data below, I am trying to write a LINQ statement that will group by ListOfAdmin by id so it will only display one user. I am also checking that the count is not 0. The record one has no ListOfAdmin so that Count==0 should return null but it is displaying records for ListOfAdmin . Also
once I add //.GroupBy(f => f.id) I get the error Cannot implicitly convert type 'System.Collections.Generic.List
All of the other section is working fine.
Once I add ListOfSubscription with no record for ListOfAdmin I get the error
LINQPad Example
class Subscription
{
public int SubscriptionId { get; set; }
public int ParentProductId { get; set; }
public string ParentProductName { get; set; }
public string ChildProductName { get; set; }
public int ChildProductId { get; set; }
public int GroupId { get; set; }
public DateTime? EndDate { get; set; }
public List<admin> ListOfAdmin { get; set; }
}
class SubscriptionViewModel
{
public int SubscriptionId { get; set; }
public int ParentProductId { get; set; }
public string ParentProductName { get; set; }
public string SubscriptionIds { get; set; }
public int GroupId { get; set; }
public List<SubscriptionChildViewModel> ListOfSubscriptionChild { get; set; }
public List<AdminViewModel> ListOfAdmin { get; set; }
}
class SubscriptionChildViewModel
{
public string ChildProductName { get; set; }
public int ChildProductId { get; set; }
}
class AdminViewModel
{
public int id { get; set; }
public string name { get; set; }
}
class admin
{
public int id { get; set; }
public string name { get; set; }
}
void Main()
{
List<Subscription> ListOfSubscription = new List<Subscription>();
List<admin> ListOfAdmin = new List<admin>();
ListOfSubscription.Add(new Subscription() { SubscriptionId = 1, ParentProductId = 4, ChildProductId = 4, ParentProductName = "Product 1", ChildProductName = "Product 1", GroupId = 362,ListOfAdmin= ListOfAdmin });
ListOfAdmin.Clear();
ListOfAdmin.Add(new admin() { id = 1, name= "Mike"});
ListOfAdmin.Add(new admin() { id = 2, name = "Bill" });
ListOfSubscription.Add(new Subscription() { SubscriptionId = 2, ParentProductId = 114, ChildProductId = 1, ParentProductName = "Product 2", ChildProductName = "Product 3", GroupId = 1, ListOfAdmin= ListOfAdmin });
ListOfSubscription.Add(new Subscription() { SubscriptionId = 3, ParentProductId = 114, ChildProductId = 2, ParentProductName = "Product 2", ChildProductName = "Product 4", GroupId = 1, ListOfAdmin = ListOfAdmin });
var groupedSubscriptions = ListOfSubscription.GroupBy(u => u.GroupId);
var result = groupedSubscriptions.Select(grp1 => new
{
GroupId = grp1.Key,
Subscriptions = grp1.GroupBy(subscr => new
{
subscr.ParentProductId,
subscr.ParentProductName,
//subscr.ListOfAdmin
})
.Select(grp2 => new SubscriptionViewModel
{
GroupId = grp1.Key,
ParentProductId = grp2.Key.ParentProductId,
ParentProductName = grp2.Key.ParentProductName,
SubscriptionIds = string.Join(",", grp2.Select(y => y.SubscriptionId)),
ListOfSubscriptionChild = grp2
.Where(subsc => subsc.ChildProductId != grp2.Key.ParentProductId)
.Select(subsc => new SubscriptionChildViewModel
{
ChildProductId = subsc.ChildProductId,
ChildProductName = subsc.ChildProductName
})
.ToList(),
ListOfAdmin = (grp2.SelectMany(y => y.ListOfAdmin).Count()) == 0 ? null : grp2.SelectMany(y => y.ListOfAdmin)
.Select(a => new AdminViewModel
{
id = a.id,
name = a.name
})
//.GroupBy(f => f.id)
.ToList(),
})
});
var x = result.SelectMany((s => s.Subscriptions));
Console.Write(x);
}
Updated: using List<IGrouping<int, AdminViewModel>> I am getting dups.
Let's take a look at the exact error: Cannot implicitly convert type 'System.Collections.Generic.List<System.Linq.IGrouping<int,Program.AdminViewModel>>' to 'System.Collections.Generic.List<Program.AdminViewModel>'.
It is pretty clear: you are trying to assign a value (of type List<IGrouping<int,Program.AdminViewModel>>) that is not of the expected type (List<Program.AdminViewModel>).
You can remedy this by changing SubscriptionViewModel, so that ListOfAdmin will be List<IGrouping<int, AdminViewModel>>, as #itectori suggested.
However, I don't think that's what you're looking for. If I understand correctly, ListOfAdmin should contain, well, the admins. If that is the case, simply select the first item of every IGrouping, like this:
ListOfAdmin = (grp2.SelectMany(y => y.ListOfAdmin).Count()) == 0 ? null : grp2.SelectMany(y => y.ListOfAdmin)
.Select(a => new AdminViewModel
{
id = a.id,
name = a.name
})
.GroupBy(f => f.id)
.Select(g => g.First())
.ToList(),
According to the documentation, GrouBy() returns an object of type IEnumerable<IGrouping<TKey,TElement>>.
In your case,
(grp2.SelectMany(y => y.ListOfAdmin).Count()) == 0 ? null : grp2.SelectMany(y => y.ListOfAdmin)
.Select(a => new AdminViewModel
{
id = a.id,
name = a.name
})
.GroupBy(f => f.id)
.ToList()
return an object of type List<IGrouping<int, AdminViewModel>>
In order for your code to work, you just need to change the type of the SubscriptionViewModel.ListOfAdmin attribute.
class SubscriptionViewModel
{
[...]
public List<IGrouping<int, AdminViewModel>> ListOfAdmin { get; set; }
}

How to create join with embedded string array in MongoDB .net?

I have two collections Product and Categories. A product can have multiple categories. Product is have a string array for keep category ids (as name Product.Categories).
I want to select products with category details.
Note: I'm using MongoDB .Net Driver. Can I do this with Linq query?
Products Collection:
`
{
_id : "product_1",
title : "Product Title 1",
categories : ["category_1", "category_2"]
},
{
_id : "product_2",
title : "Product Title 2",
categories : ["category_2"]
}
Categories Collection:
{
_id: "category_1",
name : "Category 1 Name",
},
{
_id: "category_2",
name : "Category 2 Name",
}
I want to result like below:
{
_id : "product_1",
title :"Product Title 1",
categories : [
{_id = "category_1", name="Category 1 Name"},
{_id = "category_2", name="Category 2 Name"},
]
},
{
_id : "product_2",
title :"Product Title 2",
categories : [
{_id = "category_2", name="Category 2 Name"},
]
}
It's basically a join. Which is a Lookup aggregate in C# side. I believe you want the following>
public class Category
{
public string _id { get; set; }
public string name { get; set; }
}
public class Product
{
public string _id { get; set; }
public string title { get; set; }
public string[] categories { get; set; }
}
public class AggregatedProduct
{
[BsonElement("_id")]
public string Id { get; set; }
[BsonElement("title")]
public string Title { get; set; }
[BsonElement("categories")]
public Category[] Categories { get; set; }
}
string connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);
var db = client.GetDatabase("test");
var products = db.GetCollection<Product>("Products");
var categories = db.GetCollection<Category>("Categories");
var resultOfJoin = products.Aggregate().Lookup(foreignCollection: categories, localField: x => x.categories,
foreignField: x => x._id, #as: (AggregatedProduct pr) => pr.Categories).ToList();

LINQ. Loading related elements

I have an entity Contracts, ListKindWorks and KindWorks.
public partial class Contracts
{
public Contracts()
{
ListKindWorks = new HashSet<ListKindWorks>();
}
public int Id { get; set; }
...
public virtual ICollection<ListKindWorks> ListKindWorks { get; set; }
}
public partial class ListKindWorks
{
public int IdContract { get; set; }
public int IdKindWork { get; set; }
public virtual Contracts IdContractNavigation { get; set; }
public virtual KindWorks IdKindWorkNavigation { get; set; }
}
public partial class KindWorks
{
public KindWorks()
{
ListKindWorks = new HashSet<ListKindWorks>();
}
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<ListKindWorks> ListKindWorks { get; set; }
}
I want to load related elements. Something like this pseudocode:
source = model.Contracts
.Select(c => new MyType
{
IdContract = c.Id,
KindWork = new List<Item>
{ Id = KindWorks.Id, Value = KindWorks.Title }
// or
// KindWork = c.ListKindWorks
// .Where(x => x.IdContract == c.Id)
// .Select(y => new Item
// { Id = y.IdKindWork, Value = y.IdKindWorkNavigation.Title })
...
})
.ToList();
public class Item
{
public int Id { get; set; }
public string Value { get; set; }
}
Can I load List<Item> for each Contracts?
If I understand what you are looking for, I create a List for each contract in a dictionary. And here is my result:
var contracts = new List<Contracts>
{
new Contracts { Id = 1 },
new Contracts { Id = 2 },
new Contracts { Id = 3 },
};
var listKindWorks = new List<ListKindWorks>
{
new ListKindWorks { IdContract = 1, IdKindWork = 1 },
new ListKindWorks { IdContract = 1, IdKindWork = 2 },
new ListKindWorks { IdContract = 2, IdKindWork = 2 },
new ListKindWorks { IdContract = 2, IdKindWork = 3 }
};
var kindWorks = new List<KindWorks>
{
new KindWorks { Id = 1, Title = "Title 1" },
new KindWorks { Id = 2, Title = "Title 2" },
new KindWorks { Id = 3, Title = "Title 3" },
};
Dictionary<Contracts, List<Item>> myDic = contracts.Select(
contract => contract).ToDictionary(
contract => contract,
contract => listKindWorks.Where(
listKindWork => listKindWork.IdContract.Equals(contract.Id))
.Select(listKindWork => new Item
{
Id = kindWorks.FirstOrDefault(kindWork => kindWork.Id.Equals(listKindWork.IdKindWork))?.Id?? listKindWork.IdKindWork,
Value = kindWorks.FirstOrDefault(kindWork => kindWork.Id.Equals(listKindWork.IdKindWork))?.Title?? "KindWork not found"
}).ToList());
I obtain this for my test :
Contract1 : Title1, Title2
Contract2 : Title2, Title3
Contract3 : Nothing
IEnumerable<Item> KindWork = c.ListKindWorks
.Select(y => new Item
{
Id = y.IdKindWork,
Value = y.IdKindWorkNavigation.Title
})
IEnumerable<Item> Subject = c.ListSubjects
.Select(y => new Item
{
Id = y.IdSubject,
Value = y.IdSubjectNavigation.Title
})

C# aggregating attributes in a list

I have an object like as shown below
public class SampleObject
{
public int MsfId { get; set; }
public List<string> PgId { get; set; }
public List<string> DcId { get; set; }
}
In the above aggregation of the PgId values grouped by MsfId. Same is the case with DcId as well.
For example:
MsfId: 100
PgId: "abc"
DcId: "123"
MsfId: 100
PgId: "def"
DcId: "456"
MsfId: 100
PgId: "ghi"
DcId: "789"
MsfId: 101
PgId: "abc"
DcId: "123"
How to write a LINQ query to aggregate this and create a list of SampleObjects like below?
MsfId: 100
PgId: "abc", "def", "ghi"
DcId: "123", "456", "789"
MsfId: 101
PgId: "abc"
DcId: "123"
Try aggregation like this:
var result = col.GroupBy(x => x.MsfId)
.Select(x => new SampleObject {
MsfId = x.Key,
PgCodes = x.Select(t=>t.PgId).ToList(),
DcCodes = x.Select(t=>t.DcId).ToList()
});
Scenario 1
public class SampleObject
{
public int MsfId { get; set; }
public List<string> PgId { get; set; }
public List<string> DcId { get; set; }
}
Scenario 2
public class SampleObjectSource
{
public int MsfId { get; set; }
public string PgId { get; set; }
public string DcId { get; set; }
}
Scenario 1 Answer
var collection = new List<SampleObject>();
var result = collection.GroupBy(y => y.MsfId)
.Select(y => new SampleObject
{
MsfId = y.Key,
PgId = y.SelectMany(g => g.PgId).Distinct().ToList(),
}).ToList();
Scenario 2
var collection2 = new List<SampleObjectSource>();
var result1 = collection2.GroupBy(y => y.MsfId)
.Select(y => new SampleObject
{
MsfId = y.Key,
PgId = y.Select(h => h.PgId).Distinct().ToList(),
}).ToList();
Update : Please see the dotnetfiddle
You need to group the items with a query. This linq grouping will create a collection of collections(with a key)
I just made a full working example:
// The class to start with
public class SampleObjectSource
{
public int MsfId { get; set; }
public string PgId { get; set; }
public string DcId { get; set; }
}
// the result class
public class SampleObject
{
public int MsfId { get; set; }
public List<string> PgId { get; set; }
public List<string> DcId { get; set; }
}
// for example:
public class Example
{
public Example()
{
// create a list that contains the items.
var list = new List<SampleObjectSource>
{
new SampleObjectSource { MsfId= 100, PgId= "abc", DcId= "123" },
new SampleObjectSource { MsfId= 100, PgId= "def", DcId= "456" },
new SampleObjectSource { MsfId= 100, PgId= "ghi", DcId= "789" },
new SampleObjectSource { MsfId= 101, PgId= "abc", DcId= "123" },
};
// the linq query that does the grouping.
var query = from item in list
// group the items by MsfId
group item by item.MsfId into itemgroup
// create the new class and initialize the properties
select new SampleObject
{
// the grouping item is the .Key (in this case MsfId)
MsfId = itemgroup.Key,
// the itemgroup is a collection of all grouped items, so you need to select the properties you're interrested in.
DcId = itemgroup.Select(i => i.DcId).ToList(),
PgId = itemgroup.Select(i => i.PgId).ToList()
};
// show the results in the Output window.
foreach (var item in query)
{
Trace.WriteLine($"MsfId: {item.MsfId}");
// some trick to format a list of strings to one string
Trace.WriteLine($"PgId: {string.Join(", ", item.PgId.Select(s => Quote(s)))}");
Trace.WriteLine($"DcId: {string.Join(", ", item.DcId.Select(s => Quote(s)))}");
Trace.WriteLine("");
}
}
// this method will surround the passed string with quotes.
private string Quote(string item)
{
return "\"" + item + "\"";
}
}
results:
MsfId: 100
PgId: "abc", "def", "ghi"
DcId: "123", "456", "789"
MsfId: 101
PgId: "abc"
DcId: "123"
Do it all with one GroupBy using the appropriate overload. Working Fiddle Here.
Note the use of SelectMany to concatenate the grouped collections into one.
var result = sampleObjects
.GroupBy(
o => o.MsfId,
(k, g) => new SampleObject
{
MsfId = k,
PgId = g.SelectMany(p => p.PgId).ToList(),
DcId = g.SelectMany(p => p.DcId).ToList()
});
If you want to remove duplicates from the collections consider Distinct() e.g.
var result = sampleObjects
.GroupBy(
o => o.MsfId,
(k, g) => new SampleObject
{
MsfId = k,
PgId = g.SelectMany(p => p.PgId).Distinct().ToList(),
DcId = g.SelectMany(p => p.DcId).Distinct().ToList()
});

How to get data in string array or JSON format using linq to entities?

I have an ASP.Net MVC5 site and using EF 6.0
One to Many relationship
Here are my models
public class Singer
{
[Key]
public int SingerID { get; set; }
public string SingerName { get; set; }
public virtual List<Album> Albums { get; set; }
}
public class Album
{
[Key]
public int AlbumID { get; set; }
public string AlbumName { get; set; }
public string AlbumDate { get; set; }
[ForeignKey("Singer")]
public int SingerID { get; set; }
public virtual Singer Singer { get; set; }
}
Now my Linq is as below
public IEnumerable<T> GetAlbums()
{
using (dbContext db = new dbContext())
{
IQueryable<T> query = (from c in db.Albums
group c.AlbumId by c.SingerId into albums
select new AlbumMapper()
{
AlbumID = albums.Key,
Total = albums.Count()
})
}
}
In the current scenario I get all the albums grouped by albumId and the count of the albums.
But my need is to form JSON string as below
[
{
"SingerID":1,
"Albums":[
{
"AlbumName":"This is Album 1",
"AlbumDate":"Dec 30,2015"
},
{
"AlbumName":"This is Album 2",
"AlbumDate":"Dec 30 2015"
}
]
},
{
"SingerID":2,
"Albums":[
{
"AlbumName":"This is Album 1",
"AlbumDate":"Dec 30,2015"
},
{
"AlbumName":"This is Album 2",
"AlbumDate":"Dec 30 2015"
}
]
}
]
Adding Mapper Classes
public class AlbumDetails
{
public DateTIme AlbumDate
public string AlbumName
}
public class AlbumMapper
{
public int AlbumID
public IEnumerable<AlbumDetails> Albums
}
Just put all the Singers into a list and serialize it using Json.NET (http://www.newtonsoft.com/json)
If you want to leave out SingerName be sure to add a [JsonIgnore] data attribute to the property
Then you want just this, combination of GroupBy with Select using anonymous object.
Using lambda
public IEnumerable<AlbumMapper> GetAlbums()
{
using(dbContext db = new dbContext())
{
return db.Albums.GroupBy(a => a.SingerID)
.Select(g => new AlbumMapper
{
SingerID = g.Key,
Albums = g.Select(a => new AlbumDetails { AlbumName = a.AlbumName, AlbumDate = a.AlbumDate })
});
}
}
You have to modify your map classes to fit this:
public class AlbumDetails
{
public DateTime AlbumDate { get; set; }
public string AlbumName { get; set; }
}
public class AlbumMapper
{
public int SingerID { get; set; }
public IEnumerable<AlbumDetails> Albums { get; set; }
}
Using linq syntax
public IEnumerable<T> GetAlbums()
{
using(dbContext db = new dbContext())
{
return from a in db.Albums
group a by a.SingerID into albums
select new AlbumMapper
{
SingerID = albums.Key,
Albums = albums.Select(album => new AlbumDetails { AlbumName = album.AlbumName, AlbumDate = album.AlbumDate })
};
}
}
With this sample data:
var albums = new List<Album>();
var singer = new Singer(1, "Singer 1");
var singer2 = new Singer(2, "Singer 2");
albums.Add(new Album(1,"This is Album 1", "Dec 30,2015", singer));
albums.Add(new Album(2,"This is Album 2", "Dec 30,2015", singer));
albums.Add(new Album(1,"This is Album 1", "Dec 30,2015", singer2));
albums.Add(new Album(2,"This is Album 2", "Dec 30,2015", singer2));
The result of this
albums.GroupBy(a => a.SingerID)
.Select(g => new
{
SingerID = g.Key,
Albums = g.Select(a => new { a.AlbumName, a.AlbumDate })
})
Is
[
{
"SingerID": 1,
"Albums": [
{
"AlbumName": "This is Album 1",
"AlbumDate": "Dec 30,2015"
},
{
"AlbumName": "This is Album 2",
"AlbumDate": "Dec 30,2015"
}
]
},
{
"SingerID": 2,
"Albums": [
{
"AlbumName": "This is Album 1",
"AlbumDate": "Dec 30,2015"
},
{
"AlbumName": "This is Album 2",
"AlbumDate": "Dec 30,2015"
}
]
}
]

Categories