MS Tutorial을 따라해보았다~
Contoso University web app을 만들어보자.
개발환경
Visual Studio Code
MS SQL Server Express : 설치방법은 아래 링크 참조
https://bigexecution.tistory.com/252
.NET 7 SDK 설치 : 설치파일 링크는 아래에
https://dotnet.microsoft.com/en-us/download/dotnet/7.0
EF Core Nuget Packages 설치
- Microsoft.EntityFrameworkCore : EF Core basic functionality
- Microsoft.EntityFrameworkCore.Relational : Common relational database functionality
- Microsoft.EntityFrameworkCore.SqlServer : SQL Server, SQL Azure 를 위한 Database providers
- Microsoft.EntityFrameworkCore.Design : cross-platform command line tooling
ASP.NET Core 웹앱(MVC) 프로젝트 생성
PS D:\myproject\EFCoreDemo> dotnet new sln
"솔루션 파일" 템플릿이 성공적으로 생성되었습니다.
PS D:\myproject\EFCoreDemo> dotnet new mvc -o WEB
"ASP.NET Core 웹앱(Model-View-Controller)" 템플릿이 성공적으로 생성되었습니다.
PS D:\myproject\EFCoreDemo> dotnet sln add WEB
'WEB\WEB.csproj' 프로젝트가 솔루션에 추가되었습니다.
PS D:\myproject\EFCoreDemo> cd WEB
PS D:\myproject\EFCoreDemo\WEB> dotnet build
복원할 프로젝트를 확인하는 중...
복원할 모든 프로젝트가 최신 상태입니다.
WEB -> D:\myproject\EFCoreDemo\WEB\bin\Debug\net7.0\WEB.dll
빌드했습니다.
경고 0개
오류 0개
PS D:\myproject\EFCoreDemo\WEB> dotnet run
빌드하는 중...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5236
메인화면 설정
Views/Shared/_Layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Contoso University</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2020 - Contoso University - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Views/Home/Index.cshtml
@{
ViewData["Title"] = "Home Page";
}
<div class="jumbotron">
<h1>Contoso University</h1>
</div>
<div class="row">
<div class="col-md-4">
<h2>Welcome to Contoso University</h2>
<p>
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core MVC web application.
</p>
</div>
<div class="col-md-4">
<h2>Build it from scratch</h2>
<p>You can build the application by following the steps in a series of tutorials.</p>
<p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial »</a></p>
</div>
<div class="col-md-4">
<h2>Download it</h2>
<p>You can download the completed project from GitHub.</p>
<p><a class="btn btn-default" href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-mvc/intro/samples/5cu-final">See project source code »</a></p>
</div>
</div>
dotnet run해보자.
data model 생성
Models/Student.cs
using System;
using System.Collections.Generic;
namespace WEB.Models
{
public class Student
{
public int ID { get; set; }
public string? LastName { get; set; }
public string? FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment>? Enrollments { get; set; }
}
}
Models/Enrollment.cs
namespace WEB.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
Models/Course.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace WEB.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string? Title { get; set; }
public int Credits { get; set; }
public ICollection<Enrollment>? Enrollments { get; set; }
}
}
DatabaseGenerated attribute는 PK값을 DB가 생성하지 않고, 입력할 수 있도록 한다.
database context 생성
using WEB.Models;
using Microsoft.EntityFrameworkCore;
namespace WEB.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
- entity set은 database table에 해당한다.
- entity는 table의 row에 해당한다.
database가 생성될 때, EF는 DbSet property name과 동일하게 테이블을 생성한다. 특정 이름으로 수정할 수 있다.
SchoolContext 등록
Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<SchoolContext>(option => option.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=.\\SQLExpress인스턴스명;database=ContosoUniversity;uid=sa;pwd=;TrustServerCertificate=True"
}
}
test data로 DB 초기화
EnsureCreated 함수는 database가 없는 경우, 자동으로 생성한다.
using WEB.Models;
using System;
using System.Linq;
namespace WEB.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
};
foreach (Student s in students)
{
context.Students.Add(s);
}
context.SaveChanges();
var courses = new Course[]
{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
foreach (Enrollment e in enrollments)
{
context.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}
Program.cs
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
// 1. Get a database context instance from the dependency injection container.
var context = services.GetRequiredService<SchoolContext>();
// 2. Call the DbInitializer.Initialize method.
DbInitializer.Initialize(context);
}
catch(Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occured creating the DB.");
}
// 3. Dispose the context
}
app.Run();
앱 최초 실행 시, database가 생성되고 test data가 로드된다. 모델이 변경될때마다 database를 삭제하고 seed method를 수정한다음, new database로 새롭게 시작한다.
Controller, View 생성
Controllers/StudentsController.cs
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using WEB.Models;
using WEB.Data;
using Microsoft.EntityFrameworkCore;
namespace WEB.Controllers;
public class StudentsController : Controller
{
private readonly ILogger<StudentsController> _logger;
private readonly SchoolContext _context;
public StudentsController(SchoolContext context, ILogger<StudentsController> logger)
{
_logger = logger;
_context = context;
}
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
}
Views/Students/Index.cshtml
@model IEnumerable<WEB.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
앱을 실행해보자!
dotnet run!!!
PS D:\myproject\EFCoreDemo\WEB> dotnet run
빌드하는 중...
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (916ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
CREATE DATABASE [ContosoUniversity];
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (465ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
IF SERVERPROPERTY('EngineEdition') <> 5
BEGIN
ALTER DATABASE [ContosoUniversity] SET READ_COMMITTED_SNAPSHOT ON;
END;
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT 1
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (7ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [Course] (
[CourseID] int NOT NULL,
[Title] nvarchar(max) NULL,
[Credits] int NOT NULL,
CONSTRAINT [PK_Course] PRIMARY KEY ([CourseID])
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [Student] (
[ID] int NOT NULL IDENTITY,
[LastName] nvarchar(max) NULL,
[FirstMidName] nvarchar(max) NULL,
[EnrollmentDate] datetime2 NOT NULL,
CONSTRAINT [PK_Student] PRIMARY KEY ([ID])
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [Enrollment] (
[EnrollmentID] int NOT NULL IDENTITY,
[CourseID] int NOT NULL,
[StudentID] int NOT NULL,
[Grade] int NULL,
CONSTRAINT [PK_Enrollment] PRIMARY KEY ([EnrollmentID]),
CONSTRAINT [FK_Enrollment_Course_CourseID] FOREIGN KEY ([CourseID]) REFERENCES [Course] ([CourseID]) ON DELETE CASCADE,
CONSTRAINT [FK_Enrollment_Student_StudentID] FOREIGN KEY ([StudentID]) REFERENCES [Student] ([ID]) ON DELETE CASCADE
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE INDEX [IX_Enrollment_CourseID] ON [Enrollment] ([CourseID]);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE INDEX [IX_Enrollment_StudentID] ON [Enrollment] ([StudentID]);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (11ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM [Student] AS [s]) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (26ms) [Parameters=[@p0='?' (DbType = DateTime2), @p1='?' (Size = 4000), @p2='?' (Size = 4000), @p3='?' (DbType = DateTime2), @p4='?' (Size = 4000), @p5='?' (Size = 4000), @p6='?' (DbType = DateTime2), @p7='?' (Size = 4000), @p8='?' (Size = 4000), @p9='?' (DbType = DateTime2), @p10='?' (Size = 4000), @p11='?' (Size = 4000), @p12='?' (DbType = DateTime2), @p13='?' (Size = 4000), @p14='?' (Size = 4000), @p15='?' (DbType = DateTime2), @p16='?' (Size = 4000), @p17='?' (Size = 4000), @p18='?' (DbType = DateTime2), @p19='?' (Size = 4000), @p20='?' (Size = 4000), @p21='?' (DbType = DateTime2), @p22='?' (Size = 4000), @p23='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
MERGE [Student] USING (
VALUES (@p0, @p1, @p2, 0),
(@p3, @p4, @p5, 1),
(@p6, @p7, @p8, 2),
(@p9, @p10, @p11, 3),
(@p12, @p13, @p14, 4),
(@p15, @p16, @p17, 5),
(@p18, @p19, @p20, 6),
(@p21, @p22, @p23, 7)) AS i ([EnrollmentDate], [FirstMidName], [LastName], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([EnrollmentDate], [FirstMidName], [LastName])
VALUES (i.[EnrollmentDate], i.[FirstMidName], i.[LastName])
OUTPUT INSERTED.[ID], i._Position;
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (3ms) [Parameters=[@p0='?' (DbType = Int32), @p1='?' (DbType = Int32), @p2='?' (Size = 4000), @p3='?' (DbType = Int32), @p4='?' (DbType = Int32), @p5='?' (Size = 4000), @p6='?' (DbType = Int32), @p7='?' (DbType = Int32), @p8='?' (Size = 4000), @p9='?' (DbType = Int32), @p10='?' (DbType = Int32), @p11='?' (Size = 4000), @p12='?' (DbType = Int32), @p13='?' (DbType = Int32), @p14='?' (Size = 4000), @p15='?' (DbType = Int32), @p16='?' (DbType = Int32), @p17='?' (Size = 4000), @p18='?' (DbType = Int32), @p19='?' (DbType = Int32), @p20='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
INSERT INTO [Course] ([CourseID], [Credits], [Title])
VALUES (@p0, @p1, @p2),
(@p3, @p4, @p5),
(@p6, @p7, @p8),
(@p9, @p10, @p11),
(@p12, @p13, @p14),
(@p15, @p16, @p17),
(@p18, @p19, @p20);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (6ms) [Parameters=[@p0='?' (DbType = Int32), @p1='?' (DbType = Int32), @p2='?' (DbType = Int32), @p3='?' (DbType = Int32), @p4='?' (DbType = Int32), @p5='?' (DbType = Int32), @p6='?' (DbType = Int32), @p7='?' (DbType = Int32), @p8='?' (DbType = Int32), @p9='?' (DbType = Int32), @p10='?' (DbType = Int32), @p11='?' (DbType = Int32), @p12='?' (DbType = Int32), @p13='?' (DbType = Int32), @p14='?' (DbType = Int32), @p15='?' (DbType = Int32), @p16='?' (DbType = Int32), @p17='?' (DbType = Int32), @p18='?' (DbType = Int32), @p19='?' (DbType = Int32), @p20='?' (DbType = Int32), @p21='?' (DbType = Int32), @p22='?' (DbType = Int32), @p23='?' (DbType = Int32), @p24='?' (DbType = Int32), @p25='?' (DbType = Int32), @p26='?' (DbType = Int32), @p27='?' (DbType = Int32), @p28='?' (DbType = Int32), @p29='?' (DbType = Int32), @p30='?' (DbType = Int32), @p31='?' (DbType = Int32), @p32='?' (DbType = Int32), @p33='?' (DbType = Int32), @p34='?' (DbType = Int32), @p35='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
MERGE [Enrollment] USING (
VALUES (@p0, @p1, @p2, 0),
(@p3, @p4, @p5, 1),
(@p6, @p7, @p8, 2),
(@p9, @p10, @p11, 3),
(@p12, @p13, @p14, 4),
(@p15, @p16, @p17, 5),
(@p18, @p19, @p20, 6),
(@p21, @p22, @p23, 7),
(@p24, @p25, @p26, 8),
(@p27, @p28, @p29, 9),
(@p30, @p31, @p32, 10),
(@p33, @p34, @p35, 11)) AS i ([CourseID], [Grade], [StudentID], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([CourseID], [Grade], [StudentID])
VALUES (i.[CourseID], i.[Grade], i.[StudentID])
OUTPUT INSERTED.[EnrollmentID], i._Position;
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5250
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: D:\myproject\EFCoreDemo\WEB
DB와 테이블이 자동 생성되었고, PK와 FK도 자동 설정되었다.
'Entity Framework Core' 카테고리의 다른 글
.NET Core MVC] EF Core - 3.정렬, 검색, 페이징 (0) | 2023.08.01 |
---|---|
.NET Core MVC] EF Core - 2.CRUD (0) | 2023.07.31 |
Entity Framework Core] Reverse engineering (scaffolding) (0) | 2023.07.25 |
.NET CLI] .NET 7, Entity Framework Core 오프라인 설치 (0) | 2023.07.21 |
Entity Framework Core] Custom Migrations Operations (0) | 2023.07.20 |
댓글