본문 바로가기
Entity Framework Core

.NET Core MVC] EF Core - 1.기본설정(Model, DbContext)

by Fastlane 2023. 7. 27.
728x90
반응형

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">
            &copy; 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 &raquo;</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 &raquo;</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도 자동 설정되었다. 

 

728x90
반응형

댓글