繁体   English   中英

实体框架 LINQ SQL 查询性能

[英]Entity Framework LINQ SQL Query Performance

大家好,我正在研究 API,它从包含餐厅及其菜肴的数据库中返回一道菜及其餐厅详细信息。 我想知道以下是否通过将第一个转换为第二个来使查询有效:

from res in _context.Restaurant
join resdish in _context.RestaurantDish
on res.Id equals resdish.RestaurantId
where resdish.RestaurantDishId == dishId

第二:

from resdish in _context.RestaurantDish
where resdish.RestaurantDishId == dishId
join res in _context.Restaurant
on resdish.RestaurantId equals res.Id

我争论这个的原因是因为我觉得第二个版本过滤单个餐厅菜,然后加入它,而不是加入所有菜然后过滤。 这样对吗?

您可以在数据库中使用探查器来捕获两种情况下的 SQL,或者检查 EF 生成的 SQL,您可能会发现两种情况下的 SQL 几乎完全相同。 它归结为读者(开发人员)如何解释逻辑的意图。

就EF的构建有效查询而言,EF是ORM,其意思是在面向对象的model之间提供给map编写简单高效的查询是通过使用导航属性和投影。 菜肴将被视为特定餐厅的财产,而餐厅的菜单上有许多菜肴。 这个 forms 在数据库中是一个一对多的关系,而导航属性可以 map 这个关系在你的 object model:

public class Restaurant
{
    [Key]
    public int RestaurantId { get; set; }
    // ... other fields

    public virtual ICollection<Dish> Dishes { get; set; } = new List<Dish>();
}

public class Dish
{
    [Key]
    public int DishId { get; set; }


    //[ForeignKey(nameof(Restaurant))]
    //public int RestaurantId { get; set; }

    public virtual Restaurant Restaurant { get; set; }
}

餐厅 ID 的 FK 属性是可选的,可以配置为使用影子属性。 (EF 知道并生成,但未在实体中公开)我建议对 FK 使用影子属性,主要是为了避免关系的 2 个真实来源。 (dish.RestaurantId 和 dish.Restaurant.RestaurantId)更改 FK 不会自动更新关系,除非您重新加载实体,并且更新关系不会自动更新 FK,直到您调用SaveChanges

现在如果你想得到一道特定的菜和它相关的餐厅:

var dish = _context.Dishes
    .Include(d => d.Restaurant)
    .Single(d => d.DishId == dishId);

这将获取两个实体。 请注意,现在不需要像使用 SQL 那样手动编写联接。EF 支持联接,但它应该只在极少数情况下使用,即架构未正确规范化/关系并且您需要 map 松散联接的实体/表。 (例如使用“OwnerId”的表可以根据 OwnerType 等鉴别器连接到“This”或“That”表。)

如果您离开.Include(d => d.Restaurant)并在 DbContext 上启用了延迟加载,那么当代码第一次尝试访问 dish.Restaurant 时,EF 将尝试自动加载 Restaurant。 这提供了一个 safety.net,但在许多情况下可能会导致一些严重的性能损失,因此应该避免或将其视为 safety.net,而不是拐杖。

在处理需要处理这些关系的单个实体及其相关数据时,预加载效果很好。 例如,如果我想加载一个餐厅并查看、添加/删除菜肴,或者加载一个菜肴并可能更改餐厅。 但是,在 EF 和 SQL 如何在幕后提供相关数据方面,预加载可能会付出巨大的代价。

默认情况下,当您使用Include时,EF 将在关联表之间添加 INNER 或 LEFT 联接。 这会在相关表之间创建笛卡尔积。 如果您有 100 家餐厅,每家平均有 30 道菜,并且 select 所有 100 家餐厅都急切地加载他们的菜,则生成的查询是 3000 行。 现在,如果一道菜有类似 Reviews 的内容,并且每道菜平均有 5 条评论,并且您急切地加载 Dishes 和 Reviews,那么这将是所有三个表中每一列的结果集,总共 15000 行。 希望您能理解它是如何快速失控的。 然后 EF 通过该笛卡尔坐标并填充 object 图中的关联实体。 这可能会引发以下问题:为什么“我的查询在 SSMS 中运行速度很快,但在 EF 中运行速度很慢”,因为 EF 可能有很多工作要做,尤其是如果它一直在跟踪餐厅、菜肴和/或评论的引用以扫描和提供。 EF 的更高版本可以通过使用查询拆分来帮助缓解这种情况,因此 EF 可以使用多个单独的 SELECT 语句来获取相关数据,这些语句可以更快地执行和处理,但仍然需要很多通过网络传输的数据需要 memory 才能实现。

但大多数时候,您不需要所有行,也不需要每个相关实体的所有列。 这就是投影的用武之地,例如使用Select 当我们拉回我们的餐厅列表时,我们可能想要列出给定城市的餐厅以及基于用户评论的前 5 道菜。 我们只需要在这些结果中显示 RestaurantId 和名称,以及菜品名称和正面评论数。 我们可以为这个摘要视图定义一个 Restaurants 和 Dishes 视图 model,而不是从每个表加载每一列,并将实体投影到这些视图模型:

public class RestaurantSummaryViewModel
{
    public int RestaurantId { get; set; }
    public string Name { get; set; }
    public ICollection<DishSummaryViewModel> Top5Dishes { get; set; } = new List<DishSummaryViewModel>();
}

public class DishSummaryViewModel
{
    public string Name { get; set; }
    public int PositiveReviewCount {get; set; }
}

var restaurants = _context.Restaurants
    .Where(r => r.City.CityId == cityId)
    .OrderBy(r => r.Name)
    .Select(r => new RestaurantSummaryViewModel
    {
        RestaurantId = r.RestaurantId,
        Name = r.Name,
        Top5Dishes = r.Dishes
            .OrderByDescending(d => d.Reviews.Where(rv => rv.Score > 3).Count())
            .Select(d => new DishSummaryViewModel
            {
                Name = d.Name,
                PositiveReviewCount = d.Reviews.Where(rv => rv.Score > 3).Count()
            }).Take(5)
            .ToList();
    }).ToList();

请注意,上面的 Linq 示例没有使用Join甚至Include 如果您遵循一组基本规则以确保 EF 可以计算出您想要投影到 SQL 的内容,您就可以完成相当一部分生成更高效的查询。 上面的语句将生成 SQL 以跨相关表运行,但只会返回填充所需视图模型所需的字段。 这使您可以根据最常需要的数据调整索引,还可以减少传输的数据量,以及 memory 在数据库和应用程序服务器上的使用量。 Automapper 之类的库及其ProjectTo方法可以进一步简化上述语句,将 how to select 配置一次到所需视图 model 中,然后将整个Select(... )替换为ProjectTo<RestaurantSummaryViewModel>(config)其中“config”是对 Automapper 配置的引用,它可以解决如何将 Restaurants 及其关联的实体转换为所需的视图模型。

无论如何,它应该为您提供一些途径来探索 EF 并了解它可以为表带来什么以生成(希望:)易于理解且高效的查询表达式。

暂无
暂无

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

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