자습서: 고급 시나리오에 대해 알아보기 - EF Core 사용하여 MVC ASP.NET
이전 자습서에서는 계층당 테이블 상속을 구현했습니다. 이 자습서에서는 Entity Framework Core를 사용하는 ASP.NET Core 웹 애플리케이션 개발의 기본 사항을 넘어서는 경우에 유의하는 데 유용한 몇 가지 항목을 소개합니다.
이 자습서에서는 다음을 수행합니다.
- 원시 SQL 쿼리 수행
- 쿼리를 호출하여 엔터티를 반환하다
- 쿼리를 호출하여 다른 형식 반환
- 업데이트 쿼리 호출
- SQL 쿼리 검사
- 추상화 계층 만들기
- 자동 변경 검색에 대해 알아보기
- EF Core 소스 코드 및 개발 계획에 대해 알아보기
- 동적 LINQ를 사용하여 코드를 간소화하는 방법 알아보기
필수 구성 요소
- 상속 구현
원시 SQL 쿼리 수행
Entity Framework를 사용할 때의 이점 중 하나는 데이터를 저장하는 특정 메서드에 코드를 너무 밀접하게 연결하지 않는다는 것입니다. SQL 쿼리 및 명령을 생성하여 이 작업을 수행하므로 직접 작성할 필요가 없습니다. 그러나 수동으로 만든 특정 SQL 쿼리를 실행해야 하는 경우 예외적인 시나리오가 있습니다. 이러한 시나리오의 경우 Entity Framework Code First API에는 SQL 명령을 데이터베이스에 직접 전달할 수 있는 메서드가 포함되어 있습니다. EF Core 1.0에는 다음 옵션이 있습니다.
엔터티 형식을 반환하는 쿼리에
DbSet.FromSql
메서드를 사용합니다. 반환된 개체는DbSet
개체에서 예상하는 형식이어야 하며 추적을 해제하지 않는 한 데이터베이스 컨텍스트에서 자동으로 추적됩니다.쿼리가 아닌 명령에는
Database.ExecuteSqlCommand
사용합니다.
엔터티가 아닌 형식을 반환하는 쿼리를 실행해야 하는 경우 EF에서 제공하는 데이터베이스 연결에 ADO.NET 사용할 수 있습니다. 이 메서드를 사용하여 엔터티 형식을 검색하더라도 반환된 데이터는 데이터베이스 컨텍스트에서 추적되지 않습니다.
웹 애플리케이션에서 SQL 명령을 실행할 때 항상 그렇듯이 SQL 삽입 공격으로부터 사이트를 보호하기 위해 예방 조치를 취해야 합니다. 이 작업을 수행하는 한 가지 방법은 매개 변수가 있는 쿼리를 사용하여 웹 페이지에서 제출한 문자열을 SQL 명령으로 해석할 수 없도록 하는 것입니다. 이 자습서에서는 사용자 입력을 쿼리에 통합할 때 매개 변수가 있는 쿼리를 사용합니다.
쿼리를 호출하여 엔터티를 반환합니다.
DbSet<TEntity>
클래스는 TEntity
형식의 엔터티를 반환하는 쿼리를 실행하는 데 사용할 수 있는 메서드를 제공합니다. 이 작업이 어떻게 작동하는지 확인하려면 부서 컨트롤러의 Details
메서드에서 코드를 변경합니다.
DepartmentsController.cs
Details
메서드에서 다음 강조 표시된 코드와 같이 부서를 검색하는 코드를 FromSql
메서드 호출로 바꿉니다.
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
var department = await _context.Departments
.FromSql(query, id)
.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync();
if (department == null)
{
return NotFound();
}
return View(department);
}
새 코드가 제대로 작동하는지 확인하려면 부서 탭을 선택한 다음 부서 중 하나에 대한 세부 정보 .
쿼리를 호출하여 다른 형식 반환
앞에서 각 등록 날짜에 대한 학생 수를 보여 주는 정보 페이지에 대한 학생 통계 표를 만들었습니다. 학생 엔터티 집합(_context.Students
)에서 데이터를 얻었고 LINQ를 사용하여 결과를 EnrollmentDateGroup
보기 모델 개체 목록에 프로젝팅했습니다. LINQ를 사용하는 대신 SQL 자체를 작성하려는 경우를 가정해 보겠습니다. 이렇게 하려면 엔터티 개체 이외의 항목을 반환하는 SQL 쿼리를 실행해야 합니다.
EF Core 1.0에서 이 작업을 수행하는 한 가지 방법은 ADO.NET 코드를 작성하고 EF에서 데이터베이스 연결을 가져오는 것입니다.
HomeController.cs
About
메서드를 다음 코드로 바꿉다.
public async Task<ActionResult> About()
{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}
using 문을 추가합니다.
using System.Data.Common;
앱을 실행하고 정보 페이지로 이동합니다. 이전과 동일한 데이터를 표시합니다.
업데이트 쿼리 호출
Contoso University 관리자가 모든 과정에 대한 학점 수를 변경하는 등 데이터베이스의 전역 변경을 수행하려고 하는 경우를 가정해 보겠습니다. 대학에 많은 과정이 있는 경우, 모든 과정들을 개별 엔티티로 검색하고 하나씩 변경하는 것은 비효율적입니다. 이 섹션에서는 사용자가 모든 과정에 대한 크레딧 수를 변경할 요소를 지정할 수 있도록 하는 웹 페이지를 구현하고 SQL UPDATE 문을 실행하여 변경합니다. 웹 페이지는 다음 그림과 같습니다.
CoursesController.cs
HttpGet 및 HttpPost에 대한 UpdateCourseCredits 메서드를 추가합니다.
public IActionResult UpdateCourseCredits()
{
return View();
}
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
if (multiplier != null)
{
ViewData["RowsAffected"] =
await _context.Database.ExecuteSqlCommandAsync(
"UPDATE Course SET Credits = Credits * {0}",
parameters: multiplier);
}
return View();
}
컨트롤러가 HttpGet 요청을 처리하면 ViewData["RowsAffected"]
아무 것도 반환되지 않으며, 이전 그림과 같이 보기에 빈 텍스트 상자와 제출 단추가 표시됩니다.
업데이트 버튼을 클릭하면 HttpPost 메서드가 호출되고, 텍스트 상자에 입력된 값을 승수로 사용합니다. 그 후 코드가 과정을 업데이트하는 SQL을 실행하고, 영향을 받은 행 수를 ViewData
보기에 반환합니다. 뷰가 RowsAffected
값을 가져오면 업데이트된 행 수가 표시됩니다.
솔루션 탐색기Views/Courses 폴더를 마우스 오른쪽 단추로 클릭한 다음 추가 > 새 항목클릭합니다.
새 항목 추가 대화 상자의 왼쪽 창에 있는 설치된ASP.NET Core 클릭하고 Razor 보기클릭하고 새 보기의 이름을 UpdateCourseCredits.cshtml
.
Views/Courses/UpdateCourseCredits.cshtml
템플릿 코드를 다음 코드로 바꿉다.
@{
ViewBag.Title = "UpdateCourseCredits";
}
<h2>Update Course Credits</h2>
@if (ViewData["RowsAffected"] == null)
{
<form asp-action="UpdateCourseCredits">
<div class="form-actions no-color">
<p>
Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
</p>
<p>
<input type="submit" value="Update" class="btn btn-default" />
</p>
</div>
</form>
}
@if (ViewData["RowsAffected"] != null)
{
<p>
Number of rows updated: @ViewData["RowsAffected"]
</p>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
과정 탭을 선택한 다음 브라우저 주소 표시줄의 URL 끝에 "/UpdateCourseCredits"를 추가하여 UpdateCourseCredits
메서드를 실행합니다(예: http://localhost:5813/Courses/UpdateCourseCredits
). 텍스트 상자에 숫자를 입력합니다.
업데이트을 클릭합니다. 영향을 받는 행 수가 표시됩니다.
목록으로 돌아가기 클릭하여 수정된 크레딧 수가 있는 과정 목록을 확인합니다.
프로덕션 코드는 업데이트로 인해 항상 유효한 데이터가 생성되도록 합니다. 여기에 표시된 간소화된 코드는 5보다 큰 숫자를 생성하기에 충분한 크레딧 수를 곱할 수 있습니다. (Credits
속성에는 [Range(0, 5)]
특성이 있습니다.) 업데이트 쿼리가 작동하지만 잘못된 데이터로 인해 크레딧 수가 5 이하라고 가정하는 시스템의 다른 부분에서 예기치 않은 결과가 발생할 수 있습니다.
원시 SQL 쿼리에 대한 자세한 내용은 원시 SQL 쿼리참조하세요.
SQL 쿼리 검사
데이터베이스로 전송되는 실제 SQL 쿼리를 볼 수 있는 것이 도움이 되는 경우도 있습니다. ASP.NET Core에 대한 기본 제공 로깅 기능은 EF Core 쿼리 및 업데이트에 대한 SQL이 포함된 로그를 작성하는 데 자동으로 사용됩니다. 이 섹션에서는 SQL 로깅의 몇 가지 예를 볼 수 있습니다.
StudentsController.cs
열고 Details
메서드에서 if (student == null)
문에 중단점을 설정합니다.
디버그 모드에서 앱을 실행하고 학생의 세부 정보 페이지로 이동합니다.
디버그 출력을 보여 주는 출력 창으로 이동하면 쿼리가 표시됩니다.
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
SELECT TOP(1) [s0].[ID]
FROM [Person] AS [s0]
WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]
여기서는 SQL이 Person 테이블에서 최대 2개의 행(TOP(2)
)을 선택합니다.
SingleOrDefaultAsync
메서드는 서버에서 1행으로 해석되지 않습니다. 그 이유는 다음과 같습니다.
- 쿼리가 여러 행을 반환하는 경우 메서드는 null을 반환합니다.
- 쿼리가 여러 행을 반환할지 여부를 확인하려면 EF에서 2개 이상을 반환하는지 확인해야 합니다.
디버그 모드를 사용하고 중단점에서 중지할 필요 없이 출력 창에서 로그 출력을 받을 수 있습니다. 출력을 보려는 지점에서 로깅을 중지하는 편리한 방법입니다. 이렇게 하지 않으면 로깅이 계속되며 다시 스크롤하여 관심 있는 부분을 찾아야 합니다.
추상화 계층 만들기
많은 개발자가 Entity Framework에서 작동하는 코드에 대한 래퍼로 리포지토리 및 작업 패턴 단위를 구현하는 코드를 작성합니다. 이러한 패턴은 데이터 액세스 계층과 애플리케이션의 비즈니스 논리 계층 간에 추상화 계층을 만들기 위한 것입니다. 이러한 패턴을 구현하면 데이터 저장소의 변경으로부터 애플리케이션을 격리할 수 있으며 자동화된 단위 테스트 또는 TDD(테스트 기반 개발)를 용이하게 할 수 있습니다. 그러나 이러한 패턴을 구현하기 위해 추가 코드를 작성하는 것이 EF를 사용하는 애플리케이션에 항상 가장 적합한 것은 아닙니다.
EF 컨텍스트 클래스 자체는 데이터 저장소별 코드에서 코드를 격리합니다.
EF 컨텍스트 클래스는 EF를 사용하여 수행하는 데이터베이스 업데이트에 대한 작업 단위 클래스 역할을 할 수 있습니다.
EF에는 리포지토리 코드를 작성하지 않고 TDD를 구현하는 기능이 포함되어 있습니다.
리포지토리 및 작업 패턴 단위를 구현하는 방법에 대한 자세한 내용은 이 자습서 시리즈 Entity Framework 5 버전참조하세요.
Entity Framework Core는 테스트에 사용할 수 있는 메모리 내 데이터베이스 공급자를 구현합니다. 자세한 내용은 테스트를 InMemory와 참조하세요.
자동 변경 감지
Entity Framework는 엔터티의 현재 값을 원래 값과 비교하여 엔터티가 변경된 방식(따라서 데이터베이스로 전송해야 하는 업데이트)을 결정합니다. 원래 값은 엔터티를 쿼리하거나 연결할 때 저장됩니다. 자동 변경 감지를 유발하는 방법 중 일부는 다음과 같습니다.
DbContext.SaveChanges
DbContext.Entry
변경추적기.항목
많은 수의 엔터티를 추적하고 루프에서 이러한 메서드 중 하나를 여러 번 호출하는 경우 ChangeTracker.AutoDetectChangesEnabled
속성을 사용하여 자동 변경 검색을 일시적으로 해제하여 성능이 크게 향상될 수 있습니다. 예를 들어:
_context.ChangeTracker.AutoDetectChangesEnabled = false;
소스 코드 및 개발 계획 EF Core
Entity Framework Core의 출처는 https://github.com/dotnet/efcore에 있습니다. EF Core 리포지토리에는 야간 빌드, 문제 추적, 기능 사양, 모임 노트 디자인 및 향후 개발 대한 로드맵포함됩니다. 버그를 제출하거나 찾아 기여할 수 있습니다.
소스 코드가 열려 있지만 Entity Framework Core는 Microsoft 제품으로 완전히 지원됩니다. Microsoft Entity Framework 팀은 허용되는 기여를 제어하고 모든 코드 변경 내용을 테스트하여 각 릴리스의 품질을 확인합니다.
기존 데이터베이스에서 리버스 엔지니어링
기존 데이터베이스의 엔터티 클래스를 포함하여 데이터 모델을 리버스 엔지니어링하려면 scaffold-dbcontext 명령을 사용합니다. 시작 자습서참조하세요.
동적 LINQ를 사용하여 코드 간소화
이 시리즈의 세 번째 자습서switch
문에서 열 이름을 하드 코딩하여 LINQ 코드를 작성하는 방법을 보여줍니다. 두 개의 열 중에서 선택할 수 있을 때는 잘 작동하지만, 열이 많은 경우 코드가 장황해질 수 있습니다. 이 문제를 해결하려면 EF.Property
메서드를 사용하여 속성의 이름을 문자열로 지정할 수 있습니다. 이 방법을 사용해 보려면 StudentsController
Index
메서드를 다음 코드로 바꿉다.
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] =
String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
ViewData["DateSortParm"] =
sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
var students = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
if (string.IsNullOrEmpty(sortOrder))
{
sortOrder = "LastName";
}
bool descending = false;
if (sortOrder.EndsWith("_desc"))
{
sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
descending = true;
}
if (descending)
{
students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
}
else
{
students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
}
int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(),
pageNumber ?? 1, pageSize));
}
감사의 글
톰 다이크스트라와 릭 앤더슨 (트위터 @RickAndMSFT)이 자습서를 썼다. Rowan Miller, Diego Vega 및 Entity Framework 팀의 다른 구성원은 코드 검토를 지원하고 자습서에 대한 코드를 작성하는 동안 발생한 문제를 디버그하는 데 도움을 주었습니다. John Parente와 Paul Goldman은 ASP.NET Core 2.2에 대한 자습서를 업데이트하는 작업을 수행했습니다.
일반적인 오류 문제 해결
다른 프로세스에서 사용하는 ContosoUniversity.dll
오류 메시지:
'을(를) 열 수 없습니다... bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll'은 다른 프로세스에서 사용되고 있기 때문입니다.
해결책:
IIS Express에서 사이트를 중지합니다. Windows 시스템 트레이로 이동하여 IIS Express를 찾아 해당 아이콘을 마우스 오른쪽 단추로 클릭하고 Contoso University 사이트를 선택한 다음 사이트 중지클릭합니다.
Up 및 Down 메서드에서 코드 없이 스캐폴드된 마이그레이션
가능한 원인:
EF CLI 명령은 코드 파일을 자동으로 닫고 저장하지 않습니다.
migrations add
명령을 실행할 때 저장되지 않은 변경 내용이 있는 경우 EF에서 변경 내용을 찾을 수 없습니다.
해결책:
migrations remove
명령을 실행하고, 코드 변경 내용을 저장하고, migrations add
명령을 다시 실행합니다.
데이터베이스 업데이트를 실행하는 동안 오류 발생
기존 데이터가 있는 데이터베이스에서 스키마를 변경할 때 다른 오류가 발생할 수 있습니다. 해결할 수 없는 마이그레이션 오류가 발생하면 연결 문자열에서 데이터베이스 이름을 변경하거나 데이터베이스를 삭제할 수 있습니다. 새 데이터베이스를 사용하면 마이그레이션할 데이터가 없으며 update-database 명령은 오류 없이 완료할 가능성이 훨씬 높습니다.
가장 간단한 방법은 appsettings.json
데이터베이스의 이름을 바꾸는 것입니다. 다음에 database update
실행하면 새 데이터베이스가 만들어집니다.
SSOX에서 데이터베이스를 삭제하려면 데이터베이스를 마우스 오른쪽 단추로 클릭하고 삭제를 클릭한 다음 데이터베이스 삭제 대화 상자에서 기존 연결 닫기 선택하고 확인클릭합니다.
CLI를 사용하여 데이터베이스를 삭제하려면 database drop
CLI 명령을 실행합니다.
dotnet ef database drop
SQL Server 인스턴스를 찾는 동안 오류가 발생했습니다.
오류 메시지:
SQL Server에 대한 연결을 설정하는 동안 네트워크 관련 또는 인스턴스 관련 오류가 발생했습니다. 서버를 찾을 수 없거나 액세스할 수 없습니다. 인스턴스 이름이 올바르고 SQL Server가 원격 연결을 허용하도록 구성되어 있는지 확인합니다. (공급자: SQL 네트워크 인터페이스, 오류: 26 - 서버/인스턴스를 지정하는 동안 오류 발생)
해결책:
연결 문자열을 확인합니다. 데이터베이스 파일을 수동으로 삭제한 경우 생성 문자열의 데이터베이스 이름을 변경하여 새 데이터베이스로 다시 시작합니다.
코드 가져오기
추가 리소스
EF Core대한 자세한 내용은 Entity Framework Core 설명서참조하세요. 책도 사용할 수 있습니다: Entity Framework Core in Action.
웹앱을 배포하는 방법에 대한 자세한 내용은 호스트 및 ASP.NET Core배포를 참조하세요.
인증 및 권한 부여와 같은 ASP.NET Core MVC와 관련된 다른 항목에 대한 자세한 내용은 ASP.NET Core 개요를 참조하세요.
신뢰할 수 있고, 안전하고, 성능이 뛰어나고, 테스트 가능하고, 확장 가능한 ASP.NET Core 앱을 만드는 방법에 대한 지침은 Enterprise 웹앱 패턴참조하세요. 패턴을 구현하는 완전한 프로덕션 품질 샘플 웹앱을 사용할 수 있습니다.
다음 단계
이 자습서에서는 다음을 수행합니다.
- 수행된 원시 SQL 쿼리
- 엔터티를 반환하는 쿼리 호출
- 다른 형식을 반환하는 쿼리 호출
- 업데이트 쿼리 호출
- 검사된 SQL 쿼리
- 추상화 계층 만들기
- 자동 변경 감지에 대해 알아보기
- EF Core 소스 코드 및 개발 계획에 대해 알아보았습니다.
- 동적 LINQ를 사용하여 코드를 간소화하는 방법을 알아보았습니다.
이렇게 하면 ASP.NET Core MVC 애플리케이션에서 Entity Framework Core를 사용하는 방법에 대한 이 자습서 시리즈가 완료됩니다. 이 시리즈는 새 데이터베이스에서 작동했습니다. 대안은 기존 데이터베이스 모델을 리버스 엔지니어링하는.
자습서: MVC, 기존 데이터베이스 사용하여 EF Core
ASP.NET Core