如何查询和显示EF 6和MVC 5中的子孙数据?

时间:2017-04-12 19:03:03

标签: entity-framework asp.net-mvc-5

我有一个使用EF 6和MVC 5的应用程序,可以很好地输入数据,但现在当我尝试显示其中的一些时,我遇到了麻烦。我的实体的基本布局如下图所示:

entity layout

我遇到问题的第一部分是查询和过滤数据。我想返回一份存在调查和签收的处所和相关数据清单,但批准却没有。在直接SQL中,现在可以使用的查询是:

SELECT * 
FROM Premises p LEFT OUTER JOIN Approvals a ON a.Id = p.Id
JOIN Surveys s ON s.PremiseId = p.Id
JOIN SignOffs so ON so.Id = s.Id
WHERE a.ApprovedBy IS NULL

我开始使用的代码是这样的:

var premises = Premises.Include(p => p.Approval)
    .Include(p => p.Surveys)
    .Include(p => p.Surveys.Select(s => s.SignOff));

出现 *以返回包括子数据在内的所有记录,但是当我尝试过滤它时,我只获得有签收记录但没有批准的记录,它没有&#39工作。

var premises = Premises.Include(p => p.Approval).Where(p => p.Approval.ApprovedBy == null)
    .Include(p => p.Surveys)
    .Include(p => p.Surveys.Select(s => s.SignOff).Where(s => s.Signature != null));

如果我使用此代码,我会收到此错误:

Include路径表达式必须引用在类型上定义的导航属性。使用虚线路径作为参考导航属性,使用Select运算符作为集合导航属性。 参数名称:路径

我已经改变了这个问题以尝试不同的事情,所以我不确定我做了什么,但我认为第一个Where语句本身可能有用,但是第二个肯定会导致错误。 我如何构建我的查询以使其返回正确过滤的请求数据?

另外,我在上面加上一个星号表示查询出现以返回所有数据和子数据,因为我无法对其进行实际测试。当我试图为此编写我的Razor CSHTML页面时,它没有给我儿童和孙子数据的智能感知,如果我输入我认为它应该是我会得到错误。如何在页面上引用此数据?

2 个答案:

答案 0 :(得分:1)

您不能像这样使用Include(),它只适用于指定加载导航属性,而不是指定在导航属性为某事时加载实体(在您的情况下不为null)。

要进行过滤,我建议这样:

var premises = Premises.Include(p => p.Approval).Include(p => p.Surveys).Include(p => p.Surveys.Select(s => s.SignOff))
               .Where(p=>p.Approval.ApprovedBy!=null && p.Surveys.Any(s=>s.SignOff.Signature!=null));

基本上,包含和过滤与彼此无关。使用includes,您只需指定要加载的内容,您仍然可以在原始实体集上使用过滤器。

答案 1 :(得分:1)

你混淆了Include LINQ方法的作用。它只告诉EF热切地加载这种关系,如果你的查询本身利用了这种关系,这实际上是不必要的;在这种情况下,EF将默认包含关系。

它没有做的是允许你过滤这些关系。例如,在代码的这一部分中:

.Include(p => p.Surveys.Select(s => s.SignOff).Where(s => s.Signature != null));

where子句适用于Premises不是 SignOff,您似乎在想。换句话说,Where过滤了要查询的主表格,而不是您所包含的表格。

这里有两条前进的道路。您只需按重要部分过滤Premises,即:

var premises = Premises.Where(p => p.Approval.ApprovedBy == null && p => p.Surveys.Any(s => s.SignOff.Signature != null));

这将仅返回这些条件为真的前提,但所包含的Surveys集合将包含与每个前提相关的所有调查,而不仅仅是具有空签收签名的调查。

如果您还需要过滤相关项目,则必须明确加载它们:

foreach (premise in premises)
{
    context.Entry(premise) 
        .Collection(p => p.Surveys) 
        .Query() 
        .Where(s => s.SignOff.Signature != null) 
        .Load();
}

有两点需要注意:

  1. 由于必须如何应用此查询的性质,因此无法对所有前提执行此操作。您必须遍历该处所并明确加载每个Surveys集合。

  2. 由于这会发出新查询,因此您希望避免在此明显加载之前懒惰或急切地加载Surveys集合。否则,您需要两次查询相同的信息,这是非常低效的。确保这一点的最简单方法是从集合属性中删除virtual关键字。但是,如果您这样做,那么您将不得不急于或显式加载该集合,否则它将为null。有关详细信息,请参阅:https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx