I get the following error that I'm having trouble solving:
CS1929
'List<ExternalLink>' does not contain a definition for 'Select' and
the best extension method overload
'Queryable.Select<ExternalLink, ExternalLinkDto>(IQueryable<ExternalLink>,
Expression<Func<ExternalLink, ExternalLinkDto>>)'
requires a receiver of type
'System.Linq.IQueryable<MyProject.Models.Entities.ExternalLink>'
What I'm trying to achieve is this example from @JonSkeet
https://stackoverflow.com/a/34649951/3850405
The reason for doing this is exactly as the original post, avoid duplicate code for LINQ Select
and still only project the properties I need for querying efficiently via EF Core.
public static IQueryable<ProductSummaryDtoHelper> QueryProductSummary(this IQueryable<Product> query)
{
var summary = query.Select(product => new ProductSummaryDtoHelper()
{
UnresolvedThreatAndCountermeasures =
product.ThreatAndCountermeasures.Where(tac => !tac.NotApplicable && !(tac.Verified && tac.Implemented)).Select(
vuln => new ProductSummaryVulnerabilityDtoHelper()
{
ExternalLinksHelper = vuln.ExternalLinks.Select(ExternalLinkDto.Conversion),
ExternalLinksHelperThreat = vuln.Threat.ExternalLinks.Select(externalLink => new ExternalLinkDto()
{
Id = externalLink.Id,
Link = externalLink.Link,
LinkType = externalLink.LinkType,
LinkText = externalLink.LinkText
}),
ExternalLinksHelperThreatFork = vuln.Threat.Fork.ExternalLinks.Select(externalLink => new ExternalLinkDto()
{
Id = externalLink.Id,
Link = externalLink.Link,
LinkType = externalLink.LinkType,
LinkText = externalLink.LinkText
})
})
});
return summary;
}
ExternalLinkDto class:
public class ExternalLinkDto : EntityDto
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
...
public static Expression<Func<ExternalLink, ExternalLinkDto>> Conversion = externalLink =>
new ExternalLinkDto()
{
Id = externalLink.Id,
Link = externalLink.Link,
LinkType = externalLink.LinkType,
LinkText = externalLink.LinkText
};
}
The following code works but I do not like the code duplication:
public static IQueryable<ProductSummaryDtoHelper> QueryProductSummary(this IQueryable<Product> query)
{
var summary = query.Select(product => new ProductSummaryDtoHelper()
{
UnresolvedThreatAndCountermeasures =
product.ThreatAndCountermeasures.Where(tac => !tac.NotApplicable && !(tac.Verified && tac.Implemented)).Select(
vuln => new ProductSummaryVulnerabilityDtoHelper()
{
ExternalLinksHelper = vuln.ExternalLinks.Select(externalLink => new ExternalLinkDto()
{
Id = externalLink.Id,
Link = externalLink.Link,
LinkType = externalLink.LinkType,
LinkText = externalLink.LinkText
}),
ExternalLinksHelperThreat = vuln.Threat.ExternalLinks.Select(externalLink => new ExternalLinkDto()
{
Id = externalLink.Id,
Link = externalLink.Link,
LinkType = externalLink.LinkType,
LinkText = externalLink.LinkText
}),
ExternalLinksHelperThreatFork = vuln.Threat.Fork.ExternalLinks.Select(externalLink => new ExternalLinkDto()
{
Id = externalLink.Id,
Link = externalLink.Link,
LinkType = externalLink.LinkType,
LinkText = externalLink.LinkText
})
})
});
return summary;
}
This results in the following SQL sent:
SELECT [t0].[Id], [t1].[Id] AS [Id0], [t3].[Id] AS [Id1], [e].[Id] AS [Id2], [e].[Link], [e].[LinkType], [e].[LinkText], [e0].[Id] AS [Id3], [e0].[Link] AS [Link0], [e0].[LinkType] AS [LinkType0], [e0].[LinkText] AS [LinkText0], [e1].[Id] AS [Id4], [e1].[Link] AS [Link1], [e1].[LinkType] AS [LinkType1], [e1].[LinkText] AS [LinkText1], [t0].[ProductId]
FROM [ThreatAndCountermeasure] AS [t0]
INNER JOIN [Threats] AS [t1] ON [t0].[ThreatId] = [t1].[Id]
LEFT JOIN [Threats] AS [t3] ON [t1].[ForkId] = [t3].[Id]
LEFT JOIN [ExternalLink] AS [e] ON [t0].[Id] = [e].[ThreatAndCountermeasureId]
LEFT JOIN [ExternalLink] AS [e0] ON [t1].[Id] = [e0].[ThreatId]
LEFT JOIN [ExternalLink] AS [e1] ON [t3].[Id] = [e1].[ThreatId]
WHERE [t0].[NotApplicable] = CAST(0 AS bit) AND ([t0].[Verified] = CAST(0 AS bit) OR [t0].[Implemented] = CAST(0 AS bit))
Creating an extension method like this also works:
public static IQueryable<ProductSummaryDtoHelper> QueryProductSummary(this IQueryable<Product> query)
{
var summary = query.Select(product => new ProductSummaryDtoHelper()
{
UnresolvedThreatAndCountermeasures =
product.ThreatAndCountermeasures.Where(tac => !tac.NotApplicable && !(tac.Verified && tac.Implemented)).Select(
vuln => new ProductSummaryVulnerabilityDtoHelper()
{
ExternalLinksHelper = vuln.ExternalLinks.MapToExternalLinkDto(),
ExternalLinksHelperThreat = vuln.Threat.ExternalLinks.MapToExternalLinkDto(),
ExternalLinksHelperThreatFork = vuln.Threat.Fork.ExternalLinks.MapToExternalLinkDto()
})
});
return summary;
}
public static IEnumerable<ExternalLinkDto> MapToExternalLinkDto(this List<ExternalLink> externalLinks)
{
var summary = externalLinks.Select(externalLink => new ExternalLinkDto()
{
Id = externalLink.Id,
Link = externalLink.Link,
LinkType = externalLink.LinkType,
LinkText = externalLink.LinkText
});
return summary;
}
However the SQL generated will pick up every extra field and I do not want that:
SELECT [t0].[Id], [t1].[Id] AS [Id0], [t3].[Id] AS [Id1], [e].[Id] AS [Id2], [e].[Created], [e].[CreatedById], [e].[Guid], [e].[Link], [e].[LinkText], [e].[LinkType], [e].[PeriodEnd], [e].[PeriodStart], [e].[ProductId], [e].[ThreatAndCountermeasureId], [e].[ThreatId], [e].[Updated], [e].[UpdatedById], [e].[Version], [e0].[Id] AS [Id3], [e0].[Created] AS [Created0], [e0].[CreatedById] AS [CreatedById0], [e0].[Guid] AS [Guid0], [e0].[Link] AS [Link0], [e0].[LinkText] AS [LinkText0], [e0].[LinkType] AS [LinkType0], [e0].[PeriodEnd] AS [PeriodEnd0], [e0].[PeriodStart] AS [PeriodStart0], [e0].[ProductId] AS [ProductId0], [e0].[ThreatAndCountermeasureId] AS [ThreatAndCountermeasureId0], [e0].[ThreatId] AS [ThreatId0], [e0].[Updated] AS [Updated0], [e0].[UpdatedById] AS [UpdatedById0], [e0].[Version] AS [Version0], [e1].[Id] AS [Id4], [e1].[Created] AS [Created1], [e1].[CreatedById] AS [CreatedById1], [e1].[Guid] AS [Guid1], [e1].[Link] AS [Link1], [e1].[LinkText] AS [LinkText1], [e1].[LinkType] AS [LinkType1], [e1].[PeriodEnd] AS [PeriodEnd1], [e1].[PeriodStart] AS [PeriodStart1], [e1].[ProductId] AS [ProductId1], [e1].[ThreatAndCountermeasureId] AS [ThreatAndCountermeasureId1], [e1].[ThreatId] AS [ThreatId1], [e1].[Updated] AS [Updated1], [e1].[UpdatedById] AS [UpdatedById1], [e1].[Version] AS [Version1], [t0].[ProductId] AS [ProductId2]
FROM [ThreatAndCountermeasure] AS [t0]
INNER JOIN [Threats] AS [t1] ON [t0].[ThreatId] = [t1].[Id]
LEFT JOIN [Threats] AS [t3] ON [t1].[ForkId] = [t3].[Id]
LEFT JOIN [ExternalLink] AS [e] ON [t0].[Id] = [e].[ThreatAndCountermeasureId]
LEFT JOIN [ExternalLink] AS [e0] ON [t1].[Id] = [e0].[ThreatId]
LEFT JOIN [ExternalLink] AS [e1] ON [t3].[Id] = [e1].[ThreatId]
WHERE [t0].[NotApplicable] = CAST(0 AS bit) AND ([t0].[Verified] = CAST(0 AS bit) OR [t0].[Implemented] = CAST(0 AS bit)