AutoMapper ProjectTo来自具有子列表实体的模型



这在AutoMapper中可能吗?

  1. 选择类型X的列表并筛选类型Y的子实体(返回Y的单个值)
  2. 投影到平面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 )) 

相关内容

  • 没有找到相关文章

最新更新