Group IEnumerable into a string - c#

I wonder if someone could spare me a few minutes to give me some advice please?
I've created an IEnumerable list:
public class EmailBlock
{
public int alertCategory { get; set; }
public string alertName { get; set; }
public string alertURL { get; set; }
public string alertSnippet { get; set; } //Need to work out the snippet
}
List<EmailBlock> myEmailData = new List<EmailBlock>();
Which I then loop through some data (Umbraco content - not that that's really relevant!) and add items to the list.
myEmailData.Add(new EmailBlock { alertCategory = category.Id, alertName = alert.GetPropertyValue("pageTitle"), alertURL = alert.NiceUrl });
What ultimately I'd like to do is group the list by the alertCategory and then load each 'group' (another loop occurs later to check what members have subscribed to what alert category) into a variable which I can then use as an email's content.

You could use Linq's GroupBy() to do this:
using System.Linq
...
//Create a type to hold your grouped emails
public class GroupedEmail
{
public int AlertCategory { get; set; }
public IEnumerable<EmailBlock> EmailsInGroup {get; set; }
}
var grouped = myEmailData
.GroupBy(e => e.alertCategory)
.Select(g => new GroupedEmail
{
AlertCategory = g.Key,
EmailsInGroup = g
});
You can select to an anonymous type if required and project your sequence into whatever structure you require.

Linq has a nice group by statement:
var emailGroup = emailList.GroupBy(e => e.alertCategory);
Then you can loop through each grouping and do whatever you want:
foreach(var grouping in emailGroup)
{
//do whatever you want here.
//note grouping will access the list of grouped items, grouping.Key will show the grouped by field
}
Edit:
To retrieve a group after you have grouped them, just use Where for more than one or First for just one:
var group = emailGroup.First(g => g.Key == "name you are looking for");
or
var groups = emailGroup.Where(g => listOfWantedKeys.Contains(g.Key));
this is a lot more efficient than looping through every time you need to find something.

Related

get the distinct element(eg: ID) from student detail where collection of student details are inside another array using C#

//Model
public class StudentDetails
{
public int Contact { get; set; }
public string Summary { get; set; }
public string Address { get; set; }
public long ID { get; set; }
}
//controller
public async Task AddStudDetails([FromBody] StudentDetails[] studentDetails){}
You could use LINQ to do this:
var distinctResults = studentDetails.DistinctBy(student => student.ID).ToArray();
This'll yield an array of all the students where no two students have the same ID.
you'd also have to add a using System.Linq; to your imports :)
Edit
if you have a StudentDetails[][] as an input (array of arrays of StudentDetails) and you still want to use LINQ then this would be one way of doing it
StudentDetails[][] myArrArr = ...;
myArrArr
.Aggregate(new List<StudentDetails>(), (resultList, studentDetails) => resultList
.Concat(studentDetails)
.DistinctBy(s => s.ID)
.ToList());
however using a simple dictionary may be faster and less resource intensive
StudentDetails[][] myArrArr = ...;
Dictionary<long, StudentDetails> distinctStudents = new();
foreach (StudentDetails[] array in myArrArr)
{
foreach (StudentDetails details in array)
{
distinctStudents.TryAdd(details.ID, details);
}
}
var result = distinctStudents.Values.ToArray();
Edit 2
I think I finally understood your question, if you just want to extract a single property of a collection of objects you could just do a simple Select()
var allIds = studentDetails.Select(student => student.ID).ToArray();
var stud = from s in studentDetails select s.ID;
figured out the code I was looking for. Hope this will be helpful to others too.

RavenDB many to many and indexes

Need a help with RavenDB.
In my web page I want to have such list:
item1 category1
item2 category2
...
and another one:
category1, number of items
category2, number of items
...
My data structures:
public class Item
{
public string Id { get; set; }
public string Name { get; set; }
public string CategoryId { get; set; }
}
public class Category
{
public string Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
Index for the first list:
public class Item_WithCategory : AbstractIndexCreationTask<Item>
{
public class Result
{
public string Name { get; set; }
public string CategoryName { get; set; }
}
public Item_WithCategory()
{
Map = items => from item in items
select new
{
Name = item.Name,
CategoryName = LoadDocument<Category>(item.CategoryId).Name
};
}
}
Is this data structure suitable for my case, or will it be better to have Category instead of CategoryId in item structure?
Should I use my index or is there a better solution to take category name?
If my index is good, how to write a correct query? My current try:
Item_WithCategory.Result[] all;
using (var session = DocumentStoreHolder.Store.OpenSession())
{
all = session.Query<Item_WithCategory.Result, Item_WithCategory>().ToArray();
}
but it throws exception stating that return type is item, not result. How to fix it?
You have a couple of options here. You could store both the CategoryId and the CategoryName on the Item entity. This will of course lead to duplicated data (if you still need to store the Category entity), but "storage is cheap" is a popular term these days.The downside of this is that you need to update each Item document of a given category if the category name changes to keep things consistent. A benefit is that you need to do less work to get your desired result.
If you store Category Name on the item as well you don't need a special index to handle the first list, just query on the Items and return what you need. For the second list you need to create a Map/Reduce index on the Item entity that groups on the category.
However, if you need to use the data structure you've given, there are a couple of ways of solving this. First, it's really not recommended to use a LoadDocument inside of an index definition, especially not in a select statement. This might affect indexing performance in a negative way.
Instead, just index the properties you need to query on (or use an auto index) and then use a Result Transformer to fetch information from related documents:
public class ItemCategoryTransformer : AbstractTransformerCreationTask<Item>
{
public ItemCategoryTransformer()
{
TransformResults = results => from item in results
let category = LoadDocument<Category>(item.CategoryId)
select new ItemCategoryViewModel
{
Name = item.Name,
CategoryName = category.Name
};
}
}
public class ItemCategoryViewModel
{
public string Name { get; set; }
public string CategoryName { get; set; }
}
You can use this Transformer with a Query on the Item entity:
using (var session = documentStore.OpenSession())
{
var items = session.Query<Item>()
.TransformWith<ItemCategoryTransformer, ItemCategoryViewModel>()
.ToList();
}
As for the second list, still using your data structure, you have to use a couple of things. First, a Map/Reduce index over the Items, grouped by CategoryId:
public class Category_Items_Count : AbstractIndexCreationTask<Item, Category_Items_Count.Result>
{
public class Result
{
public string CategoryId { get; set; }
public int Count { get; set; }
}
public Category_Items_Count()
{
Map = items => from item in items
select new Result
{
CategoryId = item.CategoryId,
Count = 1
};
Reduce = results => from result in results
group result by result.CategoryId
into c
select new Result
{
CategoryId = c.Key,
Count = c.Sum(x => x.Count)
};
}
}
But as you only have the CategoryId on the Item entity, you have to use a similar transformer as in the first list:
public class CategoryItemsCountTransformer : AbstractTransformerCreationTask<Category_Items_Count.Result>
{
public CategoryItemsCountTransformer()
{
TransformResults = results => from result in results
let category = LoadDocument<Category>(result.CategoryId)
select new CategoryItemsCountViewModel
{
CategoryName = category.Name,
NumberOfItems = result.Count
};
}
}
public class CategoryItemsCountViewModel
{
public string CategoryName { get; set; }
public int NumberOfItems { get; set; }
}
And lastly, query for it like this:
using (var session = documentStore.OpenSession())
{
var items = session.Query<Category_Items_Count.Result, Category_Items_Count>()
.TransformWith<CategoryItemsCountTransformer, CategoryItemsCountViewModel>()
.ToList();
}
As you can see, there are quite a difference in work needed depending on what data structure you're using. If you stored the Category Name on the Item entity directly you wouldn't need any Result Transformers to achieve the results you're after (you would only need a Map/Reduce index).
However, Result Transformers are executed server side and are only executed on request, instead of using LoadDocument inside of an index which is executed every time indexing occurs. Also, and maybe why LoadDocuments inside of index definitions isn't recommended, every change to a document that's referenced with a LoadDocument in an index will cause that index to have to be rewritten. This might lead to a lot of work for the index engine.
Lastly, to answer your last question about why you get an exception when querying: As the actual return type of your index is the document that that's being indexed (in this case Item). To use something else you need to project your result to something else. This can be done by using ".As()" in the query:
Item_WithCategory.Result[] all;
using (var session = DocumentStoreHolder.Store.OpenSession())
{
all = session.Query<Item_WithCategory.Result, Item_WithCategory>()
.As<Item_WithCategory.Result>()
.ToArray();
}
Long post, but hope it helps!

Changing GroupBy keys depending on Class Structure

I have a list of items with multiple columns and would like to group them by some fields depending on a boolean:
I have the following class:
public class Item
{
public string Group { get; set; }
public string Person { get; set; }
public string Currency { get; set; }
public string Country { get; set; }
public string County { get; set; }
public string OtherAdd { get; set; }
public string Income { get; set; }
}
which is part of a List:
var results = items.ToList(); //items is IEnumerable<Item>
if int type = 1, then I want to group by more elements:
results = results
.GroupBy(e => new { e.Group, e.Person, e.Branch, e.Currency, e.Country, e.County, e.OtherAdd})
.Select(g => new Item
{
Group = g.Key.Group,
Person = g.Key.Person,
Currency = g.Key.Currency,
Currency = g.Key.Country,
Currency = g.Key.County,
Currency = g.Key.OtherAdd,
Income = g.Sum(p => double.Parse(p.Income, System.Globalization.CultureInfo.InvariantCulture)).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
})
.ToList();
if int type = 2, then I want to group by fewer elements (e.g. because OtherAdd would be an empty String):
results = results
.GroupBy(e => new { e.Group, e.Person, e.Branch, e.Currency})
.Select(g => new Item
{
Group = g.Key.Group,
Person = g.Key.Person,
Currency = g.Key.Currency,
Income = g.Sum(p => double.Parse(p.Income, System.Globalization.CultureInfo.InvariantCulture)).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
})
.ToList();
etc.
Is there a way for me to change the GroupBy key depending on my integer type without repeating the code?
Well, you could use the old SQL trick, conditional values:
.GroupBy(e => new { e.Group, Person = (e.Type == 1 ? e.Person : Guid.NewGuid().ToString()), ... }
While this will still include the columns in the group by, all the items will have unique keys, so it doesn't quite matter. Sadly, I don't think there's a way around generating the unique keys, unlike in SQL (where you could just use NULL).
A better way might be to implement your own grouping class, instead of using an anonymous type. You could then use your own equality and hashing semantics, to make sure whether you include all the fields or not. However, that is arguably going to be more work than just having the similar code repeated.
Or, you might want to revise your whole design. It doesn't sound like what you're trying to do makes much sense - it's already quite suspicious that you're using the same type for two different things, and using strings for all the fields doesn't help either. Maybe you could try a different object design?

LINQ Sum list of items grouped by type inside list

I have the following order object which contains a list of order addons. I am trying to create a report that shows all the addon types and their quantities summed.
public class Order {
public IList<OrderAddon> OrderAddons { get; set; }
}
public class OrderAddon {
public enum OrderType { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
This is where I am at and can't figure out if the entire query is wrong of I am just missing something.
var query = from order in Model.Orders
from addon in order.OrderAddons
group order by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.) << This is where I am stuck
};
When I hit . my intellisense is showing me properties in order object not the addon object.
That's because you're saying group order by ..., so the orderAddons object becomes a grouping of orders. You can use this if you're going to need properties from both objects:
from order in Model.Orders
from addon in order.OrderAddons
group new{addon, order} by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.addon.Quantity)
};
If this is all the data you need, this is a little simpler:
from order in Model.Orders
from addon in order.OrderAddons
group order.Quantity by addon.AddonType
into quantityByAddonType select new
{
Name = quantityByAddonType.Key,
Quantity = quantityByAddonType.Sum()
};
an alternative syntax same result...
var result = Model.Orders
.SelectMany(order => order.OrderAddons)
.GroupBy(addon => addon.OrderType)
.Select(grouping => new
{
Name = grouping.Key,
Quantity = grouping.Sum(addon => addon.Quantity)
});

LINQ- Ordering a list of objects based on a particular value

I am new to LINQ. I have a class like this:
public class StudentResults
{
public int Id { get; set; }
public ResultsStatus StatusResult { get; set; }
public string Message { get; set; }
public StudentDetails Detail { get; set; }
}
There is a method that returns a List of the above class to a variable
I need to iterate thru that variable and put the students into two different classes.
PassedStudents, FailedStudents based on ResultsStatus.
Here is what I have tried but it doesnt work
var studIds = from r in StudList
group r by r.Id into GroupedData
select new
{
//what to put here
};
foreach(var crs in studIds)
{
//what to put here to get all student names from the Detail property.
}
Is there another way?
Personally, I would just treat this as two queries:
var passed = StudList.Where(student => student.StatusResult == ResultStatus.Passed);
var failed = StudList.Where(student => student.StatusResult == ResultStatus.Failed);
Console.WriteLine("Passed...");
foreach(var student in passed)
Console.WriteLine(student.Detail.Name);
Console.WriteLine("Failed...");
foreach(var student in failed)
Console.WriteLine(student.Detail.Name);
Sounds like you'd like 2 lists: one for failed, and one for passed.
Try this:
List<StudentResults> failed = StudList.Where(x=>x.ResultStatus=="Failed")
.ToList();
List<StudentResults> passed = StudList.Where(x=>x.ResultStatus=="Passed")
.ToList();
Simply use "where". For instance, to get all the "PassedStudents":
var passedStudents = from student in StudentList
where student.StatusResult == ResultsStatus.PassedStudent
select student;
foreach (var student in passedStudents)
{
Console.WriteLine("[{0}.] {1} passed.", student.Id, student.Detail.Name);
}
You can also write the query using lambda expressions:
var passedStudents =
StudentList.Where(student => student.StatusResult == ResultsStatus.PassedStudent);

Categories