관련 데이터 로드Loading Related Data

Entity Framework Core에서는 모델의 탐색 속성을 사용하여 관련 엔터티를 로드할 수 있습니다.Entity Framework Core allows you to use the navigation properties in your model to load related entities. 관련 데이터를 로드하는 데 사용되는 세 개의 일반적인 O/RM 패턴이 있습니다.There are three common O/RM patterns used to load related data.

  • 즉시 로드는 관련 데이터가 초기 쿼리의 일부로 데이터베이스에서 로드됨을 의미합니다.Eager loading means that the related data is loaded from the database as part of the initial query.
  • 명시적 로드는 관련 데이터가 나중에 데이터베이스에서 명시적으로 로드됨을 의미합니다.Explicit loading means that the related data is explicitly loaded from the database at a later time.
  • 지연 로드는 탐색 속성에 액세스할 때 관련 데이터가 데이터베이스에서 투명하게 로드됨을 의미합니다.Lazy loading means that the related data is transparently loaded from the database when the navigation property is accessed.

GitHub에서 이 문서의 샘플을 볼 수 있습니다.You can view this article's sample on GitHub.

즉시 로드Eager loading

Include 메서드를 사용하여 쿼리 결과에 포함할 관련 데이터를 지정할 수 있습니다.You can use the Include method to specify related data to be included in query results. 다음 예제에서 결과에 반환되는 블로그에는 관련 게시물로 채워진 Posts 속성이 있습니다.In the following example, the blogs that are returned in the results will have their Posts property populated with the related posts.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ToList();
}

Entity Framework Core는 이전에 컨텍스트 인스턴스에 로드된 다른 엔터티로 탐색 속성을 자동으로 수정합니다.Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. 따라서 탐색 속성에 대한 데이터를 명시적으로 포함하지 않더라도 관련 엔터티의 일부 또는 전체가 이전에 로드된 경우 속성이 채워질 수 있습니다.So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.

여러 관계의 관련 데이터를 단일 쿼리에 포함할 수 있습니다.You can include related data from multiple relationships in a single query.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .Include(blog => blog.Owner)
        .ToList();
}

여러 수준 포함Including multiple levels

ThenInclude 메서드를 사용하여 여러 수준의 관련 데이터를 포함하도록 관계를 드릴다운할 수 있습니다.You can drill down through relationships to include multiple levels of related data using the ThenInclude method. 다음 예제에서는 모든 블로그, 관련 게시물 및 각 게시물의 작성자를 로드합니다.The following example loads all blogs, their related posts, and the author of each post.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .ToList();
}

ThenInclude에 대한 여러 호출을 연결하여 관련 데이터의 추가 수준을 계속 포함할 수 있습니다.You can chain multiple calls to ThenInclude to continue including further levels of related data.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
                .ThenInclude(author => author.Photo)
        .ToList();
}

이 호출을 모두 결합하여 여러 수준 및 여러 루트의 관련 데이터를 동일한 쿼리에 포함할 수 있습니다.You can combine all of this to include related data from multiple levels and multiple roots in the same query.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
                .ThenInclude(author => author.Photo)
        .Include(blog => blog.Owner)
            .ThenInclude(owner => owner.Photo)
        .ToList();
}

포함하려는 엔터티 중 하나에 대한 여러 관련 엔터티를 포함할 수 있습니다.You may want to include multiple related entities for one of the entities that is being included. 예를 들어 Blogs를 쿼리하는 경우 Posts를 포함한 다음, PostsAuthorTags를 모두 포함할 수 있습니다.For example, when querying Blogs, you include Posts and then want to include both the Author and Tags of the Posts. 이렇게 하려면 루트에서 시작하는 각 포함 경로를 지정해야 합니다.To do this, you need to specify each include path starting at the root. 예를 들어 Blog -> Posts -> AuthorBlog -> Posts -> Tags를 지정합니다.For example, Blog -> Posts -> Author and Blog -> Posts -> Tags. 그렇다고 중복 조인을 가져온다는 의미는 아니며, 대부분의 경우 EF에서 SQL을 생성할 때 조인을 통합합니다.This does not mean you will get redundant joins; in most cases, EF will consolidate the joins when generating SQL.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Tags)
        .ToList();
}

단일 및 분할 쿼리Single and split queries

참고

이 기능은 EF Core 5.0에서 도입되었습니다.This feature is introduced in EF Core 5.0.

관계형 데이터베이스에서 모든 관련 엔터티는 기본적으로 조인을 도입하여 로드됩니다.In relational databases, all related entities are by default loaded by introducing JOINs:

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]

일반적인 블로그에 여러 관련 게시물이 있는 경우 이러한 게시물에 대한 행은 블로그의 정보를 복제하여 이른바 "데카르트 급증" 문제를 일으킬 수 있습니다.If a typical blog has multiple related posts, rows for these posts will duplicate the blog's information, leading to the so-called "cartesian explosion" problem. 로드되는 일대다 관계가 늘어나면 중복된 데이터 양이 증가하여 애플리케이션의 성능에 부정적인 영향을 줄 수 있습니다.As more one-to-many relationships are loaded, the amount of duplicated data may grow and adversely affect the performance of your application.

EF를 사용하면 지정된 LINQ 쿼리를 여러 SQL 쿼리로 분할하도록 지정할 수 있습니다.EF allows you to specify that a given LINQ query should be split into multiple SQL queries. 포함된 일대다 탐색마다 조인 대신 분할 쿼리가 추가 SQL 쿼리를 수행합니다.Instead of JOINs, split queries perform an additional SQL query for each included one-to-many navigation:

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .AsSplitQuery()
        .ToList();
}

이는 다음 SQL을 생성합니다.This will produce the following SQL:

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
ORDER BY [b].[BlogId]

SELECT [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title], [b].[BlogId]
FROM [Blogs] AS [b]
INNER JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]

이것은 조인 및 데카르트 급증과 관련된 성능 문제를 방지하지만 다음과 같은 몇 가지 단점이 있습니다.While this avoids the performance issues associated with JOINs and cartesian explosion, it also has some drawbacks:

  • 대부분의 데이터베이스는 단일 쿼리에 대해 데이터 일관성을 보장하지만 여러 쿼리에는 이러한 보장이 없습니다.While most databases guarantee data consistency for single queries, no such guarantees exist for multiple queries. 즉, 쿼리가 실행되는 동안 데이터베이스가 동시에 업데이트되면 결과 데이터가 일치하지 않을 수 있습니다.This means that if the database is being updated concurrently as your queries are being executed, resulting data may not be consistent. 이 문제는 쿼리를 직렬화 가능 또는 스냅샷 트랜잭션으로 래핑하여 완화할 수 있지만 이 방법은 그 자체의 성능 문제를 일으킬 수 있습니다.This may be mitigated by wrapping the queries in a serializable or snapshot transaction, although this may create performance issues of its own. 자세한 내용은 데이터베이스의 설명서를 참조하세요.Consult your database's documentation for more details.
  • 현재 각 쿼리는 데이터베이스로의 추가 네트워크 왕복을 의미합니다. 이로 인해 특히 데이터베이스의 대기 시간이 높은 경우(예: 클라우드 서비스) 성능이 저하될 수 있습니다.Each query currently implies an additional network roundtrip to your database; this can degrade performance, especially where latency to the database is high (e.g. cloud services). EF Core는 앞으로 여러 쿼리를 단일 왕복으로 일괄 처리하여 이를 개선할 계획입니다.EF Core will improve this in the future by batching the queries into a single roundtrip.
  • 일부 데이터베이스에서는 동시에 여러 쿼리 결과를 사용할 수 있지만(예: MARS, Sqlite를 사용한 SQL Server) 대부분의 데이터베이스에서는 지정된 시점에서 단일 쿼리만 활성화될 수 있습니다.While some databases allow consuming the results of multiple queries at the same time (SQL Server with MARS, Sqlite), most allow only a single query to be active at any given point. 즉, 이후 쿼리를 실행하기 전에 이전 쿼리의 모든 결과가 애플리케이션의 메모리에 버퍼링되어야 하기 때문에 메모리 요구 사항이 크게 늘어날 수 있습니다.This means that all results from earlier queries must be buffered in your application's memory before executing later queries, increasing your memory requirements in a potentially significant way.

안타깝게도 모든 시나리오에 적합한 관련 엔터티를 로드하는 단 하나의 전략은 없습니다.Unfortunately, there isn't one strategy for loading related entities that fits all scenarios. 단일 및 분할 쿼리의 장단점을 신중하게 고려하여 요구 사항에 적합한 쿼리를 선택하세요.Carefully consider the advantages and disadvantages of single and split queries, and select the one that fits your needs.

참고

일대일 관련 엔터티는 성능에 영향을 주지 않으므로 항상 조인을 통해 로드됩니다.One-to-one related entities are always loaded via JOINs, as this has no performance impact.

현재 SQL Server에서 쿼리 분할을 사용하려면 연결 문자열에 MultipleActiveResultSets=true 설정이 필요합니다.At the moment, use of query splitting on SQL Server requires settings MultipleActiveResultSets=true in your connection string. 이 요구 사항은 향후 미리 보기에서 제거됩니다.This requirement will be removed in a future preview.

EF Core 5.0의 향후 미리 보기에서는 쿼리 분할을 사용자 컨텍스트의 기본값으로 지정할 수 있습니다.Future previews of EF Core 5.0 will allow specifying query splitting as the default for your context.

필터링 적용 포함Filtered include

참고

이 기능은 EF Core 5.0에서 도입되었습니다.This feature is introduced in EF Core 5.0.

Include를 적용하여 관련 데이터를 로드할 경우 포함된 컬렉션 탐색에 특정 열거 가능 작업을 적용하여 결과를 필터링 및 정렬할 수 있습니다.When applying Include to load related data, you can apply certain enumerable operations on the included collection navigation, which allows for filtering and sorting of the results.

지원되는 작업은 Where, OrderBy, OrderByDescending, ThenBy, ThenByDescending, SkipTake입니다.Supported operations are: Where, OrderBy, OrderByDescending, ThenBy, ThenByDescending, Skip, and Take.

이러한 작업은 아래 예제와 같이 Include 메서드에 전달된 람다의 컬렉션 탐색에 적용해야 합니다.Such operations should be applied on the collection navigation in the lambda passed to the Include method, as shown in example below:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(blog => blog.Posts
            .Where(post => post.BlogId == 1)
            .OrderByDescending(post => post.Title)
            .Take(5))
        .ToList();
}

포함된 각 탐색은 하나의 고유한 필터 작업 집합만 허용합니다.Each included navigation allows only one unique set of filter operations. 지정된 컬렉션 탐색에 여러 Include 작업이 적용되는 경우(아래 예제의 blog.Posts) 필터 작업은 다음 중 하나에만 지정할 수 있습니다.In cases where multiple Include operations are applied for a given collection navigation (blog.Posts in the examples below), filter operations can only be specified on one of them:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
            .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .ToList();
}

또는 여러 번 포함되는 각 탐색에 동일한 작업을 적용할 수 있습니다.Alternatively, identical operations can be applied for each navigation that is included multiple times:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
            .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
            .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .ToList();
}

주의

쿼리 추적의 경우 탐색 수정으로 인해 필터링된 Include에서 예기치 않은 결과가 발생할 수 있습니다.In case of tracking queries, results of Filtered Include may be unexpected due to navigation fixup. 이전에 쿼리되고 변경 추적기에 저장된 모든 관련 엔터티는 필터 요구 사항을 충족하지 않는 경우에도 필터링된 Include 쿼리 결과에 표시됩니다.All relevant entities that have been querried for previously and have been stored in the Change Tracker will be present in the results of Filtered Include query, even if they don't meet the requirements of the filter. 이러한 상황에서 필터링된 Include를 사용하는 경우 NoTracking 쿼리를 사용하거나 DbContext를 다시 만드는 것이 좋습니다.Consider using NoTracking queries or re-create the DbContext when using Filtered Include in those situations.

예:Example:

var orders = context.Orders.Where(o => o.Id > 1000).ToList();

// customer entities will have references to all orders where Id > 1000, rathat than > 5000
var filtered = context.Customers.Include(c => c.Orders.Where(o => o.Id > 5000)).ToList();

파생 형식에 포함Include on derived types

IncludeThenInclude를 사용하여 파생 형식에만 정의된 탐색의 관련 데이터를 포함할 수 있습니다.You can include related data from navigations defined only on a derived type using Include and ThenInclude.

다음과 같은 모델을 가정합니다.Given the following model:

public class SchoolContext : DbContext
{
    public DbSet<Person> People { get; set; }
    public DbSet<School> Schools { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<School>().HasMany(s => s.Students).WithOne(s => s.School);
    }
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Student : Person
{
    public School School { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<Student> Students { get; set; }
}

다양한 패턴을 사용하여 학생인 모든 사람의 School 탐색 콘텐츠를 로드할 수 있습니다.Contents of School navigation of all People who are Students can be eagerly loaded using a number of patterns:

  • 캐스트 사용using cast

    context.People.Include(person => ((Student)person).School).ToList()
    
  • as 연산자 사용using as operator

    context.People.Include(person => (person as Student).School).ToList()
    
  • string 형식의 매개 변수를 사용하는 Include의 오버로드 사용using overload of Include that takes parameter of type string

    context.People.Include("School").ToList()
    

명시적 로드Explicit loading

DbContext.Entry(...) API를 통해 탐색 속성을 명시적으로 로드할 수 있습니다.You can explicitly load a navigation property via the DbContext.Entry(...) API.

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    context.Entry(blog)
        .Collection(b => b.Posts)
        .Load();

    context.Entry(blog)
        .Reference(b => b.Owner)
        .Load();
}

관련 엔터티를 반환하는 별도의 쿼리를 실행하여 탐색 속성을 명시적으로 로드할 수도 있습니다.You can also explicitly load a navigation property by executing a separate query that returns the related entities. 변경 내용 추적이 사용되는 경우 엔터티를 로드하면 EF Core는 새로 로드된 엔터티의 탐색 속성을 이미 로드된 엔터티를 참조하도록 자동으로 설정하고, 이미 로드된 엔터티의 탐색 속성을 새로 로드된 엔터티를 참조하도록 설정합니다.If change tracking is enabled, then when loading an entity, EF Core will automatically set the navigation properties of the newly-loaded entitiy to refer to any entities already loaded, and set the navigation properties of the already-loaded entities to refer to the newly-loaded entity.

탐색 속성의 내용을 나타내는 LINQ 쿼리를 가져올 수도 있습니다.You can also get a LINQ query that represents the contents of a navigation property.

따라서 메모리로 로드하지 않고도 관련 엔터티에 대해 집계 연산자를 실행하는 것과 같은 작업을 수행할 수 있습니다.This allows you to do things such as running an aggregate operator over the related entities without loading them into memory.

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var postCount = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Count();
}

메모리로 로드되는 관련 엔터티를 필터링할 수도 있습니다.You can also filter which related entities are loaded into memory.

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var goodPosts = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Where(p => p.Rating > 3)
        .ToList();
}

지연 로드Lazy loading

지연 로드를 사용하는 가장 간단한 방법은 Microsoft.EntityFrameworkCore.Proxies 패키지를 설치하고 이를 사용하여 UseLazyLoadingProxies를 호출하는 것입니다.The simplest way to use lazy-loading is by installing the Microsoft.EntityFrameworkCore.Proxies package and enabling it with a call to UseLazyLoadingProxies. 예를 들어:For example:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlServer(myConnectionString);

또는 AddDbContext를 사용하는 경우:Or when using AddDbContext:

.AddDbContext<BloggingContext>(
    b => b.UseLazyLoadingProxies()
          .UseSqlServer(myConnectionString));

EF Core에서 재정의할 수 있는 모든 탐색 속성에 대해 지연 로드를 사용합니다. 즉, virtual이어야 하고 상속될 수 있는 클래스에 있어야 합니다.EF Core will then enable lazy loading for any navigation property that can be overridden--that is, it must be virtual and on a class that can be inherited from. 예를 들어 다음 엔터티에서 Post.BlogBlog.Posts 탐색 속성은 지연 로드됩니다.For example, in the following entities, the Post.Blog and Blog.Posts navigation properties will be lazy-loaded.

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

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

    public virtual Blog Blog { get; set; }
}

프록시 없는 지연 로드Lazy loading without proxies

지연 로드 프록시는 엔터티 형식 생성자에 설명된 대로 ILazyLoader 서비스를 엔터티에 삽입하여 작동합니다.Lazy-loading proxies work by injecting the ILazyLoader service into an entity, as described in Entity Type Constructors. 예를 들어:For example:

public class Blog
{
    private ICollection<Post> _posts;

    public Blog()
    {
    }

    private Blog(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }

    public ICollection<Post> Posts
    {
        get => LazyLoader.Load(this, ref _posts);
        set => _posts = value;
    }
}

public class Post
{
    private Blog _blog;

    public Post()
    {
    }

    private Post(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog
    {
        get => LazyLoader.Load(this, ref _blog);
        set => _blog = value;
    }
}

이 경우에는 상속되는 엔터티 형식이나 탐색 속성이 가상일 필요가 없으며 컨텍스트에 연결되면 new로 만든 엔터티 인스턴스가 지연 로드되도록 합니다.This doesn't require entity types to be inherited from or navigation properties to be virtual, and allows entity instances created with new to lazy-load once attached to a context. 하지만 Microsoft.EntityFrameworkCore.Abstractions 패키지에 정의된 ILazyLoader 서비스에 대한 참조가 필요합니다.However, it requires a reference to the ILazyLoader service, which is defined in the Microsoft.EntityFrameworkCore.Abstractions package. 이 패키지에는 최소의 형식 집합이 포함되어 있으므로 이 패키지에 따른 영향이 거의 없습니다.This package contains a minimal set of types so that there is very little impact in depending on it. 그러나 엔터티 형식의 EF Core 패키지를 전혀 사용하지 않으려면 ILazyLoader.Load 메서드를 대리자로 삽입할 수 있습니다.However, to completely avoid depending on any EF Core packages in the entity types, it is possible to inject the ILazyLoader.Load method as a delegate. 예를 들어:For example:

public class Blog
{
    private ICollection<Post> _posts;

    public Blog()
    {
    }

    private Blog(Action<object, string> lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private Action<object, string> LazyLoader { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }

    public ICollection<Post> Posts
    {
        get => LazyLoader.Load(this, ref _posts);
        set => _posts = value;
    }
}

public class Post
{
    private Blog _blog;

    public Post()
    {
    }

    private Post(Action<object, string> lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private Action<object, string> LazyLoader { get; set; }

    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog
    {
        get => LazyLoader.Load(this, ref _blog);
        set => _blog = value;
    }
}

위의 코드에서는 Load 확장 메서드를 사용하여 대리자 사용을 좀 더 깔끔하게 합니다.The code above uses a Load extension method to make using the delegate a bit cleaner:

public static class PocoLoadingExtensions
{
    public static TRelated Load<TRelated>(
        this Action<object, string> loader,
        object entity,
        ref TRelated navigationField,
        [CallerMemberName] string navigationName = null)
        where TRelated : class
    {
        loader?.Invoke(entity, navigationName);

        return navigationField;
    }
}

참고

지연 로드 대리자에 대한 생성자 매개 변수를 “lazyLoader”라고 합니다.The constructor parameter for the lazy-loading delegate must be called "lazyLoader". 이와 다른 이름을 사용하는 구성은 향후 릴리스에 포함될 예정입니다.Configuration to use a different name than this is planned for a future release.

EF Core는 탐색 속성을 자동으로 수정하므로 개체 그래프의 주기로 끝날 수 있습니다.Because EF Core will automatically fix-up navigation properties, you can end up with cycles in your object graph. 예를 들어 블로그와 관련 게시물을 로드하면 게시물 모음을 참조하는 블로그 개체가 생성됩니다.For example, loading a blog and its related posts will result in a blog object that references a collection of posts. 각 게시물은 다시 블로그를 참조합니다.Each of those posts will have a reference back to the blog.

일부 serialization 프레임워크에서는 이러한 주기를 허용하지 않습니다.Some serialization frameworks do not allow such cycles. 예를 들어 주기가 발생하면 Json.NET은 다음 예외를 throw합니다.For example, Json.NET will throw the following exception if a cycle is encountered.

Newtonsoft.Json.JsonSerializationException: 형식이 'MyApplication.Models.Blog'인 'Blog' 속성에 대해 자체 참조 루프가 검색되었습니다.Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'Blog' with type 'MyApplication.Models.Blog'.

ASP.NET Core를 사용하는 경우 개체 그래프에서 찾은 주기를 무시하도록 Json.NET을 구성할 수 있습니다.If you are using ASP.NET Core, you can configure Json.NET to ignore cycles that it finds in the object graph. 이 작업은 Startup.csConfigureServices(...) 메서드에서 수행됩니다.This is done in the ConfigureServices(...) method in Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddMvc()
        .AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );

    ...
}

또 다른 대안은 탐색 속성 중 하나를 [JsonIgnore] 특성으로 데코레이트하는 것입니다. 이 특성은 Json.NET이 직렬화하는 동안 해당 탐색 속성을 트래버스하지 않도록 지시합니다.Another alternative is to decorate one of the navigation properties with the [JsonIgnore] attribute, which instructs Json.NET to not traverse that navigation property while serializing.