首页 > 解决方案 > Linq 连接不包括实体框架中子表中的数据

问题描述

我正在尝试加入几个表并返回列表中所有表的所有数据。

这是我尝试过的查询

List<TblCrrequests> mycr = (from ep in _context.TblCrrequests
                            join e in _context.TblCrExternalBudget on ep.CrId equals e.CrId
                            join i in _context.TblCrInternalbudget on ep.CrId equals i.CrId
                            join t in _context.TblExternalBudgetTypes on e.TypeId equals t.TypeId
                            where ep.ProjectCode == pcode
                            select ep)
                            .OrderByDescending(d => d.RequestedOn)
                            .Take(8).ToList<TblCrrequests>();

但是在这个列表变量中执行时,它只包含主表的数据。将子表数组显示为空。如何修改查询以从主表和子表以及列表变量中获取所有这些详细信息。我对linq很陌生..

我试过 select ep,e,i,t 没有用。另外,如果我不需要一张表中的所有列,这可能吗?

这是我的班级辩护

public partial class TblCrrequests
{
        public TblCrrequests()
        {
            TblCrExternalBudget = new HashSet<TblCrExternalBudget>();
        }

        public int CrId { get; set; }
        public string ProjectCode { get; set; }
       
        public virtual ICollection<TblCrExternalBudget> TblCrExternalBudget { get; set; }
}

public partial class TblCrExternalBudget
{
        public int? CrId { get; set; }
        public int? TypeId { get; set; }

        public virtual TblCrrequests Cr { get; set; }
        public virtual TblExternalBudgetTypes Type { get; set; }
}

public partial class TblExternalBudgetTypes
{
        public TblExternalBudgetTypes()
        {
            TblCrExternalBudget = new HashSet<TblCrExternalBudget>();
        }

        public int TypeId { get; set; }
        public string TypeName { get; set; }

        public virtual ICollection<TblCrExternalBudget> TblCrExternalBudget { get; set; }
}

标签: c#entity-frameworklinq

解决方案


唉,你忘了告诉我们你有什么课程。你也没有告诉我们表之间的关系。我不得不从你的用法中猜测。您提出的下一个问题考虑提供此信息。

看来你有一张桌子Requests,还有一张桌子InternalBudgetsRequests和之间似乎存在一对多的关系InternalBudgets:每个Request都有零个或多个InternalBudgets,每个都InternalBudget恰好属于一个Request,即外键所指的那个。

Requests和之间似乎存在类似的一对多ExternalBudgets

ExternalBudgets最后还有和之间的一对多ExternalBudgetTypes。就我个人而言,我期望一个多对多的关系: everyExternalBudgetType被 zero 或 more 使用ExternalBudgets。这不会彻底改变答案。

如果您遵循实体框架约定,您将拥有类似于以下的类:

class Request
{
    public int Id {get; set;}
    ...

    // Every Request has zero or more InternalBudgets (one-to-many)
    public virtual ICollection<InternalBudget> InternalBudgets {get; set;}

    // Every Request has zero or more ExternalBudgets (one-to-many)
    public virtual ICollection<ExternalBudged> ExternalBudgets {get; set;}
}

内部和外部预算:

class InternalBudget
{
    public int Id {get; set;}
    ...

    // Every InternalBudget belongs to exactly one Request, using foreign key
    public int RequestId {get; set;}
    public virtual Request Request {get; set;}
}

class ExternalBudget
{
    public int Id {get; set;}
    ...

    // Every ExternalBudget belongs to exactly one Request, using foreign key
    public int RequestId {get; set;}
    public virtual Request Request {get; set;}

    // Every ExternalBudget has zero or more ExternalBudgetTypess (one-to-many)
    public virtual ICollection<ExternalBudgetType> ExternalBudgetTypes {get; set;}

}

最后ExternalBudgetTypes

class ExternalBudgetType
{
    public int Id {get; set;}
    ...

    // Every ExternalBudgetType belongs to exactly one ExternalBudget, using foreign key
    public int ExternalBudgetId{get; set;}
    public virtual ExternalBudget ExternalBudget {get; set;}
}

因为您遵守约定,所以这就是实体框架检测您的表、表之间的关系以及主键和外键所需的全部内容

在实体框架中,表的列由非虚拟属性表示。虚拟属性表示表之间的关系(一对多、多对多)

外键是表中的一列。因此它是非虚拟的。外键引用的对象不是表的一部分,因此 if 是虚拟的。

xxx-to-many 关系中的“Many”方应该用 实现ICollection<...>,而不是用IList<...>。Ilist 提供了数据库中未定义的功能:

Requests fetchedRequest = ...
InternalBudget internalBudget = fetchedRequest[3];

你能说哪个 internalBudget 有列表索引 [3] 吗?最好不要提供未定义的功能。为您提供在数据库中具有定义含义的所有功能ICollection<...>的可能性。Add / Delete / Count

最后一个提示:在你的标识符中使用复数名词来指代序列;使用单数名词来指代这些序列中的元素。这使得 LINQ 语句更容易理解。

回到你的问题

要求给定一个项目代码,给我具有此项目代码的请求,以及它们的内部和外部预算的几个属性

简单方法:使用虚拟 ICollections

var projectCode = ...
var result = dbContext.Requests

    // Keep only the Requests that have projectCode
    .Where (request => request.ProjectCode == projectCode)

    // order the remaining Requests by descending RequestedOn date
    .OrderByDescending(request => request.RequestedOn)

    // Select the properties that you want:
    .Select(request => new
    {
        // Select only the Request properties that you plan to use
        Id = request.Id,
        Name = request.Name,
        ...

        // Internal budgets of this Request
        InternalBudgets = request.InternalBudgets.Select(budget => new
        {
             // Again, only the properties that you plan to use
             Id = budget.Id,
             ...

             // not Needed, you know the value
             // RequestId = budget.RequestId,
        })
        .ToList(),

        // External budgets of this Request
        ExternalBudgets = request.ExternalBudgets.Select(budget => new
        {
             ...

             ExternalBudgetTypes = budget.ExternalBudgetTypes
                 .Select(budgetType => ...)
                 .ToList(),
        })
        .ToList(),
    });

换句话说:从所有请求中,仅保留属性 ProjectCode 的值等于 projectCode 的那些请求。按属性 RequestedOn 的降序排列剩余的请求。最后选择请求的几个属性,它的内部预算和它的外部预算。

实体框架知道表之间的关系,并且知道如何在正确的 (Group-)join 中转换使用您的虚拟属性。

注意:结果与您的解决方案略有不同。您的解决方案将给出:

Request InternalBudget (for simplicity: leave out Externalbudget / type
  01         10
  02         12
  04         11
  01         14
  01         15
  02         13

我的解决方案给出:

  • 请求 01 及其 InternalBudgets {10, 14, 15}
  • 请求 02 及其 InternalBudgets {12, 13}
  • 没有任何 InternalBudgets 的请求 05
  • 请求 04 及其 InternalBudget {11}

恕我直言,这更加直观和高效:每个请求项目的属性仅传输一次。

请注意,您还将获得没有内部/外部预算或类型的请求。如果您不想要它们,请使用 aWhere(... => ...Count != 0)将它们过滤掉。

如果您不想要“项目及其子项目”(实际上是 GroupJoin),而是标准联接,请使用SelectMany展平结果

自己加入

有些人不喜欢使用virtual ICollection<...>. 他们更喜欢自己做(Group-)Join。在方法语法中,多于两个表的联接看起来很糟糕,幸运的是您不必进行联接。

var result = dbContext.Requests
    .Where (request => request.ProjectCode == projectCode)
    .OrderByDescending(request => request.RequestedOn)
    .Select(request => new
    {
        // Request properties:
        Id = request.Id,
        Name = request.Name,
        ...

        // Internal budgets:
        InternalBudgets = dbContext.InternalBudgets
            .Where(budget => budget.RequestId == request.Id)
            .Select(budget => new
            {
                 Id = budget.Id,
                 ...
            })
            .ToList(),

        // External budgets:
        ExternalBudgets = dbContext.ExternalBudgets
            .Where(budget => budget.RequestId == request.Id)
            .Select(budget => new
            {
                 Id = budget.Id,
                 ...

                 BudgetTypes = dbContext.BudgetTypes
                     .Where(budgetType => budgetType.ExternalBudgetId == budget.Id)
                     .Select(budgetType => ...)
                     .ToList(),
            })
            .ToList(),
      });

我不确定您是否可以说服您的项目负责人相信这种方法比使用虚拟 ICollections 的另一种方法更易读、更可重用、更容易测试和更改。


推荐阅读