【问题标题】:ASP.NET MVC LINQ Entity Framework recursiveASP.NET MVC LINQ 实体框架递归
【发布时间】:2016-09-05 15:20:39
【问题描述】:

我不确定如何编写 LINQ 查询。我有这些模型:

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

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

所以,可能会有一些类别链接,例如 -

  • 类别1
  • 类别2
    • 类别3
    • 类别4
      • 类别5
    • 6 类

我想找到所有链接到 Category2 及其 SubCategories(3, 4, 5) 的线程。
我想过只取Category1表单db,并使用C#递归函数构建我需要的线程列表,但我觉得这是个坏主意。

任何想法或链接都会很棒。谢谢! 有代码,但是有主题(在线程中),我没有提到它,因为这不是问题(至少我是这么认为的)

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;
}

【问题讨论】:

  • 你能告诉我们你到目前为止做了什么吗?
  • 你的数据库有多少个类别?
  • 不多,一开始会有~10个,以后大概有100个(没想到会更多)。

标签: c# asp.net asp.net-mvc entity-framework linq


【解决方案1】:

虽然 EF 可以延迟和透明地加载连接记录,但它无法加载递归连接记录,因为它太复杂了。

所以,首先,删除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; }
}

现在您可以使用Common Table Expressions 进行递归查询,使用Database.SqlQuery&lt;TElement&gt; method 加载查询结果。

这是获取指定@CategoryId及其所有子类别对应的所有线程的SQL查询:

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)

递归加载指定类别线程的方法:

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();
}

此方法运行递归查询并将结果映射到可枚举的线程。这里只有一个对 SQL 服务器的请求,响应中只包含必要的线程。

【讨论】:

    【解决方案2】:

    在数据库中完成这一切的方法是使用递归公用表表达式 (CTE) 来提取所有类别层次结构。然而,如果不使用直接 SQL,使用 Linq 实现这一点有点困难。

    正如您所说,只有大约 100 个左右的类别,在代码而不是数据库中进行类别提取可能更简单。

    我假设您有外键列以及导航属性。

    首先是一个 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;
      } 
    }
    

    现在您的查询

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-17
      • 2020-05-26
      • 2010-09-25
      • 1970-01-01
      • 2012-10-18
      • 2011-02-20
      • 2011-07-21
      • 1970-01-01
      相关资源
      最近更新 更多