繁体   English   中英

EF核心如何通过另一个列表过滤嵌套的多对多列表

EF Core How to filter a nested many-to-many list by another list

提示:本站收集StackOverFlow近2千万问答,支持中英文搜索,鼠标放在语句上弹窗显示对应的参考中文或英文, 本站还提供   中文繁体   英文版本   中英对照 版本,有任何建议请联系yoyou2525@163.com。

使用.Net Core 2.1和EF Core 2.1.4

我试图获取对象的所有嵌套记录,如果这些记录中的任何一个包含我在列表中的名称,并且只有具有匹配嵌套记录的对象。

我有一个产生正确结果的查询,但它正在对数据库表中的每个记录执行查询。

如果可能的话,我希望能进一步减少它。

我的ViewModel(实际模型几乎相同):

EventViewModel

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace MyProject.Models.ViewModels
{
    public class EventViewModel
    {
        private string _name;

        public Guid Id { get; set; }

        [Required, StringLength(100)]
        public string Name
        {
            get => _name?.Trim();
            set => _name = value?.Trim();
        }

        [DataType(DataType.Date)]
        public DateTime Date { get; set; }

        [Display(Name = "Programs")]
        public IEnumerable<EventProgramViewModel> EventProgramViewModels { get; set; }

        public string AllProgramNames
        {
            get
            {
                string result = EventProgramViewModels.Aggregate(string.Empty,
                    (current, program) => current + $"{program?.ProgramViewModel?.Name}, ");
                return result.TrimEnd(',', ' ');
            }
        }

        public EventViewModel()
        {
            Id = Guid.NewGuid();
            Date = DateTime.Now;
            EventProgramViewModels = new List<EventProgramViewModel>();
        }
    }
}

EventProgramViewModel

using System;

namespace MyProject.Models.ViewModels
{
    public class EventProgramViewModel
    {
        public Guid EventViewModelId { get; set; }
        public EventViewModel EventViewModel { get; set; }

        public Guid ProgramViewModelId { get; set; }
        public ProgramViewModel ProgramViewModel { get; set; }
    }
}

ProgramViewModel

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace MyProject.Models.ViewModels
{
    public class ProgramViewModel
    {
        private string _name;

        public Guid Id { get; set; }

        [Required, StringLength(100)]
        [Display(Name = "Program Name")]
        public string Name
        {
            get => _name?.Trim();
            set => _name = value?.Trim();
        }
    }
}

在我的ApplicationDbContext.cs文件中,我将其映射为:

modelBuilder.Entity<EventProgram>()
            .HasKey(eventProgram => new {eventProgram.EventId, eventProgram.ProgramId});

最后,这是我的查询

var testQuery = context.Events
        .AsNoTracking()
        .Select(e => new EventViewModel
        {
            Id = e.Id,
            Name = e.Name,
            Date = e.Date,
            EventProgramViewModels = e.EventPrograms.Select(eventProgram =>
                new EventProgramViewModel
                {
                    ProgramViewModel = new ProgramViewModel
                    {
                        Name = eventProgram.Program.Name
                    }
                })
        })
        .OrderByDescending(eventViewModel => eventViewModel.Date)
        .ThenBy(eventViewModel => eventViewModel.Name)
        .Where(eventViewModel =>
            !search.ProgramsChosen.Any() || eventViewModel.EventProgramViewModels.Any(
                eventProgramViewModel =>
                    search.ProgramsChosen.Contains(eventProgramViewModel.ProgramViewModel
                        .Name)))
        .ToList()
    ;

foreach (var item in testQuery)
{
    // Mock usage to show where queries are generated
    _loggingServices.LogInformation(JsonConvert.SerializeObject(item));
}

这会为主Event属性生成一个EF查询:

SELECT [e].[Id], [e].[Name], [e].[Date]
FROM [Events] AS [e]
ORDER BY [e].[Date] DESC, [e].[Name]

每个结果为1(在这种情况下,在上面的foreach循环中)

SELECT [eventProgram.Program].[Name]
FROM [EventPrograms] AS [eventProgram]
INNER JOIN [Programs] AS [eventProgram.Program] ON [eventProgram].[ProgramId] = [eventProgram.Program].[Id]
WHERE @_outer_Id = [eventProgram].[EventId]

有什么办法可以改善这种表现吗?


更新1:

根据TyCobb的评论,我将查询结构更改为:

var testQuery =
        context.Events
            .AsNoTracking()
            .OrderByDescending(@event => @event.Date)
            .ThenBy(@event => @event.Name)
            .Where(@event =>
                !search.ProgramsChosen.Any() || @event.EventPrograms.Any(
                    eventProgramViewModel =>
                        search.ProgramsChosen.Contains(eventProgramViewModel.Program
                            .Name)))
            .Select(e => new EventViewModel
            {
                Id = e.Id,
                Name = e.Name,
                Date = e.Date,
                EventProgramViewModels = e.EventPrograms.Select(eventProgram =>
                    new EventProgramViewModel
                    {
                        ProgramViewModel = new ProgramViewModel
                        {
                            Name = eventProgram.Program.Name
                        }
                    })
            })
    ;

哪个更好,因为它只为每个有效记录生成一个查询,在此之前它将执行每个记录。

现在生成这一个查询:

SELECT [event].[Id], [event].[Name], [event].[Date]
FROM [Events] AS [event]
WHERE EXISTS (
    SELECT 1
    FROM [EventPrograms] AS [eventProgramViewModel]
    INNER JOIN [Programs] AS [eventProgramViewModel.Program] ON [eventProgramViewModel].[ProgramId] = [eventProgramViewModel.Program].[Id]
    WHERE [eventProgramViewModel.Program].[Name] IN (N'A SEARCHED PROGRAM NAME') AND ([event].[Id] = [eventProgramViewModel].[EventId]))
ORDER BY [event].[Date] DESC, [event].[Name]

每个有效记录一个:

SELECT [eventProgram.Program].[Name]
FROM [EventPrograms] AS [eventProgram]
INNER JOIN [Programs] AS [eventProgram.Program] ON [eventProgram].[ProgramId] = [eventProgram.Program].[Id]
WHERE @_outer_Id = [eventProgram].[EventId]
1 个回复

有两个问题。

第一个(您使用Update 1解决)是由当前的EF Core查询转换缺陷引起的,该缺陷导致客户端评估Where子句( OrderBy )。 因此,在投影之前移动过滤是当前解决方法的方法。

第二个是所谓的N + 1子查询问题。 EF Core 2.1包含相关子查询的优化 ,适用于您的情况,但正如文档中所述,您需要通过添加ToList (或ToArray )来选择它:

我们改进了查询转换,以避免在许多常见场景中执行“N + 1”SQL查询,其中投影中导航属性的使用导致将来自根查询的数据与来自相关子查询的数据相关联。 优化需要缓冲子查询的结果,并且我们要求您修改查询以选择新的行为。

然后

通过在正确的位置包含ToList() ,您可以指示缓冲适用于启用优化的Orders

所以最终的查询应该是这样的:

var testQuery = context.Events
    //.AsNoTracking() <-- No need when using projection
    .OrderByDescending(@event => @event.Date)
    .ThenBy(@event => @event.Name)
    .Where(@event =>
        !search.ProgramsChosen.Any() || @event.EventPrograms.Any(
            eventProgram =>
                search.ProgramsChosen.Contains(eventProgram.Program
                    .Name)))
    .Select(e => new EventViewModel
    {
        Id = e.Id,
        Name = e.Name,
        Date = e.Date,
        EventProgramViewModels = e.EventPrograms.Select(eventProgram =>
            new EventProgramViewModel
            {
                ProgramViewModel = new ProgramViewModel
                {
                    Name = eventProgram.Program.Name
                }
            }).ToList() // <-- 
    })
    .ToList()
    ;

这将导致2个SQL查询:

SELECT [event].[Id], [event].[Name], [event].[Date]
FROM [Events] AS [event]
WHERE EXISTS (
    SELECT 1
    FROM [EventPrograms] AS [eventProgramViewModel]
    INNER JOIN [Programs] AS [eventProgramViewModel.Program] ON [eventProgramViewModel].[ProgramId] = [eventProgramViewModel.Program].[Id]
    WHERE [eventProgramViewModel.Program].[Name] IN (N'P2', N'P4', N'P7') AND ([event].[Id] = [eventProgramViewModel].[EventId]))
ORDER BY [event].[Date] DESC, [event].[Name], [event].[Id]

SELECT [t].[Date], [t].[Name], [t].[Id], [eventProgram.Program].[Name] AS [Name0], [event.EventPrograms].[EventId]
FROM [EventPrograms] AS [event.EventPrograms]
INNER JOIN [Programs] AS [eventProgram.Program] ON [event.EventPrograms].[ProgramId] = [eventProgram.Program].[Id]
INNER JOIN (
    SELECT [event0].[Date], [event0].[Name], [event0].[Id]
    FROM [Events] AS [event0]
    WHERE EXISTS (
        SELECT 1
        FROM [EventPrograms] AS [eventProgramViewModel0]
        INNER JOIN [Programs] AS [eventProgramViewModel.Program0] ON [eventProgramViewModel0].[ProgramId] = [eventProgramViewModel.Program0].[Id]
        WHERE [eventProgramViewModel.Program0].[Name] IN (N'P2', N'P4', N'P7') AND ([event0].[Id] = [eventProgramViewModel0].[EventId]))
) AS [t] ON [event.EventPrograms].[EventId] = [t].[Id]
ORDER BY [t].[Date] DESC, [t].[Name], [t].[Id]
1 EF 核心枚举列表

我正在开发小型应用程序,在 Entity Framework Core 中组织酒店预订。 我需要添加有关每个房间床位数量和类型的信息。 我在想,我决定枚举列表会比将它存储在单独的表中更好。 但我不知道如何实现它 有可能这样做吗? 我还想将它保存为像 (1,2,1,2) 这样的字符串,然后解析它以 ...

2 根据另一个列表过滤列表

我有两个基于其他对象的列表。 他们两个都有雇员的ID,但是第二个列表中只有几个。 我想过滤我在displayEmployeeList中拥有所有id的employeeList。 我怎样才能做到这一点? ...

3 根据另一个列表过滤列表

我想根据drop项目过滤data 。 而且因为我也希望将NBA ET的名单也排除在外,所以它必须超越: 我需要的是: 但我永远不记得语法。 作为记录, filtered应显示[['Basket', 'ENG', 'Shaq']] ...

4 如何通过另一个列表过滤列表

我有两个清单: 和 我试图通过一个条件和另一个条件(该元素不应该在黑名单中)创建新列表。 这是我的尝试: 此newList是在循环内生成的,在循环的第一轮中, blackList为空且有效,在第二轮中, blackList包含约200k元素。 当上面的行起作用时,它接 ...

5 通过另一个过滤列表

我要求根据过去x个月中没有预订任何作业的客户列表进行过滤。 在我的代码中,我有两个列表,一个是我的客户端,另一个是今天和x个月之前过滤的作业列表,其目的是根据客户列表中未显示的ID过滤客户端。 我尝试了以下方法: 但我似乎得到了所有客户。 我可以很容易地做一个foreach但这会严 ...

2012-12-17 22:33:43 3 2200   c#/ linq
6 EF核心:过滤与列表中的任何项目匹配的记录

(编辑)我正在尝试EF core和文档表以及文档三元组表。 每个文档可以包含0个或多个(RDF)三元组,由谓词加一个对象表示。 相应的EF实体为: 和: 他们的SQL定义: 现在,我想使用这种模式将多个变量过滤器应用于文档:我首先创建一个IQueryable do ...

8 从另一个列表中过滤一个列表

我有两个列表(即queryset和list): filter_perceptions : 还有另一个清单 results : 我想用最后两个列表创建一个新列表。 实际上,我想过滤results以便保留与filter_perceptions的看法一致的列表。 谁能想 ...

9 用另一个列表过滤一个列表

我有一个任务列表,我需要过滤到我在另一个工作表中列出的任务。 如果手动执行此操作,您将通过“列表”表中的每个任务名称选择每个复选框。 另一张纸是“标准”。 我需要能够随着时间的推移更新标准列表,所以我做了一个循环来过滤“列表”,“标准”的内容在列表中向下移动,直到活动单元格为空。 我的问题是最后的过 ...

2020-10-15 21:25:29 0 30   excel/ vba
暂无
暂无

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

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