티스토리 뷰

Entity Framework Core 101 시리즈의 마지막 편으로 소제목은 Performance Tips입니다.

Change Tracking

엔티티 프레임웍에서 쿼리를 하면, 결과 세트를 메모리 스냅 샷으로 저장하고, 그 엔티티에 대한 수정이 발생되면 나중에 데이터베이스에 기록을 위해 사용 됩니다.

그러나, 단순 조회 용도로 쿼리를 하는 경우라면 이러한 스냅 샷을 생성하지 않도록해서 리소스를 절약할 수 있습니다.

 

Entity Framework Core 시작(4/5) 에서 작성했던 소스를 계속 사용합니다.

 

Customer Index 페이지는 Customer의 목록을 출력하는 화면으로 데이터를 수정하지 않습니다.

그렇기 때문에 아래와 같이 .AsNoTracking을 추가하여 읽기 전용으로 쿼리를 할 수 있습니다.

    //Index.cshtml.cs
    
    public class IndexModel : PageModel
    {
        private readonly ContosoPets3.Data.ContosoPetsContext _context;

        public IndexModel(ContosoPets3.Data.ContosoPetsContext context)
        {
            _context = context;
        }

        public IList<Customer> Customer { get;set; }

        public async Task OnGetAsync()
        {
            Customer = await _context.Customers
                .AsNoTracking()
                .ToListAsync();
        }
    }

Eager loading and Lazy loading

Entity Framework Core에서는 navigation properties를 이용하여 관련 항목을 한번에 로드할 수 있습니다. 예를 들면 A Customer와 연결된 Order 목록을 A Customer 조회시에 함께 불러오는 것입니다.

 

연결된 항목을 로드하는 두가지 방식이 있는데 Eager loadingLazy loading입니다.

 

Eager loading 방식

.Include를 이용해서 Customer와 Order를 함께 쿼리 합니다.

//Details.cshtml.cs

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Customer = await _context.Customers
                .Include(c => c.Orders)
                .FirstOrDefaultAsync(m => m.Id == id);

            if (Customer == null)
            {
                return NotFound();
            }
            return Page();
        }

Details.cshtml에 Order 조회용 table 추가

    <table class="table">
        <thead>
            <tr>
                <th>
                    Id
                </th>
                <th>
                    OrderPlaced
                </th>
                <th>
                    OrderFulfilled
                </th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @if (Model.Customer.Orders != null)
            {
                @foreach (var item in Model.Customer.Orders)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Id)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.OrderPlaced)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.OrderFulfilled)
                        </td>
                    </tr>
                }
            }
            else
            {
                <tr>
                    No data
                </tr>
            }
        </tbody>
    </table>
Orders 테이블에 데이터는 수동으로 추가했습니다.

Lazy loading 방식

* 방금 추가했던 .Include를 삭제 합니다.

* NuGet package에서 Microsoft.EntityFrameworkCore.Proxies를 프로젝트에 추가합니다.

* Startup.cs 파일에 ConfigureServices 메소드에서 .UseLazyLoadingProxies를 추가합니다.

//Startup.cs

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddDbContext<ContosoPetsContext>(options =>
                options
                .UseLazyLoadingProxies()
                .UseSqlite("Data Source=ContosoPets.db"));
        }

* 마지막으로 모든 클래스에서 navigation properties를 virtual로 수정합니다.

    public class Customer
    {
#nullable enable 
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string? Address { get; set; }
        public string? Phone { get; set; }
#nullable disable
        public virtual ICollection<Order> Orders { get; set; }
    }
    
    public class Order
    {
        public int Id { get; set; }
        public DateTime OrderPlaced { get; set; }
        public DateTime? OrderFulfilled { get; set; }
        public int CustomerId { get; set; }

        public virtual Customer Customer { get; set; }
        public virtual ICollection<ProductOrder> ProductOrders { get; set; }
    }
    
    public class ProductOrder
    {
        public int Id { get; set; }
        public int Quantity { get; set; }
        public int ProductId { get; set; }
        public int OrderId { get; set; }

        public virtual Order Order { get; set; }
        public virtual Product Product { get; set; }
    }    

 실행하면 동일한 결과가 출력됩니다.

FromSqlRaw와 FromSqlInterpolated 

Entity Framework Core를 사용하여 데이터베이스 작업 할 때 원시 SQL 쿼리를 직접 사용 할 수 있습니다. 이 방법은 원하는 쿼리를 LINQ를 사용하여 표현할 수 없는 경우에 유용합니다.

 

FromSqlRawFromSqlInterpolated모두 원시 SQL 쿼리를 실행하는 메소드이지만, FromSqlInterpolated를 사용하면, SQL injection 공격에서 쿼리를 보호할 수 있습니다. 그렇기 때문에 가능하면 FromSqlInterpolated를 사용하시기 바랍니다.

 

아래 쿼리는 Customer의 Id가 2보다 작은 것만 조회해서 출력하게 됩니다.

//Index.cshtml.cs

        public async Task OnGetAsync()
        {
            var condition = 2;
            Customer = await _context.Customers
                .FromSqlInterpolated($"SELECT * FROM Customers WHERE Id < {condition}")
                .ToListAsync();
        }

Find와 FindAsync

Entity Framework Core가 메모리 내 스냅 샷을 사용하여 엔터티의 변경 사항을 추적하는 방법에 대해 이야기를 했었습니다. 이렇게 엔터티가 메모리에 캐시 된 경우 Find 또는 FindAsync 메서드를 이용해서 검색하면, 리소스를 절약 할 수 있습니다.

 

삭제 메소드에 적용한 이유는 삭제를 하기 위해서는 이미 해당 데이터를 한번 이상 조회를 했다는 전재 조건이 성립하기 때문입니다.

//Delete.cshtml.cs

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Customer = await _context.Customers.FindAsync(id);

            if (Customer == null)
            {
                return NotFound();
            }
            return Page();
        }

DbContext Pool

데이터베이스 컨텍스트를 사용할 때마다 개체를 만들고 파괴하는 데 들어가는 일정량의 오버 헤드가 있습니다. 데이터베이스 컨텍스트 풀링을 사용하여 데이터베이스 컨텍스트 개체를 반복해서 재사용함으로써 오버 헤드를 줄이는 방법이 있씁니다. 데이터베이스 컨텍스트 풀링을 사용하려면 AddDbContext를 사용하는 대신 AddDbContextPool 메서드를 사용하기 만하면됩니다.

//Startup.cs

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddDbContextPool<ContosoPetsContext>(options =>
                options
                .UseLazyLoadingProxies()
                .UseSqlite("Data Source=ContosoPets.db"));
        }

Entity Framework Core의 기능에 대해 더 자세히 알고 싶은 분들은 Microsoft Learning에서 더 자세하게 배울 수 있습니다. 여기를 참고합니다.

 

사용했던 셈플 코드는 여기를 참고 합니다.

 

'Entity Framework Core' 카테고리의 다른 글

ADO.NET과 ORM 비교  (0) 2021.01.12
Entity Framework Core 시작(5/5)  (0) 2021.01.07
Entity Framework Core 시작(4/5)  (0) 2020.12.30
Entity Framework Core 시작(3/5)  (0) 2020.12.28
SQL Sever Express vs SQLite  (0) 2020.12.27
Entity Framework Core 시작(2/5)  (0) 2020.12.19
댓글
댓글쓰기 폼