GroupBy Expression - c#

I have the following object(s) and I want to have a result that provides a collection of objects grouped by first category description and then grouped by subcategory description with:
Category(Count)
SubCategory(Count)
SubCategory(Count)
public class Posting
{
public Category Category { get; set; }
public SubCategory SubCategory { get; set; }
}
public class Category
{
public string Description { get; set; }
}
public class SubCategory
{
public string Description { get; set; }
}
EDIT
So I have this working like so:
foreach (var category in col.GroupBy(x => x.Category.Description).Select(x => new { CategoryName = x.Key, Items = x }).OrderBy(x => x.CategoryName))
{
Console.WriteLine(category.CategoryName);
foreach (var subCategory in category.Items.GroupBy(x => x.SubCategory.Description).Select(x => new { SubCategoryName = x.Key }).OrderBy(x => x.SubCategoryName))
{
Console.WriteLine("\t" + subCategory.SubCategoryName);
}
}
But I would like to get this into one object graph, is that possible?

This seems to be pretty close, with the caveat that the Key field of the "inner" sub-category groupings is an anonymous type containing both the Category and SubCategory. But, depending on what your application is doing, that might be a feature since you'll always have the Category available.
var results = col
.GroupBy(posting => new
{
Category = posting.Category.Description,
SubCategory = posting.SubCategory.Description
})
.GroupBy(group => group.Key.Category.Description);

Related

Category - subcategory listing with dapper and SQL Server

I have a requirement where there are two entities: Category and Bookmark. A category can contain some subcategories and/or some bookmarks. My table structure is as shown below:
tblCategories
ID int PK,
Name nvarchar
tblBookmarks
ID int PK
Name nvarchar
tblBookmarkCategories
CategoryID int (foreignKey)
BookmarkID int (foreignKey)
Similarly my entities are as following:
public class CategoryTree
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<CategoryTree> SubCategory { get; set; }
public ICollection<Bookmark> Bookmarks { get; set; }
}
public class Bookmark
{
public int ID { get; set; }
public string Name { get; set; }
}
How can I show the category list along with their subcategories and bookmarks using dapper? I want to display the result in the following format:
Category 1
Bookmark 1
Category 1.1
Bookmark 1.1
Bookmark 1.2
Category 1.2
Category 2
Bookmark 2
Category 2.1
Category 2.1.1
Category 2.2
Since the Category and Subcategory also contain relationship, you could add a "ParentID" column in the "tblCategories" table, by using it, you could find the subcategory based on the "ParentID" column, like this:
The Dapper is used to query data from the database, I prefer to load all the categories and the bookmarks at once, then based on the category to find the subcategories and bookmarks.
So, I create the following classes:
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
public int ParentID { get; set; }
public virtual ICollection<Category> SubCategories { get; set; }
public virtual ICollection<Bookmark> Bookmarks { get; set; }
}
public class Bookmark {
public int ID { get; set; }
public string Name { get; set; }
public int CategoryID { get; set; }
}
To get/display the category and its subcategory, we could use the recursive method to check whether current category contains subcategory, if it contains the subcategory, filter the data from the category list. The detail code as below:
public class HomeController : Controller
{
private readonly IConfiguration _configuration;
public HomeController(IConfiguration configuration)
{
_configuration = configuration;
}
//Index page, display the categories and bookmarks
public IActionResult DapperIndex()
{
//get the connection string
var connectionstring = _configuration.GetConnectionString("DefaultConnection");
//get all categories and bookmarks
List<Category> allcategories = new List<Category>();
using (SqlConnection connection = new SqlConnection(connectionstring))
{
allcategories = connection.Query<Category>("SELECT * FROM tblCategories").ToList();
}
List<Bookmark> allbookmarks = new List<Bookmark>();
using (SqlConnection connection = new SqlConnection(connectionstring))
{
allbookmarks = connection.Query<Bookmark>("select b.Id, b.Name, bc.CategoryID from tblBookmarks as b join tblBookmarkCategories as bc on b.id = bc.BookmarkID").ToList();
}
List<Category> categories = allcategories
.Where(e => e.ParentID == 0) /* grab only the root parent nodes */
.Select(e => new Category
{
ID = e.ID,
Name = e.Name,
ParentID = e.ParentID,
SubCategories = GetSubCategory(allcategories,allbookmarks, e.ID), /* Recursively grab the children */
Bookmarks = GetBookmarks(allbookmarks, e.ID)
}).ToList();
ViewBag.categoriesList = categories;
return View();
}
//get the subcategories
private static List<Category> GetSubCategory(List<Category> items, List<Bookmark> bookmarks, int parentId)
{
return items.Where(x => x.ParentID == parentId)
.Select(e => new Category
{
ID = e.ID,
Name = e.Name,
ParentID = e.ParentID,
SubCategories = GetSubCategory(items,bookmarks, e.ID),
Bookmarks = GetBookmarks(bookmarks,e.ID)
}).ToList();
}
//get the bookmarks.
public static List<Bookmark> GetBookmarks(List<Bookmark> bookmarks, int categoryid)
{
return bookmarks.Where(c => c.CategoryID == categoryid).Select(e=> new Bookmark()
{
ID= e.ID,
Name = e.Name,
CategoryID = e.CategoryID
}).ToList();
}
}
Code in the .cshtml page:
#{
List<Category> categorylist = ViewBag.categoriesList as List<Category>;
void ShowTree(List<Category> categories)
{
if (categories != null)
{
foreach (var item in categories)
{
<li>
<span>#item.Name</span>
#*display the bookmarks*#
#if (item.Bookmarks.Any())
{
foreach (var mark in item.Bookmarks)
{
<ul> #mark.Name </ul>
}
}
#*display the subcategories*#
#if (item.SubCategories.Any())
{
<ul>
#{ ShowTree(item.SubCategories.ToList());}
</ul>
}
</li>
}
}
}
ShowTree(categorylist);
}
The result as below:
Edit: To use Dapper in Asp.net Core application, install "Dapper" via NuGet, then check the Dapper Query Basics.

Group by with include (inner join)

I am querying a table resulting from two other tables in the image below.
In one category I can have several questions.
using this query did not have the result I expected:
var result = await _repository.GetQuestionCategory()
.Include(x => x.Category)
.Include(y => y.Question)
.Select(x => new QuestionCategoryViewModel
{
Id = x.Id,
CategoryId = x.Category.Id,
CategoryName = x.Category.Name,
IsRequired = x.IsRequired,
QuestionId = x.Question.Id,
QuestionName = x.Question.Name,
Weigth = x.Weigth
}).GroupBy(x => x.CategoryId).ToListAsync().ConfigureAwait(false);
How could I send a similar structure like this
{ categoryId, categoryName, IsRiquered, Weigth, questions: [ questionId: questionName: y]}
Mmodel Category and Question
public class QuestionCategory
{
public Guid Id { get; set; }
public Question Question { get; set;}
public Category Category { get; set;}
public int QuestionId { get; set; }
public int CategoryId { get; set; }
public bool IsRequired { get; set; }
public int Weigth { get; set; }
}
You should use a GroupBy statement with most of its parameters. Note the inconsistent naming of the result properties is taken 1:1 from the question. You may want to create some explicit DTO type instead of creating the result as anonymous type.
IQueryable<QuestionCategory> questionCategories = new EnumerableQuery<QuestionCategory>(Enumerable.Empty<QuestionCategory>());
var result = questionCategories.GroupBy(
// key selector
qc => new
{
categoryId = qc.CategoryId,
categoryName = qc.Category.Name,
IsRiquered = qc.IsRequired,
Weigth = qc.Weigth
},
// element selector
qc => qc.Question,
// result selector
(k, v) => new
{
k.categoryId,
k.categoryName,
k.IsRiquered,
k.Weigth,
questions = v.Select(q => new {questionId = q.Id, questionName = q.Name}).ToList()
});

The most efficient way to calculate total number of all descendants in self reference table

I am looking for an efficient way to calculate total number of all sub categories (all levels) for a given category, without retrieving all the data from the database
Here is my Model:
public class Category
{
public Category()
{
SubCategories = new HashSet<Category>();
}
public Guid CategoryId { get; set; }
public string Title { get; set; }
[ForeignKey("ParentCategory")]
public Guid? ParentCategoryId { get; set; }
//navigation properties
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> SubCategories { get; set; }
}
And my ViewModel:
public class CategoryViewModel
{
public CategoryViewModel()
{
SubCategories = new List<CategoryViewModel>();
}
public Guid CategoryId { get; set; }
[Required]
public string Title { get; set; }
public Guid? ParentCategoryId { get; set; }
public virtual CategoryViewModel ParentCategory { get; set; }
public virtual List<CategoryViewModel> SubCategories { get; set; }
public int TotalOfDirectSubCategories { get; set; }
public int TotalOfAllSubCategories { get; set; }
}
My AutoMapping:
cfg.CreateMap<Category, CategoryViewModel>()
.PreserveReferences()
.ReverseMap();
And finally my EF to retrieve data:
data = _ctx.Categories.Where(x => x.ParentCategoryId == categoryId)
//.Include(x => x.SubCategories)
.ToList();
Let's assume a category can contain thousands of sub categories. Since this will be SOA solution, and data will be passed from Web API into my client app, I don't want to pass all subCategories data with every web API call, so I will only need data for requested category, and corresponding counts.
Let assume we have
'cat 1' > 'cat 1 1' > 'cat 1 1 1'
'cat 1' contains exactly 2 sub categories. 'cat 1 1' contains one sub category. I can retrieve 'cat 1' data by calling web API method 'getCategory(null)', and to retrieve 'cat 1 1' data I would call 'getCategory(GuidOfCat11)'
For 'cat 1':
TotalOfDirectSubCategories - 1
TotalOfAllSubCategories - 2
And again, no subCategories will be included
Here is the best I could implement, do you think there is a better approach:
AutoMapper:
cfg.CreateMap<Category, CategoryViewModel>()
.PreserveReferences()
.ForMember(x => x.TotalOfDirectSubCategories, x => x.MapFrom(z => z.SubCategories.Count))
.ReverseMap();
Populating CategoryViewModel:
public List<CategoryViewModel> GetAllCategoriesAndTasksForParentCategoryAndUser(Guid? categoryId)
{
var data = new List<Category>();
if (categoryId == null) // retrieve root level categories
{
data = _ctx.Categories.Where(x => x.ParentCategoryId == null)
.Include(x => x.SubCategories)
.Include(x => x.Tasks)
.ToList();
}
else
{
data = _ctx.Categories.Where(x => x.CategoryId == categoryId)
.Include(x => x.SubCategories)
.Include(x => x.Tasks)
.ToList();
}
var dataToReturn = Mapper.Map<List<Category>, List<CategoryViewModel>>(data);
foreach (var category in data)
{
var vm = dataToReturn.First(x => x.CategoryId == category.CategoryId);
//subCategories
int totalCount = 0;
SubCategoriesCount(ref totalCount, category);
vm.TotalOfAllSubCategories = totalCount;
}
return dataToReturn;
}
And finally, the method:
private void SubCategoriesCount(ref int subCatCount, Category category)
{
if (category.SubCategories != null || category.SubCategories.Count() != 0)
{
subCatCount += category.SubCategories.Count();
foreach (var subCat in category.SubCategories)
{
SubCategoriesCount(ref subCatCount, subCat);
}
}
}

LINQ select all items of all subcollections that contain a string

I'm using jqueryui autocomplete to assist user in an item selection. I'm having trouble selecting the correct items from the objects' subcollections.
Object structure (simplified) is
public class TargetType
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<SubCategory> SubCategories { get; set; }
public TargetType()
{
SubCategories = new HashSet<SubCategory>();
}
}
public class SubCategory
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<SubTargetType> SubTargetTypes { get; set; }
public SubCategory()
{
SubTargetTypes = new HashSet<SubTargetType>();
}
}
Currently I'm doing this with nested foreach loops, but is there a better way?
Current code:
List<SubTargetResponse> result = new List<SubTargetResponse>();
foreach (SubCategory sc in myTargetType.SubCategories)
{
foreach (SubTargetType stt in sc.SubTargetTypes)
{
if (stt.Name.ToLower().Contains(type.ToLower()))
{
result.Add(new SubTargetResponse {
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name });
}
}
}
You can do using Linq like this
var result = myTargetType.SubCategories
.SelectMany(sc => sc.SubTargetTypes)
.Where(stt => stt.Name.ToLower().Contains(type.ToLower()))
.Select(stt => new SubTargetResponse {
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name });
The above query doesn't work. The following should work, but I'd not recommend that as that'd not be faster or more readable.
var result = myTargetType.SubCategories
.Select(sc => new Tuple<int, IEnumerable<SubTargetType>>
(sc.Id,
sc.SubTargetTypes.Where(stt => stt.Name.ToLower().Contains(type.ToLower()))))
.SelectMany(tpl => tpl.Item2.Select(stt => new SubTargetResponse {
Id = stt.Id,
CategoryId = tpl.Item1,
Name = stt.Name }));
Actually there are 2 different questions.
LINQ select all items of all subcollections that contain a string
Solutions:
(A) LINQ syntax:
var result =
(from sc in myTargetType.SubCategories
from stt in sc.SubTargetTypes.Where(t => t.Name.ToLower().Contains(type.ToLower()))
select new SubTargetResponse
{
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name
})
.ToList();
(B) Method syntax:
var result =
myTargetType.SubCategories.SelectMany(
sc => sc.SubTargetTypes.Where(stt => stt.Name.ToLower().Contains(type.ToLower())),
(sc, stt) => new SubTargetResponse
{
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name
})
.ToList();
Currently I'm doing this with nested foreach loops, but is there a better way?
Well, it depends of what do you mean by "better". Compare your code with LINQ solutions and answer the question. I personally do not see LINQ being better in this case (except no curly braces and different indentation, but a lot of a hidden garbage), and what to say about the second LINQ version in this answer - if that's "better" than your code, I don't know where are we going.

RavenDb Hierarchial Data Index

My data model is:
public class Category
{
public Guid Id { get; set; }
public List<Category> children { get; set; }
}
All I want is to get data from any level of nesting.
I've created an index for that:
public class CategoriesIndex :
AbstractIndexCreationTask<Category, CategoriesIndex.ReduceResult>
{
public class ReduceResult
{
public Guid Id { get; set; }
public Category Category { get; set; }
}
public CategoriesIndex()
{
Map = categories =>
from category in categories
from subcategory in category.Hierarchy(x=>x.children)
select new {
Id = subcategory.Id,
Category = subcategory
};
Store(x => x.Id, FieldStorage.Yes);
Store(x => x.Category, FieldStorage.Yes);
}
}
After running that code i've got an exception
Url: "/indexes/CategoriesIndex"
System.InvalidOperationException: Source code.
What's wrong? And if it is how can I index hierarchial data?
P.S. I can't change data model due to some restrictions
Update
I've got the message of the exception:
public class Index_CategoriesIndex : AbstractViewGenerator
{
public Index_CategoriesIndex()
{
this.ViewText = #"docs.Categories.SelectMany(category => category.Hierarchy(x => x.children), (category, subcategory) => new() {
Id = subcategory.Id,
Category = subcategory
})";
this.ForEntityNames.Add("Categories");
this.AddMapDefinition(docs => docs.Where(__document => __document["#metadata"]["Raven-Entity-Name"] == "Categories").SelectMany((Func<dynamic, IEnumerable<dynamic>>)(category => (IEnumerable<dynamic>)(category.Hierarchy(x => x.children))), (Func<dynamic, dynamic, dynamic>)((category, subcategory) => new { Id = subcategory.Id, Category = subcategory, __document_id = category.__document_id })));
this.AddField("Id");
this.AddField("Category");
this.AddField("__document_id");
}
} e:\RavenDB\Web\Tenants\RavenPortalAuth\IndexDefinitions\TemporaryIndexDefinitionsAsSource\4jy1udpm.0.cs(21,223) :
error CS1977: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type
You should use AbstractIndexCreationTask's Recurse method.
http://ravendb.net/docs/2.0/client-api/querying/static-indexes/indexing-hierarchies

Categories