首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >实体框架LINQ查询性能

实体框架LINQ查询性能
EN

Stack Overflow用户
提问于 2022-11-24 01:14:55
回答 1查看 46关注 0票数 0

大家好,我正在开发一个API,它从一个数据库中返回一个包含餐厅详细信息的菜品,其中包含了餐馆和他们的菜肴。我想知道,通过将第一个查询转换为第二个查询,以下内容是否提高了查询的效率:

代码语言:javascript
复制
from res in _context.Restaurant
join resdish in _context.RestaurantDish
on res.Id equals resdish.RestaurantId
where resdish.RestaurantDishId == dishId

第二:

代码语言:javascript
复制
from resdish in _context.RestaurantDish
where resdish.RestaurantDishId == dishId
join res in _context.Restaurant
on resdish.RestaurantId equals res.Id

我之所以争论这件事,是因为我觉得自己就像第二版的过滤器一样,过滤到单一餐厅的菜上,然后加入它,而不是加入所有的菜,然后过滤。这是正确的吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-11-24 21:38:54

您可以在数据库中使用分析器来捕获这两种情况下的SQL,或者检查EF生成的SQL,您可能会发现这两种情况下的SQL实际上是相同的。归根结底,读者(开发人员)是如何解释逻辑意图的。

就用EF构建高效查询而言,EF是一种ORM,这意味着它提供了在面向对象模型和关系数据模型之间的映射。它不仅仅是一个API,可以将Linq转换成SQL。编写简单高效查询的部分功能是通过使用导航属性和投影。餐点将被视为某一特定餐厅的属性,而餐厅的菜单上则有许多菜肴。这在数据库中形成一对多的关系,导航属性可以在对象模型中映射这种关系:

代码语言:javascript
复制
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; }
}

Restaurant ID的FK属性是可选的,可以配置为使用Shadow属性。(其中一个EF知道并生成,但不在实体中公开)我建议对FKs使用阴影属性,主要是为了避免关系中的两个真相来源。(dish.RestaurantId和dish.Restaurant.RestaurantId)除非重新加载实体,否则更改FK不会自动更新关系,而更新关系在调用SaveChanges之前不会自动更新FK。

现在,如果你想要一种特别的菜,而且它是与之相关的餐厅:

代码语言:javascript
复制
var dish = _context.Dishes
    .Include(d => d.Restaurant)
    .Single(d => d.DishId == dishId);

这将获取两个实体。注意,现在不需要像使用SQL那样手动编写Joins了。EF支持联接,但它只适用于模式未正确标准化/关系且需要映射松散连接的实体/表的非常罕见的情况。(例如使用"OwnerId“的表,该表可以连接到"This”或基于OwnerType之类的鉴别器的“那个”表。)

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

在处理单个实体及其相关数据时,热切加载工作良好,在这些数据中,您需要处理这些关系。例如,如果我想加载一家餐厅并检查,添加/移除盘子,或加载一个Dish,并可能更改该餐厅。然而,在EF和SQL如何在幕后提供相关数据时,急切的加载可能会付出很大的代价。

默认情况下,当您使用Include时,EF将在关联表之间添加一个内连接或左连接。这在涉及的表之间创建了笛卡儿积。如果您有100家餐馆,平均每个餐厅有30道菜,并选择所有100家渴望加载其菜肴的餐馆,则结果查询为3000行。现在,如果一个Dish有类似于Review的内容,并且每个菜平均有5个评论,并且您急切地加载菜品和评论,那么这将是包含15000行的所有三个表的每个列的结果集。希望你能体会到这是如何迅速地失去控制的。然后,EF通过笛卡儿,填充对象图中的相关实体。这可能会导致一些问题,为什么“我的查询在SSMS中运行得很快,而在EF中运行得很慢”,因为EF有很多工作要做,特别是如果它一直在跟踪来自餐馆、菜肴和/或评论的引用来扫描和提供。稍后版本的EF可以通过使用查询拆分来稍微减轻这一点,因此,EF可以使用多个单独的SELECT语句来获取相关数据,这些语句可以执行和处理相当快的速度,但仍然需要大量的数据通过连接,并且需要内存来处理。

但是,大多数情况下,对于每个相关实体,您不需要所有行,也不需要所有列。这就是投影出现的地方,比如使用Select。当我们撤回我们的餐馆名单,我们可能想列出在一个特定城市的餐厅连同他们的前5菜根据用户的评论。我们只需要在这些结果中显示RestaurantId & Name,以及Dish名称和正面评论的#。与从每个表中加载每一列不同,我们可以为此摘要视图为餐馆和菜肴定义视图模型,并将实体投影到这些视图模型中:

代码语言:javascript
复制
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,但只返回填充所需的视图模型所需的字段。这使您可以根据最需要的数据对索引进行优化,还可以减少跨越线路的数据量,以及DB和app服务器上的内存使用量。像Automapper和它的ProjectTo方法这样的库可以进一步简化上述语句,配置如何一次选择到所需的视图模型,然后将整个Select( ... )替换为一个ProjectTo<RestaurantSummaryViewModel>(config),其中"config“是对Automapper配置的引用,它可以解决如何将餐馆及其关联实体转换为所需的视图模型。

在任何情况下,它都应该为您提供一些使用EF的途径,并了解它可以为表带来什么,从而产生(希望:)容易理解和高效的查询表达式。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74554628

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档