简体   繁体   English

Entity Framework Core Include 然后 Take 2

[英]Entity Framework Core Include then Take 2

For learning purpose i made a console application using EFCore 3.1出于学习目的,我使用 EFCore 3.1 制作了一个控制台应用程序

i have relationship like this:我有这样的关系:

Person belongs To one Type属于一种类型

Person have many Queries有很多疑问

Query belongs to one Person查询属于一个

Query belongs to a Location查询属于一个位置

this Console application is connected to a Mysql DB Scaffolded into Entities with Pomelo.这个控制台应用程序连接到一个 Mysql 数据库脚手架到实体与 Pomelo。

Now i need to get every Persons from the DB then his related Type and Last Queries ordered by date, each person have hundreds queries and there is thousands persons on this db, so if i try to现在我需要从数据库中获取每个,然后按日期排序他的相关类型和最后查询,每个人都有数百个查询并且这个数据库上有数千人,所以如果我尝试

using (var ctx = new DBContext) 
{
    var persons = ctx.Persons
                  .Include(p => p.Type)
                  .Include(p => p.Queries).ThenInclude(q => q.Location)
}

it will timeout because he's trying to get every queries for each person, like it should be, because i read somewhere in another answer that with the include it's all or nothing.它会超时,因为他正在尝试获取每个人的每个查询,就像它应该的那样,因为我在另一个答案的某个地方读到,包含它是全部或全部。

In my DB i have a double linked view for this purpose, one for get the max_query_id (person_maxquery) for each person_id and one for joining this 4 tables在我的数据库中,我有一个用于此目的的双链接视图,一个用于获取每个 person_id 的 max_query_id (person_maxquery),一个用于加入这 4 个表

SELECT p.*, ty.*, pq.*, loc.* FROM persons p
LEFT JOIN person_maxquery max ON p.id = max.person_id
LEFT JOIN person_queries pq ON pq.id = max.query_id
LEFT JOIN locations loc ON loc.id = pq.location_id
LEFT JOIN types ty ON ty.id = p.type_id

I want to achieve the same thing in EF but i didn't know if this is possible, could i filter data got from DB?我想在 EF 中实现同样的事情,但我不知道这是否可能,我可以过滤从 DB 获得的数据吗? Should i "make" my object using the view manually assigning every value to related model value?我应该使用视图手动将每个值分配给相关的 model 值来“制作”我的 object 吗?

Queries that pull sets of data rarely need the entire details from the applicable entities.提取数据集的查询很少需要来自适用实体的全部详细信息。 You can mitigate this and filter data by projecting the entities to a view model structure that reflects what data you really need.您可以通过将实体投影到反映您真正需要的数据的视图 model 结构来缓解这种情况并过滤数据。

using (var ctx = new DBContext) 
{
    var persons = ctx.Persons
        .Select(x => new PersonViewModel
        {
           PersonId = x.PersonId,
           Name = x.Name,
           TypeName = x.Type.Name,
           RecentQueries = x.Queries
               .OrderByDecending(q => q.QueryDate)
               .Select(q => new QueryViewModel
               {
                  QueryId = q.QueryId,
                  Name = q.Name,
                  LocationName = q.Location.Name
                  // ... etc.
               }).Take(2).ToList()
        }).ToList();
    //...
}

This approach helps build more efficient queries with just the data you need.这种方法有助于仅使用您需要的数据构建更高效的查询。 Pulling full entity graphs is more of a per-item request such as when applying an update to a person and their related entities rather than trying to pull back all people with all, or a subset of their related entities.拉取完整的实体图更像是一个按项目的请求,例如在对一个人及其相关实体应用更新时,而不是试图拉回所有人及其相关实体的全部或一部分。

Edit: Example lifecycle of some data between server and view:编辑:服务器和视图之间某些数据的示例生命周期:

Part 1... List a bunch of people.第 1 部分...列出一群人。 These are summaries, enough data to satisfy a summary list view.这些是摘要,足以满足摘要列表视图的数据。

public IEnumerable<PersonSummaryViewModel> GetPeople(/*criteria*/)
{
    using (var ctx = new DBContext) 
    {
        var people = ctx.Persons
            .Where( /* based on criteria */ )
            .Select(x => new PersonSummaryViewModel
            {
               PersonId = x.PersonId,
               Name = x.Name,
               TypeName = x.Type.Name,
               RecentQueries = x.Queries
                   .OrderByDecending(q => q.QueryDate)
                   .Select(q => new QuerySummaryViewModel
                   {
                      QueryId = q.QueryId,
                      Name = q.Name,
                      LocationName = q.Location.Name
                      // ... etc.
                   }).Take(2).ToList()
            }).ToList();
        //...
        return people;
    }
}

Part 2: Get a Person Detailed Model. Once a user selects a person, we will want to pull back more data. Part 2: Get a Person Detailed Model. 一旦用户选择了一个人,我们会想要拉回更多的数据。 This view model can have a lot more fields and relationships to satisfy the view.这个视图 model 可以有更多的字段和关系来满足视图。 But even here we may want to exclude data that is not commonly viewed, (such as in expandable regions or tabs) that can be pulled back on demand with Ajax calls if/when the user wants.但即使在这里,我们也可能希望排除不常查看的数据(例如在可扩展区域或选项卡中),如果/当用户需要时,这些数据可以通过 Ajax 调用按需撤回。

public PersonDetailViewModel> GetPerson(int personId)
{
    using (var ctx = new DBContext) 
    {
        var person = ctx.Persons
            .Select(x => new PersonDetailViewModel
            {
               PersonId = x.PersonId,
               Name = x.Name,
               TypeName = x.Type.Name,
               // ... Can load all visible properties and initial, important related data...
            }).Single(x => x.PersonId == personId);
        //...
        return person;
    }
}

Part 3: Example, Update a Person第 3 部分:示例,更新人员

public void UpdatePerson(UpdatePersonViewModel updatePersonModel)
{
    if (updatePersonModel == null)
       throw new ArgumentNullException("updatePersionModel");

    // TODO: Validate updateModel, ensure everything is Ok, not tampered/invalid.
    using (var ctx = new DBContext) 
    {
        var person = ctx.Persons
            // can .Include() any related entities that can be updated here...
            .Where( x => x.PersonId == updatePersonModel.PersonId )
            .Single();
        person.Name = updatePersonModel.Name;
        // etc.
        ctx.SaveChanges();
    }
}

Part 4: Example, add a Query.第 4 部分:示例,添加一个查询。

public void AddQuery(int personId, AddQueryViewModel queryModel)
{
    if (queryModel == null)
       throw new ArgumentNullException("queryModel");

    // TODO: Validate queryModel, ensure everything is Ok, not tampered/invalid.
    using (var ctx = new DBContext) 
    {
        var person = ctx.Persons
            .Include( x => x.Queries )
            .Where( x => x.PersonId == personId )
            .Single();

        // TODO: Maybe check for duplicate query already on Person, etc.
        var query = new Query
        {
            // copy data from view model
        };
        person.Queries.Add(query);
        ctx.SaveChanges();
    }
}

The view models (or DTOs) only relate to transmitting data between the server and the client.视图模型(或 DTO)仅涉及在服务器和客户端之间传输数据。 When we get data back to the server and want to update entities, we load those entities.当我们将数据返回到服务器并想要更新实体时,我们加载这些实体。 By using these view models we reduce the amount of data sent between client and server (just the fields needed, not entire entity graphs) which means faster code and less data over the wire.通过使用这些视图模型,我们减少了客户端和服务器之间发送的数据量(只是需要的字段,而不是整个实体图),这意味着更快的代码和更少的数据传输。 We don't expose more about our entity structure than the client needs to know, and we don't risk simply overwriting data coming back by something like Attach + EntityState.Modified + SaveChanges where the data could be stale (someone else modified since that copy was taken) or the data coming back could be tampered with.我们不会暴露比客户需要知道的更多的实体结构,我们不会冒险简单地通过Attach + EntityState.Modified + SaveChanges之类的东西覆盖返回的数据,其中数据可能已经过时(其他人自那以后修改过)副本被拿走)或者返回的数据可能被篡改。 View models are single purpose.视图模型是单一用途的。 We could use a PersonDetailViewModel in an UpdatePerson type operation but our update may only apply to a select few properties so there is the overhead of sending everything back to the server, and no implied restriction on what should, and should not be allowed to be updated.我们可以在 UpdatePerson 类型操作中使用 PersonDetailViewModel,但我们的更新可能仅适用于 select 几个属性,因此存在将所有内容发送回服务器的开销,并且没有暗示限制应该允许和不应该允许更新的内容. (Especially if you use Automapper or such to help copy fields back and forth between entities and view models) (特别是如果您使用 Automapper 等来帮助在实体和视图模型之间来回复制字段)

In cases where we just want to interact with some entity data, without sending it back to the client, we don't need view models/DTOs, we can just selectively retrieve fields from the entire entity graph using Anonymous Types.在我们只想与一些实体数据交互而不将其发送回客户端的情况下,我们不需要视图模型/DTO,我们可以使用匿名类型从整个实体图中选择性地检索字段。 For instance if I just want to check if a person has a certain matching criteria and summarize the query names that match:例如,如果我只想检查一个人是否具有特定的匹配条件并总结匹配的查询名称:

var queryNames = ctx.Persons
    .Where(x => x.PersonId == personId)
    .SelectMany(x => Queries.Select( q => new  
    {
       q.QueryId,
       q.Name
    }).ToList();
var message = string.Join(", ", 
    queryNames.Select(x => string.Format("{0} ({1})", x.Name, x.QueryId)));

Just as a simple example with an anonymous type to return a structure that can be used by further code without the need for a view model / DTO.就像一个简单的示例,使用匿名类型返回一个结构,该结构可以被更多代码使用,而无需视图 model / DTO。 We're not passing the entity data back, but perhaps just inspecting values to determine a course of action, or composing something like a message string.我们不会传回实体数据,而可能只是检查值以确定操作过程,或者编写类似消息字符串的内容。

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

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