Using .Net Core 2.1 and EF Core 2.1.4
I'm trying to grab all the nested records of an object if any of those records contains a name that I have in a list, and only objects that have matching nested records.
I have a query that is producing the right results, but it's performing a query for every single record in the database table.
I'm hoping to reduce it further, if possible.
My ViewModels (actual models are nearly identical):
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();
}
}
}
In my ApplicationDbContext.cs file I have this mapped like so:
modelBuilder.Entity<EventProgram>()
.HasKey(eventProgram => new {eventProgram.EventId, eventProgram.ProgramId});
Finally, here is my query:
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));
}
This produces one EF query for the main Event properties:
SELECT [e].[Id], [e].[Name], [e].[Date]
FROM [Events] AS [e]
ORDER BY [e].[Date] DESC, [e].[Name]
And 1 for each result (in this case, in the above foreach loop)
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]
Is there any way I can improve this performance?
Update 1:
Based on TyCobb's comment I changed the query structure to:
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
}
})
})
;
Which is better, as it only produces a query for each valid record, where before it would do every record regardless.
This now produces this one query:
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]
And one for each valid record:
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]