Entity Framework Core: AutoMapper로 쿼리 간소화

코드 예제의 경우 다음 엔터티를 사용합니다.

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public Blog Blog { get; set; }
    public List<Comment> Comments { get; set; }
}

public class Comment
{
    public int Id { get; set; }
    public string Content { get; set; }
    public Post Post { get; set; }
}


그리고 DTO:

public class PostDto
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int BlogId { get; set; }
    public string BlogName { get; set; }
    public List<CommentDto> CommentsDto { get; set; }
}

public class CommentDto
{
    public int Id { get; set; }
    public string Content { get; set; }
}

public class PostSummaryDto
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int CommentsCount { get; set; }
}


Entity Framework로 데이터를 쿼리할 때 Include 메서드를 사용하는 것이 매우 일반적입니다. 그러나 여기에는 두 가지 단점이 있을 수 있습니다. 아마도 필요하지 않은 필드를 쿼리하게 될 것이고 두 번째는 엔터티를 다른 모델이나 DTO에 매핑해야 할 수도 있습니다.

var posts = await dbContext.Posts
    .Include(x => x.Blog)
    .Include(x => x.Comments)
    .ToListAsync();

// map to DTO     


이 쿼리의 경우 Entity Framework는 다음 SQL 코드를 생성합니다.

SELECT [p].[Id], [p].[BlogId], [p].[Title], [b].[Id], [b].[Name], 
    [c].[Id], [c].[Content], [c].[PostId]
FROM [Posts] AS [p]
INNER JOIN [Blogs] AS [b] ON [p].[BlogId] = [b].[Id]
LEFT JOIN [Comments] AS [c] ON [p].[Id] = [c].[PostId]
ORDER BY [p].[Id], [b].[Id]


이를 해결하는 기존 방법은 프로젝션을 사용하여 필요한 필드를 선택하고 엔터티 대신 DTO를 직접 사용하는 것입니다.

var post = await dbContext.Posts
    .Select(x => new PostDto()
    {
        Id = x.Id,
        Title = x.Title,
        BlogId = x.Blog.Id,
        BlogName = x.Blog.Name,
        CommentsDto = x.Comments.Select(c => new CommentDto()
        {
            Id = c.Id,
        }).ToList()
    })
    .ToListAsync();


결과는 다음과 같습니다.

SELECT [p].[Id], [p].[Title], [b].[Id], [b].[Name], [c].[Id]
FROM [Posts] AS [p]
INNER JOIN [Blogs] AS [b] ON [p].[BlogId] = [b].[Id]
LEFT JOIN [Comments] AS [c] ON [p].[Id] = [c].[PostId]
ORDER BY [p].[Id], [b].[Id]


그러나 수동으로 쿼리를 작성하는 것은 약간 지루할 수 있습니다.

구조에 ProjectTO



AutoMapper에는 .ProjectTo 방법을 사용하여 이 문제를 해결하는 간단한 방법이 있습니다.

var posts = await mapper.ProjectTo<PostDto>(dbContext.Posts, null).ToListAsync();


프로젝션과 정확히 동일한 쿼리 생성:

SELECT [p].[Id], [p].[Title], [b].[Id], [b].[Name], [c].[Id]
FROM [Posts] AS [p]
INNER JOIN [Blogs] AS [b] ON [p].[BlogId] = [b].[Id]
LEFT JOIN [Comments] AS [c] ON [p].[Id] = [c].[PostId]
ORDER BY [p].[Id], [b].[Id]

.ProjectTo<PostDto>()는 DTO에 있는 필드만 쿼리하면 됨을 Entity Framework에 알리는 IQueryable에 대한 select 절을 내보내도록 AutoMapper의 매핑 엔진에 지시합니다.
.ProjectTo의 일반적인 경우는 작은 DTO/모델을 사용하는 것입니다. 예를 들어 더 작은 DTO를 사용하여 프런트엔드에 나열할 게시물 요약을 ID, 이름 및 댓글 수와 함께 반환할 수 있습니다.

public class PostSummaryDto
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int CommentsCount { get; set; }
}


그리고 C#에서 단 한 줄만 다시 작성합니다.

var postSummary = await mapper.ProjectTo<PostSummaryDto>(dbContext.Posts, null).ToListAsync();


SQL 코드는 정말 멋져 보입니다.

SELECT [p].[Id], [p].[Title], (
        SELECT COUNT(*)
        FROM [Comments] AS [c]
        WHERE [p].[Id] = [c].[PostId]
    ) AS [CommentsCount]
FROM [Posts] AS [p]


보시다시피 코드 한 줄만 사용하여 매우 성능이 좋은 쿼리를 만들 수 있습니다. .ProjectTo에 대한 자세한 내용을 보려면 here을 읽으십시오.

좋은 웹페이지 즐겨찾기