Skip to content

Instantly share code, notes, and snippets.

@JimBobSquarePants
Last active June 12, 2019 06:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JimBobSquarePants/932f555c803fd41c1bee96722cd7bdcd to your computer and use it in GitHub Desktop.
Save JimBobSquarePants/932f555c803fd41c1bee96722cd7bdcd to your computer and use it in GitHub Desktop.
Comparison of EF query mapping approaches.
--Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (176ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM [Customers] AS [x])
THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END
--Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (185ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT COUNT(*)
FROM [Customers] AS [x]
--Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (77ms) [Parameters=[@__p_0='?' (DbType =
Int32), @__p_1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT [x].[Id], [x].[Email], [x].[FirstName], [x].[LastName]
FROM [Customers] AS [x]
ORDER BY [x].[LastName], [x].[FirstName] DESC, [x].[Id]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
--Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (86ms) [Parameters=[@__p_0='?' (DbType = Int32), @__p_1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT [t].[LastName], [t].[FirstName], [t].[Id], [x.School].[Name] AS [Item1], [x.CustomerSchools].[SchoolId] AS [Item2], [x.CustomerSchools].[CustomerId]
FROM [CustomerSchool] AS [x.CustomerSchools]
INNER JOIN [Schools] AS [x.School] ON [x.CustomerSchools].[SchoolId] = [x.School].[Id]
INNER JOIN (
SELECT [x0].[LastName], [x0].[FirstName], [x0].[Id]
FROM [Customers] AS [x0]
ORDER BY [x0].[LastName], [x0].[FirstName] DESC, [x0].[Id]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
) AS [t] ON [x.CustomerSchools].[CustomerId] = [t].[Id]
ORDER BY [t].[LastName], [t].[FirstName] DESC, [t].[Id]
--Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (182ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM [Customers] AS [x])
THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END
--Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (155ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT COUNT(*)
FROM [Customers] AS [x]
--Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (124ms) [Parameters=[@__p_0='?' (DbType = Int32), @__p_1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT [x].[Id], [x].[CreateDateUtc], [x].[Email], [x].[FirstName], [x].[LandLine], [x].[LastName], [x].[Title], [x].[UpdateDateUtc]
FROM [Customers] AS [x]
ORDER BY [x].[LastName], [x].[FirstName] DESC, [x].[Id]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
--Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (150ms) [Parameters=[@__p_0='?' (DbType = Int32), @__p_1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT [x.CustomerSchools].[CustomerId], [x.CustomerSchools].[SchoolId], [c.School].[Id], [c.School].[Address], [c.School].[CreateDateUtc], [c.School].[CreateUser], [c.School].[DPID], [c.School].[DeniedReason], [c.School].[Name], [c.School].[OldId], [c.School].[Status], [c.School].[SuburbId], [c.School].[UpdateDateUtc], [c.School].[UpdateUser]
FROM [CustomerSchool] AS [x.CustomerSchools]
INNER JOIN [Schools] AS [c.School] ON [x.CustomerSchools].[SchoolId] = [c.School].[Id]
INNER JOIN (
SELECT [x0].[Id], [x0].[LastName], [x0].[FirstName]
FROM [Customers] AS [x0]
ORDER BY [x0].[LastName], [x0].[FirstName] DESC, [x0].[Id]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
) AS [t] ON [x.CustomerSchools].[CustomerId] = [t].[Id]
ORDER BY [t].[LastName], [t].[FirstName] DESC, [t].[Id]
public static class MappingExtensions
{
public static IQueryable<Index.Model> MapCollectionTo(this IQueryable<Customer> customers)
{
return customers.Select(customer => new Index.Model
{
Id = customer.Id,
Email = customer.Email,
FirstName = customer.FirstName,
LastName = customer.LastName,
Schools = customer.CustomerSchools.Select(x => new Tuple<string, Guid>(x.School.Name, x.School.Id)).ToArray()
});
}
}
/// <summary>
/// Provides named configuration for maps.
/// </summary>
public class MappingProfile : Profile
{
/// <summary>
/// Initializes a new instance of the <see cref="MappingProfile"/> class.
/// </summary>
public MappingProfile()
{
this.CreateMap<Customer, Index.Model>().ConvertUsing(source => Map(source));
}
/// <summary>
/// Maps the <see cref="Customer"/> to an <see cref="Index.Model"/>.
/// </summary>
/// <param name="customer">The customer.</param>
/// <returns>The <see cref="Index.Model"/>.</returns>
public static Index.Model Map(Customer customer)
{
return new Index.Model
{
Id = customer.Id,
Email = customer.Email,
FirstName = customer.FirstName,
LastName = customer.LastName,
Schools = customer.CustomerSchools.Select(x => new Tuple<string, Guid>(x.School.Name, x.School.Id))
};
}
}
@JimBobSquarePants
Copy link
Author

Interesting things to note.

  • The SQL generated via AutoMappers ProjectTo<T>(IQueryable) selects extra properties
  • If the ToArray() call is removed, EF Core will generate an additional SQL query for each school in the IQueryable extension method, with ProjectTo the SQL remains the same

It would be nice if I could figure out a way to avoid multiple queries but EF Core does this by design for navigation properties in 2.x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment