Split and group by using Linq - c#

I have done the below which is working fine.
public StandardReportsModel GetStandardReportsModel(string adUser, string adPassword, IPrincipal user)
{
var myItems = getMyItems().Where(myItem => !string.IsNullOrEmpty(myItem.Description));
var categories = new List<string>();
var myItems = new List<MyModel>();
foreach (var myItem in myItems)
{
var myIndex = myItem.Description.IndexOf('*');
var category = myIndex != -1 ? myItem.Description.Substring(0, myIndex).ToUpper() : myItem.Description.ToUpper();
if (categories.IndexOf(category) == -1)
{
categories.Add(category);
}
myItems.Add(getMyItem(myItem, category));
}
categories.Sort();
return new StandardModel { Categories = categories, MyItems = myItems };
}
private MyModel getMyItem(MyItem myItem, string category)
{
var categoryIdentifierIndex = myItem.Description.LastIndexOf(Delimiters.CategoryDescriptionDelimiter);
var description = categoryIdentifierIndex != -1 ? myItem.Description.Substring(categoryIdentifierIndex + 1, (myItem.Description.Length - categoryIdentifierIndex + 1))) : myItem.Description;
return new MyModel{ Name = myItem.Name, Description = myItem.Description, Category = category };
}
public class StandardModel
{
public List<string> Categories { get; set; }
public List<MyModel> MyItems { get; set; }
}
Now I am trying to do the same with Linq and I have reached till below. From the obtained result I can get distinct of category and sort it. Is there a way by which it can be doen in a single query?
var result = myItems.Where(myItem => !string.IsNullOrEmpty(myItem.Description))
.Select(ci => new MyModel
{
Name = ci.Name,
Category = ci.Description.Split('*')[0],
Description = ci.Description.Split('*')[2]
}).ToList();
I want categories and their items. Probably I might have to use group by here.
Please suggest

Dictionary<string, List<MyModel>> result = myItems
.Where(myItem => !string.IsNullOrEmpty(myItem.Description))
.Select(ci => new MyModel
{
Name = ci.Name,
Category = ci.Description.Split('*')[0],
Description = ci.Description.Split('*')[2]
})
.GroupBy(e => e.Category)
.ToDictionary(e => e.Key, e => e.ToList());

Related

Use GroupBy to make SubLists from List where the GroupBy value is a List<t>

I have a simple List that I would like to display, grouped by which Category it is a member of. All the examples I have seen use GroupBy but with a single ID, I'm having trouble figuring out how to do so with a List. It's ok if the product appears under both Categories.
public class Product
{
public int Id { get; set; }
public string Title { get; set; }
public List<Category> Categories { get; set; }
}
StringBuilder ProductList = new StringBuilder();
var p = _products.GroupBy(a => a.Categories);
foreach (var item in p)
{
ProductList.Append($"<p><strong>{item.Key}</strong><br/>");
foreach (var e in item)
{
ProductList.Append($"{e.Title}");
ProductList.Append("</p>");
}
}
Here's a solution with sample data you can test yourself:
var categories = Enumerable.Range(0, 10).Select(n => new Category { Id = n }).ToArray();
var products = new[]
{
new Product { Id = 1, Title = "P1", Categories = new List<Category> { categories[0], categories[1], categories[2] } },
new Product { Id = 2, Title = "P2", Categories = new List<Category> { categories[0], categories[1], categories[3] } },
new Product { Id = 3, Title = "P3", Categories = new List<Category> { categories[0], categories[2], categories[3] } },
new Product { Id = 4, Title = "P4", Categories = new List<Category> { categories[0], categories[2], categories[3] } },
new Product { Id = 5, Title = "P5", Categories = new List<Category> { categories[0], categories[3], categories[5] } },
new Product { Id = 6, Title = "P6", Categories = new List<Category> { categories[0], categories[4], categories[5] } },
};
var categoryGroups =
from p in products
from c in p.Categories
group p by c.Id into g
select g;
foreach (var categoryGroup in categoryGroups)
{
Console.WriteLine($"Category {categoryGroup.Key}:");
foreach (var product in categoryGroup)
{
Console.WriteLine($"\tProduct {product.Id}: {product.Title}");
}
Console.WriteLine("-----------------------------------------------------");
}
I assume class Category has some id property, e.g. Id, to group by. If it's reference based grouping you can replace group p by c.Id into g with group p by c into g.
You can use SelectMany to flatten the list into tuples of (Product, Category), then use GroupBy to group product by category.
StringBuilder ProductList = new StringBuilder();
var p = _products
.SelectMany(a => a.Categories, (prod, cat) => (prod, cat))
.GroupBy(tuple => tuple.cat, tuple => tuple.prod);
foreach (var item in p)
{
ProductList.Append($"<p><strong>{item.Key}</strong><br/>");
foreach (var e in item)
{
ProductList.Append(e.Title);
ProductList.Append("</p>");
}
}

Query SQL Table with Value from MultipleSelectionComboBox

So, I'm using Syncfusion controls and I've a MultipleSelectionCombobox where user can filter multiple arguments.
I have a query which will load a list based on parameters query.
So, first, I have a class to hold my values;
public class Orders
{
public int ID { get; set; }
public string OrderNum { get; set; }
public string Status { get; set; }
public DateTime Date { get; set; }
}
Then, the query:
public IEnumerable<Orders> LoadData()
{
var ctx = new DbContext();
var query = (from o in ctx.tblOrders.AsQueryable()
select new Orders
{
ID = o.OrderID,
OrderNum = o.OrderNum.ToString(),
Status = o.OrderStatus,
Date = o.OrderDate
});
if(CmbOrderStatus.SelectedItems != null)
{
List<string> list = new List<string>();
foreach (SelectedItems obj in CmbOrderStatus.SelectedItems)
{
list.Add(obj.ToString());
}
for(int i = 0; i < list.Count; i++)
{
var value = list[i];
query = query.Where(p => p.Status == value);
}
}
return query.ToList();
}
So, in Database in have many Orders and many OrderStatus, like "Opened", "Delayed", "Closed".
So, if I filter in CmbOrderStatus "Opened" and "Delayed", I get nothing! If only one is selected, I get nothing!
Any help here?
Thanks
The code use only last filter.
Try this:
public IEnumerable<Orders> LoadData()
{
var ctx = new DbContext();
var query = (from o in ctx.tblOrders.AsQueryable()
select new Orders
{
ID = o.OrderID,
OrderNum = o.OrderNum.ToString(),
Status = o.OrderStatus,
Date = o.OrderDate
});
if(CmbOrderStatus.SelectedItems != null)
{
List<string> list = new List<string>();
foreach (SelectedItems obj in CmbOrderStatus.SelectedItems)
{
list.Add(obj.ToString());
}
query = query.Where(p => list.Contains(p.Status));
}
return query.ToList();
}

Select a restricted subset of items based on child property

I'm just wondering if there's a better way to write this code, basically the source object contains a mix of items with a boolean property however the destination object has two lists which should contain the true/false items independently.
I've written it in Linq and it works just fine but it feels as though there's a better way. Any suggestions?
void Main()
{
var s = new ResponseObject()
{
Results = new List<GroupedObject>()
{
new GroupedObject()
{
Name = "List A",
List=new List<DetailObject>()
{
new DetailObject{ Name = "Allowed", AllowedAccess = true},
new DetailObject{ Name = "Restricted", AllowedAccess = false}
}
},
new GroupedObject()
{
Name = "List B",
List=new List<DetailObject>()
{
new DetailObject{ Name = "Allowed", AllowedAccess = true},
new DetailObject{ Name = "Restricted", AllowedAccess = false}
}
}
}
};
var d = new ResponseViewModel();
d.AllowedResults = FilterObjectsByAccess(s.Results, true);
d.RestrictedResults = FilterObjectsByAccess(s.Results, false);
// Other stuff
}
public IEnumerable<GroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source, bool allowAccess)
{
return source.Where(i => i.List.Any(c => c.AllowedAccess == allowAccess))
.Select(i => new GroupedObject()
{
Name = i.Name,
List = i.List.Where(c => c.AllowedAccess == allowAccess)
});
}
public class ResponseObject
{
public IEnumerable<GroupedObject> Results { get; set; }
}
public class ResponseViewModel
{
public IEnumerable<GroupedObject> AllowedResults { get; set; }
public IEnumerable<GroupedObject> RestrictedResults { get; set; }
}
public class GroupedObject
{
public string Name { get; set; }
public IEnumerable<DetailObject> List { get; set; }
}
public class DetailObject
{
public string Name { get; set; }
public bool AllowedAccess { get; set; }
}
One change that may be worth benchmarking would be changing:
public IEnumerable<GroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source, bool allowAccess)
{
return source.Where(i => i.List.Any(c => c.AllowedAccess == allowAccess))
.Select(i => new GroupedObject()
{
Name = i.Name,
List = i.List.Where(c => c.AllowedAccess == allowAccess)
});
}
to:
public IEnumerable<GroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source, bool allowAccess)
{
return source
.Select(i => new GroupedObject()
{
Name = i.Name,
List = i.List.Where(c => c.AllowedAccess == allowAccess).ToList() // `ToList` here is optional - it is a trade-off between RAM and CPU
})
.Where(z => z.List.Any());
}
Your original code, with the use of Any then Where would enumerate i.List twice. The above change would likely improve that.
Another approach, which would likely involve even higher memory consumption could be to switch to using ToLookup:
var d = new ResponseViewModel
{
AllowedResults =
FilterObjectsByAccess(s.Results)
.Select(z => new GroupedObject() { Name = z.Name, List = z.GroupedList[false] })
.Where(z => z.List.Any()),
RestrictedResults =
FilterObjectsByAccess(s.Results)
.Select(z => new GroupedObject() { Name = z.Name, List = z.GroupedList[true] })
.Where(z => z.List.Any())
};
// Other stuff
}
public List<SpecialGroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source)
{
return source.Select(i => new SpecialGroupedObject()
{
Name = i.Name,
GroupedList = i.List.ToLookup(c => c.AllowedAccess)
}).ToList();
}
I can suggest you to use ToDictionary() like this:
var result = new[] {true, false}.ToDictionary(k => k,
v =>
s.Results.Where(w => w.List.Any(x => x.AllowedAccess == v))
.Select(c => new GroupedObject {Name = c.Name, List = c.List.Where(l => l.AllowedAccess == v)}));
var allowedResults = result[true];
var restrictedResults = result[false];
Or this:
var result = s.Results
.SelectMany(c => c.List, (b, c) => new {b.Name, DObj = c})
.GroupBy(g => g.DObj.AllowedAccess)
.ToDictionary(k=> k.Key,
c =>
new {
c.Key,
List =
c.GroupBy(cg => cg.Name)
.Select(
x => new GroupedObject {Name = x.Key, List = x.Select(l => l.DObj).ToList()})
.ToList()
});

c# - web.api - try to build a new return value

I'am learning c# and angular and I try to aggregate the data of a product with the follower. I have some NotMapped-fields in my class (codefirst)
[NotMapped]
public string followitemtitle { get; set; }
[NotMapped]
public string followitemprice { get; set; }
And I get 2 items of each group
return _context.Product
.Include(c => c.productgroup)
.GroupBy(p => p.productgroupid)
.SelectMany(g => g.OrderByDescending(p => p.productdate).Take(2));
What I try is a loop and build a new return-value -> only the first product ( every second in the list) - with some information of the follower product.
[HttpGet("new")]
public IActionResult GetProductsIncFollower()
{
var productEntities = _productrepository.GetProductsIncFollower();
Product tmpData = new Product();
IEnumerable<Product> tmpDataList = new List<Product>();
int index = 1;
string tmpFollowtitle = "";
string tmpFollowprice = "";
foreach (var item in productEntities)
{
if(index % 2 != 0)
{
tmpFollowtitle = item.title;
tmpFollowprice = item.price;
}
else
{
tmpData = item;
tmpData.followitemtitle = tmpFollowtitle;
tmpData.followitemprice = tmpFollowprice;
tmpDataList.Append(tmpData);
}
index++;
}
var results = tmpDataList;
return Ok(results);
}
But tmpDataList is empty and I dont understand why.

Query or algorithm to generate a structure to populate a tree (C#, LINQ)

I couldn't find or come up with a generic and elegant algorithm that would let me populate the tree-like structure.
The simplest example is a blog archive: I have a bunch of records which can be selected and sorted by date.
I want to have a tree where years may be top level, months are next level and actual post titles are next again.
So far I've came up with a naive straight forward implementation that works, but I'm sure that could be improved using LINQ, etc.
Here I just sort records by date and iterate checking if the year or month has changed, and add tree nodes accordingly.
"BlogEntry" is a class that has a reference to both a parent and children and is later used to generate HTML.
I welcome suggestions on improving the algorithm!
IEnumerable<Post> posts = db.Posts.OrderBy(p => p.DateCreated);
var topPost = posts.First();
int curYear = topPost.DateCreated.Year;
int curMonth = topPost.DateCreated.Month;
//create first "year-level" item
var topYear = new BlogEntry { Name = topPost.DateCreated.Year.ToString().ToLink(string.Empty) };
entries.Add(topYear);
var currentYear = topYear;
var topMonth = new BlogEntry { Name = topPost.DateCreated.ToString("MMMM").ToLink(string.Empty), Parent = currentYear };
currentYear.Children.Add(topMonth);
var currentMonth = topMonth;
foreach (var post in posts)
{
if(post.DateCreated.Year == curYear)
{
if (post.DateCreated.Month != curMonth)
{
//create "month-level" item
var month = new BlogEntry { Name = post.DateCreated.ToString("MMMM").ToLink(string.Empty), Parent = currentYear };
currentYear.Children.Add(month);
currentMonth = month;
curMonth = post.DateCreated.Month;
}
//create "blog entry level" item
var blogEntry = new BlogEntry { Name = post.Title.ToLink("/Post/" + post.PostID + "/" + post.Title.ToSeoUrl() ), Parent = currentMonth };
currentMonth.Children.Add(blogEntry);
}
else
{
//create "year-level" item
var year = new BlogEntry { Name = post.DateCreated.Year.ToString().ToLink(string.Empty) };
entries.Add(year);
currentYear = year;
curMonth = post.DateCreated.Month;
curYear = post.DateCreated.Year;
}
}
I've created a test example, to check the correctness of the logic. I think this is what you need.
public class BlogEntyTreeItem
{
public string Text { set; get; }
public string URL { set; get; }
public List<BlogEntyTreeItem> Children { set; get; }
public List<BlogEntyTreeItem> GetTree()
{
NWDataContext db = new NWDataContext();
var p = db.Posts.ToList();
var list = p.GroupBy(g => g.DateCreated.Year).Select(g => new BlogEntyTreeItem
{
Text = g.Key.ToString(),
Children = g.GroupBy(g1 => g1.DateCreated.ToString("MMMM")).Select(g1 => new BlogEntyTreeItem
{
Text = g1.Key,
Children = g1.Select(i => new BlogEntyTreeItem { Text = i.Name }).ToList()
}).ToList()
}).ToList();
return list;
}
}
using the link Playing with Linq grouping: GroupByMany ?
suggested in How can I hierarchically group data using LINQ?
I first refactored my code into
Solution 1
var results = from allPosts in db.Posts.OrderBy(p => p.DateCreated)
group allPosts by allPosts.DateCreated.Year into postsByYear
select new
{
postsByYear.Key,
SubGroups = from yearLevelPosts in postsByYear
group yearLevelPosts by yearLevelPosts.DateCreated.Month into postsByMonth
select new
{
postsByMonth.Key,
SubGroups = from monthLevelPosts in postsByMonth
group monthLevelPosts by monthLevelPosts.Title into post
select post
}
};
foreach (var yearPosts in results)
{
//create "year-level" item
var year = new BlogEntry { Name = yearPosts.Key.ToString().ToLink(string.Empty) };
entries.Add(year);
foreach (var monthPosts in yearPosts.SubGroups)
{
//create "month-level" item
var month = new BlogEntry { Name = new DateTime(2000, (int)monthPosts.Key, 1).ToString("MMMM").ToLink(string.Empty), Parent = year };
year.Children.Add(month);
foreach (var postEntry in monthPosts.SubGroups)
{
//create "blog entry level" item
var post = postEntry.First() as Post;
var blogEntry = new BlogEntry { Name = post.Title.ToLink("/Post/" + post.PostID + "/" + post.Title.ToSeoUrl()), Parent = month };
month.Children.Add(blogEntry);
}
}
}
And then into a more generic
Solution 2
var results = db.Posts.OrderBy(p => p.DateCreated).GroupByMany(p => p.DateCreated.Year, p => p.DateCreated.Month);
foreach (var yearPosts in results)
{
//create "year-level" item
var year = new BlogEntry { Name = yearPosts.Key.ToString().ToLink(string.Empty) };
entries.Add(year);
foreach (var monthPosts in yearPosts.SubGroups)
{
//create "month-level" item
var month = new BlogEntry { Name = new DateTime(2000, (int)monthPosts.Key, 1).ToString("MMMM").ToLink(string.Empty), Parent = year };
year.Children.Add(month);
foreach (var postEntry in monthPosts.Items)
{
//create "blog entry level" item
var post = postEntry as Post;
var blogEntry = new BlogEntry { Name = post.Title.ToLink("/Post/" + post.PostID + "/" + post.Title.ToSeoUrl()), Parent = month };
month.Children.Add(blogEntry);
}
}
}
................................................
public static class MyEnumerableExtensions
{
public static IEnumerable<GroupResult> GroupByMany<TElement>(
this IEnumerable<TElement> elements,
params Func<TElement, object>[] groupSelectors)
{
if (groupSelectors.Length > 0)
{
var selector = groupSelectors.First();
//reduce the list recursively until zero
var nextSelectors = groupSelectors.Skip(1).ToArray();
return
elements.GroupBy(selector).Select(
g => new GroupResult
{
Key = g.Key,
Items = g,
SubGroups = g.GroupByMany(nextSelectors)
});
}
else
return null;
}
}
public class GroupResult
{
public object Key { get; set; }
public IEnumerable Items { get; set; }
public IEnumerable<GroupResult> SubGroups { get; set; }
}

Categories

Resources