这在AutoMapper中可能吗?
- 选择类型X的列表并筛选类型Y的子实体(返回Y的单个值)
- 投影到平面DTO包含来自X和Y的道具
如果不是,那么在这种情况下,填充DTO的最佳方式是什么,例如,在实际情况下,表有很多列,我希望避免为了获得一两个道具而读取整行。
以下是.Net 5.0 中的快速控制台应用程序代码
项目.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.0" />
</ItemGroup>
</Project>
控制台应用程序测试代码
using System;
using System.Linq;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
namespace MappingTest
{
public class Parent
{
public int Id { get; set; }
public string ParentName { get; set; }
public List<Child> Children { get; set; } = new List<Child>();
}
public class Child
{
public int Id { get; set; }
public int Age { get; set; }
public string ChildName { get; set; }
public Parent Parent { get; set; }
}
public class ParentDto
{
public int Id { get; set; }
public string ParentName { get; set; }
public string ChildName { get; set; }
}
public class DataContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<Child> Children { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase("MyDb");
}
}
internal class Program
{
public static void Seed(DataContext context)
{
context.Parents.Add(new Parent
{
Id = 1,
ParentName = "John",
Children = new List<Child>()
{
new Child { Id = 1, ChildName = "Peter", Age = 10 },
new Child { Id = 2, ChildName = "Smith", Age = 20 }
}
});
context.Parents.Add(new Parent
{
Id = 2,
ParentName = "Micheal",
Children = new List<Child>()
{
new Child { Id = 3, ChildName = "Wale", Age = 10 },
new Child { Id = 4, ChildName = "Robert", Age = 25 }
}
});
context.SaveChanges();
}
static void Main(string[] args)
{
var config = new MapperConfiguration((cfg) =>
{
cfg.CreateMap<Parent, ParentDto>();
cfg.CreateMap<Child, ParentDto>();
});
var mapper = config.CreateMapper();
using (var context = new DataContext())
{
Seed(context);
var parent = context.Parents
// Filter children and get only the 10 years old (Peter and Wale)
// Project to the Dto and have the ChildName mapped to the Dto
// Note: Parent should have only one 10 years old child
.ProjectTo<ParentDto>(mapper.ConfigurationProvider)
.ToList();
foreach(var p in parent)
{
Console.WriteLine(string.Format("{0} - {1} - {2}",p.Id, p.ParentName, p.ChildName));
}
}
}
}
}
找不到解决方案的类似场景
更新#1
我真的在考虑Dapper No Mapper,@Prolog你的答案帮助很大,我通过使用的另一种方法正确地解决了它
var config = new MapperConfiguration((cfg) =>
{
cfg.CreateMap<Parent, ParentDto>();
cfg.CreateMap<Child, ParentDto>()
.IncludeMembers(e => e.Parent);
});
然后投影喜欢这个
var parents = context.Parents
.SelectMany(e => e.Children.Where(a => a.Age == 10))
.ProjectTo<ParentDto>(mapper.ConfigurationProvider)
.ToList();
但是生成的SQL很有趣
SELECT [t].[ChildName], [t].[Id], [p0].[ParentName]
FROM [Parents] AS [p]
INNER JOIN (
SELECT [c].[Id], [c].[ChildName], [c].[ParentId]
FROM [Children] AS [c]
WHERE [c].[Age] = 10
) AS [t] ON [p].[Id] = [t].[ParentId]
LEFT JOIN [Parents] AS [p0] ON [t].[ParentId] = [p0].[Id]
其中所需的SQL非常简单
SELECT p.Id,
p.ParentName,
c.ChildName
FROM dbo.Parents p
LEFT JOIN dbo.Children c ON p.Id = c.Id
-- or INNER JOIN
WHERE c.Age = 10;
您可以使用IncludeMembers()
告诉AutoMapper在完成Parent
的映射后,尝试从Child
填充ParentDto
的will值的属性。在AutoMapper文档中阅读有关此功能的更多信息。
var config = new MapperConfiguration((cfg) =>
{
cfg.CreateMap<Parent, ParentDto>()
.IncludeMembers(src => src.Children.First());
cfg.CreateMap<Child, ParentDto>();
});
此外,不要用内存中数据库测试面向数据库的投影,因为它会隐藏所有类型的查询执行错误,直到切换到真正的数据库。
因此,如果你只想筛选出有10岁孩子的父母:
var parents = context.Parents
.Where(x => x.Children.Any(x => x.Age == 10))
.ProjectTo<ParentDto>(mapper.ConfigurationProvider)
.ToList();
使用Microsoft SQL Server,将生成这样的查询:
SELECT [p].[id],
[p].[parentname],
(SELECT TOP(1) [c0].[childname]
FROM [children] AS [c0]
WHERE [p].[id] = [c0].[parentid]) AS [ChildName]
FROM [parents] AS [p]
WHERE EXISTS (SELECT 1
FROM [children] AS [c]
WHERE ( [p].[id] = [c].[parentid] )
AND ( [c].[age] = 10 ))