Question

I want to display categories and subcategories like so:

Category 1
Subcategory 1
Subcategory 2
Subcategory 3

Category 2
Subcategory 5
Subcategory 6
Subcategory 7

In other words, foreach category, display the subcategories that belong to each one underneath.

My two tables are like so:
Category-
CategoryID
Name

SubCategory-
SubCategoryID
SubCategoryName
CategoryID
I have a foreign key from category to subcategory a one to many.

Here is where I got in the code, which display all sub categories foreach category.

public void displayLinqCategory()
{
    MyDataContext dbm = new MyDataContext();

    var q = from category in dbm.Categories
            join subCat in dbm.SubCategories
            on category.CategoryID equals subCat.CategoryID
            select new { category.Name, subCat.SubCategoryName };

    resultSpan.InnerHtml += "<table>";
    foreach (var c in q)
    {
        resultSpan.InnerHtml += "<tr><td>" + c.Name + "</td></tr>";
        foreach (var s in q)
        {
            resultSpan.InnerHtml += "<tr><td>&nbsp;&nbsp;&nbsp;" + s.SubCategoryName + "</td></td>";
        }

    }
    resultSpan.InnerHtml += "</table>";
}
Was it helpful?

Solution

If you add an into clause it will group the related categories into a collection you can easily iterate over.

Here's how:

using (var dbm = new MyDataContext())
{
    var query = dbm.Categories
                join s in dbm.SubCategories on c.CategoryID equals s.CategoryID
                //group the related subcategories into a collection
                into subCollection
                select new { Category = c, SubCategories = subCollection };

    foreach (var result in query)
    {
        //use result.Category here...

        //now go through the subcategories for this category
        foreach (var sub in result.Subcategories)
        {
            //use sub here...
        }   
    }
}

OTHER TIPS

If you have navigational properties in your model:

MyDataContext dbm = new MyDataContext();

var groups = dbm.SubCategories
          .Select(x=> new { CatName = x.Category.Name, x.SubCategoryName });
          .GroupBy(x=>x.CatName);

resultSpan.InnerHtml += "<table>";
foreach (var group in groups)
{
    resultSpan.InnerHtml += "<tr><td>" + group.Key + "</td></tr>";
    foreach (var s in group)
    {
        resultSpan.InnerHtml += "<tr><td>&nbsp;&nbsp;&nbsp;" + s.SubCategoryName + "</td></td>";
    }

}
resultSpan.InnerHtml += "</table>";

if you didn't add references to your model you still can achieve what you need using GroupJoin

var groups = dbm.Categories
   .GroupJoin(
        dbm.SubCategories,
        x => x.CategoryID,
        x => x.CategoryID,
        (x, y) => new {Category = x.CategoryName, SubCategories = y.Select(s=>s.SubCategoryName)}
);

As you can see, there are a number of "right" answers. Here's how I'd do it:

// Data access belongs in its own area. Don't do it alongside HTML generation.
// Program to an interface so you can mock this repository in unit tests.
public interface ICategoryInfoRepository {
    IEnumerable<CategoryInfo> GetCategoryInfo();
}

public class CategoryInfo {
    public string CategoryName {get;set;}
    public IEnumerable<string> SubCategoryNames {get;set;}
}

public class CategoryInfoRepository : ICategoryInfoRepository 
{
    public IEnumerable<CategoryInfo> GetCategoryInfo()
    {
        // The 'using' clause ensures that your context will be disposed
        // in a timely manner.
        using (var dbm = new MyDataContext())
        {
            // This query makes it pretty clear what you're selecting.
            // The groupings are implied.
            var q = from category in dbm.Categories
                    select new {
                        CategoryName = category.Name,
                        SubCategoryNames = 
                            from subcategory in category.SubCategories
                            select subcategory.Name
                    };
            // Make sure all the data is in memory before disposing the context
            return q.ToList(); 
        }
    }
}
// Since all this method does is convert its input into a string, it would
// be very easy to unit-test.
public string GetCategoriesHtml(IEnumerable<CategoryInfo> categoryInfos) {
    // A StringBuilder will make this HTML generation go much faster
    var sb = new StringBuilder();
    // Don't use tables to represent non-tabular data.
    // This is a list, so let's make it a list.
    // Use CSS to format it to your liking.
    sb.Append("<ul>");
    foreach(var categoryInfo in categoryInfos)
    {
        sb.Append("<li>").Append(categoryInfo.CategoryName).Append("<ul>");
        foreach(var subCategoryName in categoryInfo.SubCategoryNames)
        {
            sb.Append("<li>").Append(subCategoryName).Append("</li>");
        }
        sb.Append("</ul></li>");
    }
    sb.Append("</ul>");
    return sb.ToString();
}

public void DisplayLinqCategory()
{
    // The repository would ideally be provided via dependency injection.
    var categoryInfos = _categoryInfoRepository.GetCategoryInfo();
    resultSpan.InnerHtml = GetCategoriesHtml(categoryInfos);
}

I've made various "improvements" that would make sense in a long-term, real-world project. Feel free to ignore the ones that don't make sense for your particular situation.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top