我正在尝试将 EF Core LINQ 查询的投影逻辑提取到单独的方法中,以使代码更清晰或为不同的投影重用代码?
有一个类似的关于重用代码的帖子,位于我可以重用代码为使用 EF Core 为子属性选择自定义 DTO 对象吗?,但所选答案使用其他库,并且问题是要使用类似
Select(Model.AsDto)
的调用
,封装了 lambda。
我想使用一个简单的解决方案,并且不想删除 lambda 表达式。我只想提取创建新对象的映射部分,例如
(danceCoverImage, image) => Mappings.MapToImageItemResponse(danceCoverImage, image))
。
这适用于一个嵌套级别,但不适用于两个嵌套级别,如下例所示。
我有这个示例投影,它返回带有嵌套封面图像的位置数据。图像 URI、宽度和高度是通过连接从图像集合中提取的。
List<LocationMLResponse> Locations;
var query = dbContext.Location.AsNoTracking()
.Select(location => new LocationMLResponse(
location.Id.Value,
location.Name.ToStringDictionary(),
location.CoverImages
.Join(dbContext.Image.AsNoTracking(),
danceCoverImage => danceCoverImage.ImageId,
image => image.Id,
(danceCoverImage, image) => new ImageItemResponse(
danceCoverImage.ImageId.Value,
danceCoverImage.DisplayOrder,
danceCoverImage.Language == null ? null : danceCoverImage.Language.Tag,
danceCoverImage.ScreenSize == null ? null : danceCoverImage.ScreenSize.Value,
danceCoverImage.FocusPointX,
danceCoverImage.FocusPointY,
image.Uri.ToString(),
image.MetaData.Width,
image.MetaData.Height
)).ToList(),
location.Address.MapToDto(),
location.Created,
location.LastModified,
location.Version
));
Locations = await query.ToListAsync();
我尝试提取映射方法:
var query = dbContext.Location.AsNoTracking()
.Select(location => MapToLocationResponse.MLResponse(dbContext, location));
internal static class MapToLocationResponse
{
public static LocationMLResponse MLResponse(VCDbContext dbContext, Location location)
{
return new LocationMLResponse(
location.Id.Value,
location.Name.ToStringDictionary(),
location.CoverImages
.Join(dbContext.Image.AsNoTracking(),
danceCoverImage => danceCoverImage.ImageId,
image => image.Id,
(danceCoverImage, image) => Mappings.MapToImageItemResponse(danceCoverImage, image)).ToList(),
location.Address.MapToDto(),
location.Created,
location.LastModified,
location.Version
);
}
}
internal static partial class Mappings
{
public static ImageItemResponse MapToImageItemResponse(ImageItem imageItem, Image image)
{
return new ImageItemResponse(
imageItem.ImageId.Value,
imageItem.DisplayOrder,
imageItem.Language == null ? null : imageItem.Language.ToString(),
imageItem.ScreenSize == null ? null : imageItem.ScreenSize.ToString(),
imageItem.FocusPointX,
imageItem.FocusPointY,
image.Uri.ToString(),
image.MetaData.Width,
image.MetaData.Height
);
}
}
此代码创建以下查询:
SELECT [l].[LocationId], [l].[Created], [l].[CreatedBy], [l].[CreatedInNameOf], [l].[LastModified], [l].[LastModifiedBy], [l].[LastModifiedInNameOf], [l].[Name], [l].[TId], [l].[Version], [l].[Address_City], [l].[Address_CountryId], [l].[Address_CountryName], [l].[Address_DeliveryInstruction], [l].[Address_State], [l].[Address_Street], [l].[Address_StreetAffix], [l].[Address_StreetNumber], [l].[Address_ZipCode], [l0].[TId], [l0].[DisplayOrder], [l0].[FocusPointX], [l0].[FocusPointY], [l0].[ImageId], [l0].[Language], [l0].[LocationId], [l0].[ScreenSize]
FROM [VC].[Location] AS [l]
LEFT JOIN [VC].[LocationCoverImage] AS [l0] ON [l].[LocationId] = [l0].[LocationId]
ORDER BY [l].[LocationId]
缺少图像表的连接和图像字段的选择。
我尝试仅使用一个嵌套进行映射:
List<LocationMLResponse> Locations;
var query = dbContext.Location.AsNoTracking()//.AsSplitQuery()
.Select(location => new LocationMLResponse(
location.Id.Value,
location.Name.ToStringDictionary(),
location.CoverImages
.Join(dbContext.Image.AsNoTracking(),
danceCoverImage => danceCoverImage.ImageId,
image => image.Id,
(danceCoverImage, image) => Mappings.MapToImageItemResponse(danceCoverImage, image)).ToList(),
location.Address.MapToDto(),
location.Created,
location.LastModified,
location.Version
));
Locations = await query.ToListAsync();
这有效。该投影创建与顶部完整投影相同的查询。
(请注意,我在 EF Core GitHub 问题中发布的创建的查询存在一个普遍问题。
这是 EF Core 的总体问题还是仅与连接有关?是否可以使用嵌套调用或者是否需要使用表达式?
EF核心版本:8.0.8
数据库提供程序:Microsoft.EntityFrameworkCore.SqlServer
目标框架:NET 8.0
操作系统:Win 11
Visual Studio 2022 17.11.4
(danceCoverImage, image) => Mappings.MapToImageItemResponse(danceCoverImage, image))
和类似的是方法调用表达式。
EF Core 或其他工具可以提取的唯一信息是表达式有两个输入参数,它调用某些方法并返回某种类型的对象。 MapToImageItemResponse
的内容是一个黑盒子。它是经过编译的,无法轻松检查和翻译为 SQL。Expression<Func<CoverImage, ImageItemResponse>> GetImageItemResponseMapping()
{
return (danceCoverImage, image) => new ImageItemResponse(
danceCoverImage.ImageId.Value,
danceCoverImage.DisplayOrder,
danceCoverImage.Language == null ? null : danceCoverImage.Language.Tag,
danceCoverImage.ScreenSize == null ? null : danceCoverImage.ScreenSize.Value,
danceCoverImage.FocusPointX,
danceCoverImage.FocusPointY,
image.Uri.ToString(),
image.MetaData.Width,
image.MetaData.Height);
}
然后
location.CoverImages
.Join(dbContext.Image.AsNoTracking(),
danceCoverImage => danceCoverImage.ImageId,
image => image.Id,
GetImageItemResponseMapping())
如果做得正确,无论嵌套层数有多少,它都应该可以工作。