简体   繁体   English

实体框架 LINQ SQL 查询性能

[英]Entity Framework LINQ SQL Query Performance

Hello everyone I'm working on an API that returns a dish with its restaurant details from a database that has restaurants and their dishes.大家好,我正在研究 API,它从包含餐厅及其菜肴的数据库中返回一道菜及其餐厅详细信息。 I'm wondering if the following makes the query any efficient by converting the first, to second:我想知道以下是否通过将第一个转换为第二个来使查询有效:

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

Second:第二:

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

The reason why I'm debating this is because I feel like the second version filters to the single restaurant dish, then joining it, rather than joining all dishes then filtering.我争论这个的原因是因为我觉得第二个版本过滤单个餐厅菜,然后加入它,而不是加入所有菜然后过滤。 Is this correct?这样对吗?

You can use a profiler on your database to capture the SQL in both cases, or inspect the SQL that EF generates and you'll likely find that the SQL in both cases is virtually identical.您可以在数据库中使用探查器来捕获两种情况下的 SQL,或者检查 EF 生成的 SQL,您可能会发现两种情况下的 SQL 几乎完全相同。 It boils down to how the reader (developers) interprets the intention of the logic.它归结为读者(开发人员)如何解释逻辑的意图。

As far as building efficient queries in EF goes, EF is an ORM meaning it offers to map between an object-oriented model and a relational data model. It isn't just an API to enable translating Linq to SQL. Part of the power for writing simple and efficient queries is through the use of navigation properties and projection.就EF的构建有效查询而言,EF是ORM,其意思是在面向对象的model之间提供给map编写简单高效的查询是通过使用导航属性和投影。 A Dish will be considered the property of a particular Restaurant, while a Restaurant has many Dishes on its menu.菜肴将被视为特定餐厅的财产,而餐厅的菜单上有许多菜肴。 This forms a One-to-Many relationship in the database, and navigation properties can map this relationship in your object model:这个 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; }
}

The FK propery for the Restaurant ID is optional and can be configured to use a Shadow Property.餐厅 ID 的 FK 属性是可选的,可以配置为使用影子属性。 (One that EF knows about and generates, but isn't exposed in the Entity) I recommend using shadow properties for FKs mainly to avoid 2 sources of truth for relationships. (EF 知道并生成,但未在实体中公开)我建议对 FK 使用影子属性,主要是为了避免关系的 2 个真实来源。 (dish.RestaurantId and dish.Restaurant.RestaurantId) Changing the FK does not automatically update the relationship unless you reload the entity, and updating the relationship does not automatically update the FK until you call SaveChanges . (dish.RestaurantId 和 dish.Restaurant.RestaurantId)更改 FK 不会自动更新关系,除非您重新加载实体,并且更新关系不会自动更新 FK,直到您调用SaveChanges

Now if you wanted to get a particular dish and it's associated restaurant:现在如果你想得到一道特定的菜和它相关的餐厅:

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

This fetches both entities.这将获取两个实体。 Note that there is no need now to manually write Joins like you would with SQL. EF supports Join, but it should only be used in very rare cases where a schema isn't properly normalized/relational and you need to map loosely joined entities/tables.请注意,现在不需要像使用 SQL 那样手动编写联接。EF 支持联接,但它应该只在极少数情况下使用,即架构未正确规范化/关系并且您需要 map 松散联接的实体/表。 (Such as a table using an "OwnerId" that could join to a "This" or a "That" table based on a discriminator such as OwnerType.) (例如使用“OwnerId”的表可以根据 OwnerType 等鉴别器连接到“This”或“That”表。)

If you leave off the .Include(d => d.Restaurant) and have lazy loading enabled on the DbContext, then EF would attempt to automatically load the Restaurant if and when the first attempt of the code to access dish.Restaurant.如果您离开.Include(d => d.Restaurant)并在 DbContext 上启用了延迟加载,那么当代码第一次尝试访问 dish.Restaurant 时,EF 将尝试自动加载 Restaurant。 This provides a safety.net, but can incur some steep performance penalties in many cases, so it should be avoided or treated as a safety.net, not a crutch.这提供了一个 safety.net,但在许多情况下可能会导致一些严重的性能损失,因此应该避免或将其视为 safety.net,而不是拐杖。

Eager loading works well when dealing with single entities and their related data where you will need to do things with those relationships.在处理需要处理这些关系的单个实体及其相关数据时,预加载效果很好。 For instance if I want to load a Restaurant and review, add/remove dishes, or load a Dish and possibly change the Restaurant.例如,如果我想加载一个餐厅并查看、添加/删除菜肴,或者加载一个菜肴并可能更改餐厅。 However, eager loading can come at a significant cost in how EF and SQL provides that related data behind the scenes.但是,在 EF 和 SQL 如何在幕后提供相关数据方面,预加载可能会付出巨大的代价。

By default when you use Include , EF will add an INNER or LEFT join between the associated tables.默认情况下,当您使用Include时,EF 将在关联表之间添加 INNER 或 LEFT 联接。 This creates a Cartesian Product between the involved tables.这会在相关表之间创建笛卡尔积。 If you have 100 restaurants that have an average of 30 dishes each and select all 100 restaurants eager loading their dishes, the resulting query is 3000 rows.如果您有 100 家餐厅,每家平均有 30 道菜,并且 select 所有 100 家餐厅都急切地加载他们的菜,则生成的查询是 3000 行。 Now if a Dish has something like Reviews and there are an average of 5 reviews per dish and you eager load Dishes and Reviews, that would be a resultset of every column across all three tables with 15000 rows in total.现在,如果一道菜有类似 Reviews 的内容,并且每道菜平均有 5 条评论,并且您急切地加载 Dishes 和 Reviews,那么这将是所有三个表中每一列的结果集,总共 15000 行。 You can hopefully appreciate how this can grow out of hand pretty fast.希望您能理解它是如何快速失控的。 EF then goes through that Cartesian and populates the associated entities in the object graph.然后 EF 通过该笛卡尔坐标并填充 object 图中的关联实体。 This can lead to questions about why "my query runs fast in SSMS but slow in EF" since EF can have a lot of work to do, especially if it has been tracking references from restaurants, dishes, and/or reviews to scan through and provide.这可能会引发以下问题:为什么“我的查询在 SSMS 中运行速度很快,但在 EF 中运行速度很慢”,因为 EF 可能有很多工作要做,尤其是如果它一直在跟踪餐厅、菜肴和/或评论的引用以扫描和提供。 Later versions of EF can help mitigate this a bit by using query splitting so instead of JOINs, EF can work out to fetch the related data using multiple separate SELECT statements which can execute and process a fair bit faster, but it still amounts to a lot of data going over the wire and needing memory to materialize to work with. EF 的更高版本可以通过使用查询拆分来帮助缓解这种情况,因此 EF 可以使用多个单独的 SELECT 语句来获取相关数据,这些语句可以更快地执行和处理,但仍然需要很多通过网络传输的数据需要 memory 才能实现。

Most of the time though, you won't need ALL rows, nor ALL columns for each and every related entity.但大多数时候,您不需要所有行,也不需要每个相关实体的所有列。 This is where Projection comes in such as using Select .这就是投影的用武之地,例如使用Select When we pull back our list of restaurants, we might want to list the restaurants in a given city along with their top 5 dishes based on user reviews.当我们拉回我们的餐厅列表时,我们可能想要列出给定城市的餐厅以及基于用户评论的前 5 道菜。 We only need the RestaurantId & Name to display in these results, along with the Dish name and # of positive reviews.我们只需要在这些结果中显示 RestaurantId 和名称,以及菜品名称和正面评论数。 Instead of loading every column from every table, we can define a view model for Restaurants and Dishes for this summary View, and project the entities to these view models:我们可以为这个摘要视图定义一个 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();

Notice that the above Linq example doesn't use Join or even Include .请注意,上面的 Linq 示例没有使用Join甚至Include Provided you follow a basic set of rules to ensure that EF can work out what you want to project down to SQL you can accomplish a fair bit producing far more efficient queries.如果您遵循一组基本规则以确保 EF 可以计算出您想要投影到 SQL 的内容,您就可以完成相当一部分生成更高效的查询。 The above statement would generate SQL to run across the related tables but would only return the fields needed to populate the desired view models.上面的语句将生成 SQL 以跨相关表运行,但只会返回填充所需视图模型所需的字段。 This allows you to tune indexes based on what data is most commonly needed, and also reduces the amount of data going across the wire, plus memory usage on both the DB and app servers.这使您可以根据最常需要的数据调整索引,还可以减少传输的数据量,以及 memory 在数据库和应用程序服务器上的使用量。 Libraries like Automapper and it's ProjectTo method can simplify the above statements even more, configuring how to select into the desired view model once, then replacing that whole Select(... ) with just a ProjectTo<RestaurantSummaryViewModel>(config) where "config" is a reference to the Automapper configuration where it can resolve how to turn Restaurants and their associated entities into the desired view model(s). Automapper 之类的库及其ProjectTo方法可以进一步简化上述语句,将 how to select 配置一次到所需视图 model 中,然后将整个Select(... )替换为ProjectTo<RestaurantSummaryViewModel>(config)其中“config”是对 Automapper 配置的引用,它可以解决如何将 Restaurants 及其关联的实体转换为所需的视图模型。

In any case it should give you some avenues to explore with EF and learning what it can bring to the table to produce (hopefully:) easy to understand, and efficient query expressions.无论如何,它应该为您提供一些途径来探索 EF 并了解它可以为表带来什么以生成(希望:)易于理解且高效的查询表达式。

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

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