简体   繁体   English

ASP.NET MVC LINQ实体框架递归

[英]ASP.NET MVC LINQ Entity Framework recursive

I'm not sure how to write LINQ query. 我不确定如何编写LINQ查询。 I have these models: 我有以下模型:

class Category
{
    ICollection<Thread> Threads {get;set;}
    ICollection<Category> SubCategories {get;set;}
}

class Thread 
{
    Category Category {get;set;}
    //Some Stuff
}

So, there could be categories linked like - 因此,可能会有类似的类别链接-

  • Category1 类别1
  • Category2 类别2
    • Category3 类别3
    • Category4 类别4
      • Category5 类别5
    • Category6 类别6

I want find all threads linked to Category2 and it SubCategories(3, 4, 5 ). 我想找到链接到产品组别和它的子类别的所有线程(3,4,5)。
I thought about just take Category1 form db, and using C# recursive function build List of threads i need, but i feel it's bad idea. 我考虑过只采用Category1形式的db,并使用C#递归函数build我需要的线程列表,但是我觉得这不是一个好主意。

Any ideas or links would be great. 任何想法或链接都很好。 Thank you! 谢谢! There code, but there is Topics(in Threads), i didnt mention it couse it's not rly matter(at least i think so) 有代码,但有主题(在线程中),我没有提到它,因为这不是一件小事(至少我认为是这样)

public ActionResult ShowCategoryTopics(int id)
{
  var category = db.Categories.Where(x => x.Id == id).FirstOrDefault();
  var topics = GetTopics(category);
  return View();
}
public List<Topic> GetTopics(Category category)
{
    List<Topic> topics = new List<Topic>();

    if (!category.IsDeleted && !category.IsHidden)
        return null;

    foreach (Thread thread in category.Threads)
    {
        topics.AddRange(thread.Topics.Where(x => !x.IsDeleted).ToList());
    }

    foreach(Category childCategory in category.SubCategories)
    {
        topics.AddRange(GetTopics(childCategory));
    }

        return topics;
}

While EF can load joined records lazily and transparently, it can't load recursive joined records cause it's too complicate. 尽管EF可以延迟和透明地加载联接的记录,但由于过于复杂而无法加载递归联接的记录。

So, first of all, remove the Category.Threads navigation property: 因此,首先,删除Category.Threads导航属性:

public class Category
{
    public int Id { get; set; }

    public int? ParentId { get; set; }

    // you can remove the attribute
    [ForeignKey(nameof(ParentId))]
    public virtual Category Parent { get; set; }

    public string Title { get; set; }

    public virtual ICollection<Category> SubCategories { get; set; } = new HashSet<Category>();
}

public class Thread
{
    public int Id { get; set; }

    public int CategoryId { get; set; }

    // you can remove the attribute
    [ForeignKey(nameof(Category))]
    public Category Category { get; set; }

    public string Title { get; set; }
}

Now you can use Common Table Expressions to recursive query and Database.SqlQuery<TElement> method to load the result of the query. 现在,您可以使用“ 公共表表达式”来递归查询,并使用Database.SqlQuery<TElement>方法加载查询结果。

This is the SQL query to get all Threads corresponded to the specified @CategoryId and all its subcategories: 这是SQL查询,用于获取与指定的@CategoryId及其所有子类别相对应的所有线程:

WITH RecursiveCategories(Id, ParentId, Title)
AS
(
    SELECT Id, ParentId
    FROM dbo.Categories AS c1
    WHERE Id = @CategoryId
    UNION ALL
    SELECT Id, ParentId
    FROM dbo.Categories AS c2
    INNER JOIN c1 ON c2.ParentId = c1.Id
)
SELECT th.*
FROM dbo.Threads AS th
WHERE th.CategoryId IN (SELECT Id FROM RecursiveCategories)

The method to load threads of specified category recursively: 递归地加载指定类别的线程的方法:

public IEnumerable<Thread> GetAllRecursivelyByCategoryId(int categoryId)
{
    var query = @"WITH RecursiveCategories(Id, ParentId, Title)
                  AS
                  (
                      SELECT Id, ParentId
                      FROM dbo.Categories AS c1
                      WHERE Id = @CategoryId
                      UNION ALL
                      SELECT Id, ParentId
                      FROM dbo.Categories AS c2
                      INNER JOIN c1 ON c2.ParentId = c1.Id
                  )
                  SELECT th.*
                  FROM dbo.Threads AS th
                  WHERE th.CategoryId IN (SELECT Id FROM RecursiveCategories)";

    var parameter = new SqlParameter("CategoryId", categoryId);

    return _dbContext.Database
                     .SqlQuery<Thread>(query, parameter)
                     .AsEnumerable();
}

This method runs the recursive query and maps the result to enumerable of threads. 此方法运行递归查询,并将结果映射到可枚举的线程。 Here is only one request to the SQL server, and the response contains only necessary threads. 这只是对SQL Server的一个请求,并且响应仅包含必要的线程。

The way to do this all in database would be to use a recursive Common Table Expression (CTE) to extract all the category hierarchy. 在数据库中完成所有操作的方法是使用递归公用表表达式(CTE)提取所有类别层次结构。 However this is a bit difficult to implement using Linq without resorting to direct SQL. 但是,如果不使用直接SQL,使用Linq很难实现。

As you state there will only be about 100 or so categories it may me simpler to do the category extraction in the code rather than database. 如您所言,类别大约只有100个左右,所以用代码而不是数据库进行类别提取可能更简单。

I'm assuming you have the foreign key columns as wells as the navigation properties. 我假设您具有外键列以及导航属性。

First a Helper function, converts a list of categories to an enumerable of nested ids; 首先是一个Helper函数,将类别列表转换为可枚举的嵌套ID;

static IEnumerable<int> GetCategoryIds(IList<Category> categories, int? targetId) {
  if (!targetId.HasValue) {
    yield break;
  }
  yield return targetId;
  foreach (var id in categories.Where(x => x.ParentId==targetId).SelectMany(x => GetCategoryIds(x.Id))) {
    yield return id;
  } 
}

Now your query 现在您的查询

var ids = GetCategoryIds(db.Categories.ToList(), 2).ToList();
var threads = db.Threads.Where(x => ids.Contains(x.CategoryId));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM