EF:通过where子句包含+ SubIncludes

时间:2018-08-31 21:02:17

标签: c# entity-framework include where-clause

这是这里问题的后续内容:Include with where clause。这个问题想找到所有在公交车上醒着的乘客

没有关于乘客的WHERE条款,这很简单,就像这样:

var result = Context.Busses.Where(x => x.IsDriving)
    .Include(x => x.Passengers);

没有关于乘客的WHERE条款,将乘客的子关系包括在内也非常简单,就像这样:

var result = Context.Busses.Where(x => x.IsDriving)
    .Include(x => x.Passengers.CarryOns)
    .Include(x => x.Passengers.Luggage);

但是,这个问题要求在Navigation属性上使用WHERE子句。  除子关系外,对上一个问题的回答非常完美:

var result = Context.Busses.Where(x => x.IsDriving)
    .Select(bus => new {bus, Passengers = bus.Passengers.Where(x => x.Awake)})
    .AsEnumerable().Select(x => x.bus).ToList();

但是您如何使用这种方法将乘客的随身行李和行李也包括在内?乘客不可查询,因此您目前无法Include。我尝试过类似的操作,但是第一部分只是被第二部分覆盖:

var bussesQuery = Context.Busses.Where(x => x.IsDriving)
    .Include(x => x.Passengers.CarryOns)
    .Include(x => x.Passengers.Luggage);
// var check = bussesQuery.ToList(); 
// The Sub-Relationship data is included here, but the Passengers are not filtered.
var result = bussesQuery
    .Select(bus => new {bus, Passengers = bus.Passengers.Where(x => x.Awake)})
    .AsEnumerable().Select(x => x.bus).ToList();
// The Sub-Relationship data is missing, but the Passengers are filtered

2 个答案:

答案 0 :(得分:0)

通过查看您先前的查询,我发现您从N + 1的极限转到了单个查询的极限。您曾经有很多查询,现在您想拥有一个查询,但是请考虑实际情况。要获取数据实体框架,需要交叉连接所有实体,因此,对于每个包含的实体,您都会在结果中获得额外的列,并且结果与该包含物交叉连接。

比方说,您有5辆驾驶公共汽车,30名清醒乘客和每辆公共汽车15个行李,因此,您得到了x行李x乘客= 2250条记录,每条记录都包含乘客和行李数据。如果您用单独的查询来查询旅客和行李,则记录将更少(5 * 30 + 5 * 15 = 225),每个实体将被提取一次。

执行一个大查询以返回所有内容并不是一个好主意-它比较慢,难以维护并且不值得您花时间。只需查询醒着的乘客,然后查询行李。

答案 1 :(得分:0)

要获取答案,请向下滚动到答案部分。

免责声明:我喜欢EF。对于系统中99.999%的呼叫,我可以最快地编写代码(LINQ),而OR-Mapping是最快的系统。而且,生成查询(虽然让人困惑)与执行SQL相比,执行计划要快得多。但这不是事实。

研究科

首先说:查看我的最终请求的原始SQL是这样的:

SELECT * FROM [Busses] [bus]
LEFT JOIN [Passengers] [passenger] ON [passenger].[BusID] = [bus].[BusID] AND [passenger].[Awake] <> 1
    LEFT JOIN [CarryOns] [carryOn] ON [carryOn].[PassengerID] = [passenger].[PassengerID]
    LEFT JOIN [Luggages] [luggage] ON [luggage].[PassengerID] = [passenger].[PassengerID]
WHERE [bus].[IsDriving] = 1

当然,如果EF为这些结果生成某些内容,则需要嵌套和关键字段来知道如何映射它们。没什么。

不幸的是,为了通过一次点击数据库来实现这一目标,我必须执行以下操作:

var busses = context.Set<BusEntity>().Where(x => x.IsDriving);
var passengers = context.Set<PassengerEntity>().Where(x => x.Awake);
var carryOns = context.Set<CarryOnEntity>();
var luggages = context.Set<LuggageEntity>();

var passengerJoins = passengers.GroupJoin(
        carryOns,
        x => x.PassengerID,
        y => y.PassengerID,
        (x, y) => new { Passenger = x, CarryOns = y }
    )
    .SelectMany(
        x => x.CarryOns.DefaultIfEmpty(),
        (x, y) => new { Passenger = x.Passenger, CarryOns = x.CarryOns }
    ).GroupJoin(
        luggages,
        x => x.Passenger.PassengerID,
        y => y.PassengerID,
        (x, y) => new { Passenger = x.Passenger, CarryOns = x.CarryOns, Luggages = y }
    )
    .SelectMany(
        x => x.Luggages.DefaultIfEmpty(),
        (x, y) => new { Passenger = x.Passenger, CarryOns = x.CarryOns, Luggages = x.Luggages }
    );

var bussesToPassengers = busses.GroupJoin(
        passengerJoins,
        x => x.BusID,
        y => y.Passenger.BusID,
        (x, y) => new { Bus = x, Passengers = y }
    )
    .SelectMany(
        x => x.Passengers.DefaultIfEmpty(),
        (x, y) => new { Bus = x.Bus, Passengers = x.Passengers }
    )
    .GroupBy(x => x.Bus);

var rez = bussesToPassengers.ToList()
    .Select(x => x.First().Bus)
    .ToList();

我没有抱怨EF生成的SQL,但是一条SQL语句只有几百行。我对此进行了修改,删除了SELECT列,并更改了一些ID以匹配该问题,它是这样的:

SELECT *
FROM ( SELECT *
    FROM   (SELECT *
        FROM ( SELECT DISTINCT *
            FROM  [dbo].[Bus] AS [Extent1]
            LEFT OUTER JOIN  (SELECT *
                FROM    [dbo].[Passenger] AS [Extent2]
                LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent3] ON [Extent2].[PassengerId] = [Extent3].[PassengerId]
                LEFT OUTER JOIN [dbo].[Luggages] AS [Extent4] ON [Extent2].[PassengerId] = [Extent4].[PassengerId]
            WHERE [Extent1].[IsDriving] = 1
        )  AS [Distinct1] ) AS [Project2]
    OUTER APPLY  (SELECT *
        FROM   (SELECT *
            FROM  [dbo].[Bus] AS [Extent6]
            LEFT OUTER JOIN  (SELECT *
                FROM    [dbo].[Passenger] AS [Extent7]
                LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent8] ON [Extent7].[PassengerId] = [Extent8].[PassengerId]
                LEFT OUTER JOIN [dbo].[Luggages] AS [Extent9] ON [Extent7].[PassengerId] = [Extent9].[PassengerId]
            WHERE ([Extent6].[IsDriving] = 1) AND ([Project2].[BusId] = [Extent6].[BusId]) ) AS [Project3]
        OUTER APPLY  (SELECT *
            FROM     [dbo].[Passenger] AS [Extent11]
            LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent12] ON [Extent11].[PassengerId] = [Extent12].[PassengerId]
            LEFT OUTER JOIN [dbo].[Luggages] AS [Extent13] ON [Extent11].[PassengerId] = [Extent13].[PassengerId]
            LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent15] ON [Extent11].[PassengerId] = [Extent15].[PassengerId]
            WHERE ([Extent11].[IsAwake] = 1) AND ([Project3].[BusId] = [Extent11].[BusId])
        UNION ALL
            SELECT *
            FROM     [dbo].[Passenger] AS [Extent16]
            LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent17] ON [Extent16].[PassengerId] = [Extent17].[PassengerId]
            LEFT OUTER JOIN [dbo].[Luggages] AS [Extent18] ON [Extent16].[PassengerId] = [Extent18].[PassengerId]
            WHERE ([Extent16].[IsAwake] = 1) AND ([Project3].[BusId] = [Extent16].[BusId])
)  AS [Project7]
ORDER BY ........................

对于我的个人测试数据,“我的手写SQL查询”返回54行,而“ EF生成的查询”返回约30,000行。因此,如果仅考虑通过无线进行数据传输的时间增加,那是不可接受的。

答案部分

答案是:您可以在单个调用中使用Linq to Entities(在DB上)和Linq to Objects(在代码中)来实现结果,但效果不佳。相反,您可以选择多个性能更好的呼叫,包括更少的有线传输数据,更可读的生成查询和更易理解的代码。

最好的选择是执行多个查询。这就是我的做法:

var bus = context.Set<BusEntity>().Where(x => x.IsDriving).ToList();
var busIDs = bus.Select(x => x.BusID).ToList();
var passengers = context.Set<PassengerEntity>().Where(x => x.IsAwake && busIDs.Contains(x.BusID)).ToList();
var passengerIDs = passengers.Select(x => x.PassengerID).ToList();
var carryOns = context.Set<CarryOnEntity>().Where(x => passengerIDs.Contains(x.PassengerID)).ToList();
var luggages = context.Set<LuggageEntity>().Where(x => passengerIDs.Contains(x.PassengerID)).ToList();
passengers.ForEach(x => {
    x.CarryOns = carryOns.Where(y => y.PassengerID == x.PassengerID).ToList();
    x.Luggages = luggages.Where(y => y.PassengerID == x.PassengerID).ToList();
});
bus.ForEach(x => x.Passengers = passengers.Where(y => y.BusID == x.BusID).ToList());

这产生了4个呼叫。 SQL总共有40行。我对此进行了修改,删除了SELECT列,并更改了一些ID以匹配该问题,它是这样的:

SELECT * FROM [dbo].[Busses] AS [Extent1]
    WHERE [Extent1].[IsDriving] = 1

SELECT * FROM [dbo].[Passengers] AS [Extent1]
    WHERE ([Extent1].[Awake] = 1) AND ([Extent1].[BusID] IN (......................))

SELECT * FROM [dbo].[CarryOns] AS [Extent1]
    WHERE [Extent1].[PassengerID] IN (......................)

SELECT * FROM [dbo].[Luggages] AS [Extent1]
    WHERE [Extent1].[PassengerID] IN (......................)

EF生成的查询在4个往返调用中总共返回约100行。因此,这意味着要对数据库进行4次调用,但是这些调用非常小,可读且非常快速。

我没有计时,但是每当我在此答案的代码上方的断点处暂停,并在结果的另一端按F5时,它都是即时的。当我在研究中为Single-Call做同样的事情时,花了相当长的一秒钟或更长时间,明显的滞后。