티스토리 뷰
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 loading과 Lazy 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를 사용하여 표현할 수 없는 경우에 유용합니다.
FromSqlRaw와 FromSqlInterpolated모두 원시 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' 카테고리의 다른 글
코드로 Database Schema Migration 확인 및 실행 (0) | 2021.01.30 |
---|---|
ADO.NET과 ORM 비교 (0) | 2021.01.12 |
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 |
- Total
- Today
- Yesterday
- kiosk
- C#
- Behavior
- uno platform
- ef core
- Cross-platform
- Windows 10
- Bot Framework
- ComboBox
- UWP
- visual studio 2019
- Microsoft
- WPF
- .net
- LINQ
- #uwp
- dotNETconf
- IOT
- XAML
- Always Encrypted
- #prism
- Visual Studio 2022
- PRISM
- MVVM
- uno-platform
- #Windows Template Studio
- windows 11
- #MVVM
- Build 2016
- .net 5.0
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |