앞의 tutorial에서는 3 entities의 간단한 data model을 다루어보았다. 추가 entities와 관계를 설정하고 data model을 customize해보자.
Customize the Data model
attribute를 사용해서 data model을 customize해보자.
DataType attribute
학생 등록일에 대해 날짜까지만 관리하지만, web page는 data와 time을 표시한다. annotation attribute를 사용하므로 web page에서 날짜까지만 표시하도록 수정할 수 있다.
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
DataType Enum은 아래와 같이, 다양한 data types을 제공한다. 예를들어 DataType.EmailAddress로는 mailto: 링크가 생성되고, DataType.Date에는 date selector가 제공된다. DataType attribute는 validation을 제공하지 않는다.
DataType.Date는 특정 format을 지정하지 않는다. 기본으로 server의 cultureinfo를 따른다. DisplayFormat attribute는 date format을 지정하는데 사용된다.
ApplyFormatInEditMode는 값이 text box에 표기될때에도 format을 따를지 지정한다.
#region 어셈블리 System.ComponentModel.Annotations, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.ComponentModel.Annotations.dll
#endregion
namespace System.ComponentModel.DataAnnotations
{
//
// 요약:
// Represents an enumeration of the data types associated with data fields and parameters.
public enum DataType
{
//
// 요약:
// Represents a custom data type.
Custom = 0,
//
// 요약:
// Represents an instant in time, expressed as a date and time of day.
DateTime = 1,
//
// 요약:
// Represents a date value.
Date = 2,
//
// 요약:
// Represents a time value.
Time = 3,
//
// 요약:
// Represents a continuous time during which an object exists.
Duration = 4,
//
// 요약:
// Represents a phone number value.
PhoneNumber = 5,
//
// 요약:
// Represents a currency value.
Currency = 6,
//
// 요약:
// Represents text that is displayed.
Text = 7,
//
// 요약:
// Represents an HTML file.
Html = 8,
//
// 요약:
// Represents multi-line text.
MultilineText = 9,
//
// 요약:
// Represents an email address.
EmailAddress = 10,
//
// 요약:
// Represent a password value.
Password = 11,
//
// 요약:
// Represents a URL value.
Url = 12,
//
// 요약:
// Represents a URL to an image.
ImageUrl = 13,
//
// 요약:
// Represents a credit card number.
CreditCard = 14,
//
// 요약:
// Represents a postal code.
PostalCode = 15,
//
// 요약:
// Represents file upload data type.
Upload = 16
}
}
StringLength attribute
data validation rules과 error message를 attribute를 사용해서 지정할 수 있다. StringLength는 db에서 최대 길이를 지정하고 MVC앱을 위한 client/server side validation을 제공한다. minimum string length도 지정할 수 있지만, db schema에 영향을 주지 않는다.
학생 이름이 50자를 넘지 않도록 한다.
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
public string FirstMidName { get; set; }
StringLength는 이름에 공백이 들어가는 것을 막지 못한다. 이러한 제한은 정규식을 사용할 수 있다. 예를들어 아래와 같이, Regular Expression을 사용해서 첫글자는 대문자로 시작하고 나머지 알파벳 입력하도록 한다.
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
MaxLength는 StringLength와 비슷하지만 client side validation을 제공하지 않는다.
이제 database model이 변경되었다. 데이터 손실없이 migration을 사용해서 schema를 업데에트 해보자.
dotnet ef migrations add MaxLengthOnNames
dotnet ef database update
migration add는 maximum length가 짧아짐에 따라 데이터 손실이 생길 수 있다고 경고를 한다. migration file이름에 timestamp가 prefix되어서 정렬된다. database update 명령문 이전에 여러개의 migrations을 생성할 수 있다. 그 다음 적용하면, 생성된 migration 순서대로 적용된다.
앱 실행 후, 이름에 50자 이상 입력하려고 하면 입력할 수 없다.
Column attribute
attribute를 사용해서 class와 property를 database에 매핑하는 것을 제어할 수 있다. FirstMidName이라는 명칭을 사용했었는데, FirstName으로 database column명을 바꾸고 싶다면 Colum attribute를 사용할 수 있다.
Column attribute는 database가 생성될 때, Student table의 컬럼이 FirstMidName property가 FirstName으로 매핑될 것을 명시한다.
[StringLength(50)]
[Column("FirstName")]
public string FirstMidName { get; set; }
변경사항을 database에 적용해보자.
dotnet ef migrations add ColumnFirstName
dotnet ef database update
Student Entity
최종 코드는 아래와 같다.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WEB.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50)]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment>? Enrollments { get; set; }
}
}
Required attribute
not null이다. value type(DateTime, int, double, float, etc.)과 같은 non-nullable types에는 필요없다. 최소길이를 강제하기 위해, Required는 MinimumLength와 반드시 같이 사용된다.
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
Display attribute
빈칸이 없는 proprety name대신에 text boxes에 사용할 caption을 지정하기 위해 Display attribute를 사용한다.
FullName 계산 property
FullName은 2 properties를 합쳐서 만들어진다. 따라서 오직 get 접근자만 있으며, db에 생성되는 컬럼이 아니다.
Instructor Entity
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WEB.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public ICollection<CourseAssignment>? CourseAssignments { get; set; }
public OfficeAssignment? OfficeAssignment { get; set; }
}
}
몇몇 properties가 Student, Instructor entities에 동일하게 존재한다. 이후 post에서 중복코드를 제거하기 위한 리팩토링을 할 것이다.
multiple attributes는 한 줄로 작성이 가능하다. 예를들어, HireDate attribute는 다음과 같아진다.
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
CourseAssignment, OfficeAssignment navigation properties
CourseAssignment, OfficeAssignment는 navigation properties이다.
instructor는 여러개의 course를 가르칠 수 있으므로, CourseAssignment는 collection으로 정의된다.
public ICollection<CourseAssignment> CourseAssignments { get; set; }
navigation property가 복수개의 entities를 가진다면, type은 반드시 추가, 삭제, 수정이 가능해야 한다. ICollection<T>, List<T>, HashSet<T>로 지정할 수 있다.
이 앱에서 instructor는 하나의 office를 갖는다.
public OfficeAssignment OfficeAssignment { get; set; }
OfficeAssignment Entity
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WEB.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public Instructor? Instructor { get; set; }
}
}
Key attribute
Instructor, OfficeAssignment 는 1:1관계이다. office assignment는 instuctor와의 관계에서만 존재한다. 그러므로, primary key가 Instructor에게 foreign key이다. 하지만, EF는 InstructorID가 naming convention(ID 또는 classnameID)를 따르지 않기 때문에 PK로 자동 인식하지 못한다. 따라서 Key attribute를 사용해서 key로 identify한다.
entity 자체 PK를 사용하지만, naming convention을 따르지 않을때도 사용한다.
기본으로 EF는 key를 non-database-generated로 다룬다.
Instructor navigation property
Instructor entity는 nullable OfficeAssignment navigation property를 갖는다. office가 없는 instructor가 있을 수 있기 때문이다. 그리고 OfficeAssignment는 non-nullable Instuctor navigation property를 갖는다. instructor없이 office assignment가 존재할 수 없기 때문이다.
Course Entity
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WEB.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
public Department Department? { get; set; }
public ICollection<Enrollment>? Enrollments { get; set; }
public ICollection<CourseAssignment>? CourseAssignments { get; set; }
}
}
Department navigation property를 갖는 related Department entity를 가리키는 DepartmentID FK property를 갖는다. EF는 related entity에 대해 navigation property를 갖는 data model에 FK property를 추가하도록 요구하지 않는다. EF가 필요하다고 판단하면 자동으로 shadow properties로 FK를 생성한다. 하지만, data model에 FK를 갖는것이 단순하고 효율적이다.
예를들어, Course를 수정할때 로드하지 않으면 Department entity는 null이 된다. 따라서 Course entity를 수정하기 위해서 항상 Department entity를 먼저 fetch해야 한다. DepartmentID가 data model에 FK property로 포함되어 있으면, fetch할 필요가 없다.
DatabaseGenerated attribute
DatabaseGeneratedOption.None parameter를 전달받은 DatabaseGenerated attribute가 있는 property는 DB에 의해 값이 생성되지 않고, user가 제공하는 값을 사용한다.
기본으로 EF는 PK를 db생성값으로 가정한다. 하지만, Course의 경우 하나의 부서에 대해서 1000번대를 사용하고 다른 부서에 대해 2000번대를 사용하는 등 특정 번호를 사용할 수 있다.
namespace System.ComponentModel.DataAnnotations.Schema
{
//
// 요약:
// Represents the pattern used to generate values for a property in the database.
public enum DatabaseGeneratedOption
{
//
// 요약:
// The database does not generate values.
None = 0,
//
// 요약:
// The database generates a value when a row is inserted.
Identity = 1,
//
// 요약:
// The database generates a value when a row is inserted or updated.
Computed = 2
}
}
Foreign key, navigation properties
하나의 강의는 한 부서에 할당된다.
public int DepartmentID { get; set; }
public Department Department { get; set; }
하나의 강의에 여러명의 학생이 등록할 수 있다.
public ICollection<Enrollment> Enrollments { get; set; }
여러 강사가 하나의 강의를 가르칠 수 있다.
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Department entity
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WEB.Models
{
public class Department
{
public int DepartmentID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
public ICollection<Course> Courses { get; set; }
}
}
Column attribute
앞에서 Column attribute를 매핑할 컬럼명을 수정하기 위해 사용했다. 여기서는 SQL data type을 meony로 수정하기 위해 사용했다.
[Column(TypeName="money")]
public decimal Budget { get; set; }
TypeName을 꼭 지정해야 하는것은 아니다. EF가 기본으로 설정해주지만, SQL Server decimal type이 아닌 money를 사용하려면 attribute를 통해 지정해주어야 한다.
Foreign key, navigation properties
한 department는 administrator가 있을 수도 없을 수도 있다. administrator는 반드시 instructor이다.
따라서, InstructorID 가 nullable FK로 포함되었다. navigation property명은 Administrator이지만 Instructor entity를 갖고 있다.
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
한 department는 많은 강의를 갖는다.
public ICollection<Course> Courses { get; set; }
Enrollment Entity
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
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; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
Foreign key, navigation properties
등록기록은 하나의 강의에 대한 정보이며, CourseID는 FK이고, Course navigation property가 있다.
public int CourseID { get; set; }
public Course Course { get; set; }
등록기록은 학생 한명에 대한 정보이며, StudentID는 FK이고, Student navigation property가 있다.
public int StudentID { get; set; }
public Student Student { get; set; }
다대다 관계
Student와 Course 사이는 다대다 관계이다. Enrollment는 database에서 다대다 조인 테이블(with payload) 역할을 한다. with payload란 Enrollment 테이블이 FK외에 추가 데이터(pk, Grade property)를 가진다는 의미이다.
아래 diagram은 EF Power Tools을 사용하여 생성된 것이다.
관계는 line 끝에 1과 다른쪽 끝에 (*)를 갖으며, 1:N 관계를 나타낸다.
Enrollment table이 grade 정보 없이 2개의 FK(CourseID, StudentID)만 갖는다면, 순수한 조인테이블이 된다.
EF Core는 다대다 관계를 위한 암시적 조인 테이블을 지원한다.
CourseAssignment
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WEB.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}
Join entity names
Instructor와 Courses 다대다 관계를 위해 db에 조인테이블이 필요하다. 조인테이블명은 EntityName1EntityName2로 이 경우엔 CourseInstructor가 될 수 있지만, 관계를 설명하는 고유의 이름을 선택하는 것이 좋다.
Composite Key
FK 둘 다 not nullable이므로, 별도의 PK없이 함께 테이블의 각 항을 유일하게 구분한다. InstructorID, CourseID는 복합키로 동작한다. EF에서 복합키를 지정하는 방법은 Fluent API를 이용하는 방법이 유일하다.
복합키는 동일한 instructor와 course에 대해서 중복 rows를 방지한다. Enrollment join entity는 자체 PK가 있어서 중복이 가능하다. 중복을 막으려면 FK에 대해 unique index를 설정한다.
database context 업데이트
Data/SchoolContext.cs 파일을 아래와 같이 업데이트 한다.
새로운 entities를 추가했고 CourseAssignment의 복합키를 설정했다.
using ContosoUniversity.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; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}
Fluent API 대체에 대해서
이 post에서는 attributes를 사용할 수 없는 database mapping을 위해서만 fluent API를 사용했다. 하지만, fluent API를 사용해서도 대부분의 formatting, validation, mappin rules를 지정할 수 있다.
어떤 개발자들은 entity classes를 clean하게 유지하기 위해 fluent API만 사용하기도 한다. fluent API와 attribute를 혼용할 수도 있다. 하지만 둘 중 하나의 방법으로 일관되게 사용하는 것이 추천된다. 둘다 사용하다가 conflict가 있으면 Fluent API가 attributes를 override한다.
Seed database with test data
Data/DbInitializer.cs를 아래와 같이 수정한다.
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using WEB.Models;
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("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2013-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 instructors = new Instructor[]
{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};
foreach (Instructor i in instructors)
{
context.Instructors.Add(i);
}
context.SaveChanges();
var departments = new Department[]
{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID }
};
foreach (Department d in departments)
{
context.Departments.Add(d);
}
context.SaveChanges();
var courses = new Course[]
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
Location = "Thompson 304" },
};
foreach (OfficeAssignment o in officeAssignments)
{
context.OfficeAssignments.Add(o);
}
context.SaveChanges();
var courseInstructors = new CourseAssignment[]
{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
};
foreach (CourseAssignment ci in courseInstructors)
{
context.CourseAssignments.Add(ci);
}
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").ID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
context.SaveChanges();
}
}
}
Add a migration
migration add 하면 데이터 손실에 대한 경고가 나온다.
PS D:\myproject\EFCoreDemo\WEB> dotnet ef migrations add ComplexDataModel
Build started...
Build succeeded.
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'
database update하면 에러가 난다.
때때로 data가 있는 상태에서 migration을 실행하는 경우, FK 제약조건을 만족하기 위해 나머지 데이터를 INSERT할 필요가 있다. Up method에 Course 테이블에 not null DepartmentID FK를 추가하는 아래 코드가 있다.
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
Course 테이블에 이미 rows가 있기 때문에, AddColumn 동작은 실패된다. 이번 post에서는 새로운 database에 migration을 실행할 예정이다. 하지만 production 앱인 경우, 기존 데이터를 다루며 migration을 해야 한다.
기존 데이터와 함께 migration을 하기 위해서, 새로운 컬럼에 default value를 주도록 code를 수정해야 한다.
1. {timestamp}_ComplexDataModel.cs 파일을 연다.
2. Course table에 DepartmentID 컬럼 추가부분 소스를 주석처리 한다.
3. Department 테이블 생성 코드 뒤에 아래 코드를 추가한다.
migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
// Default value for FK points to department created above, with
// defaultValue changed to 1 in following AddColumn statement.
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
nullable: false,
defaultValue: 1);
DB connection string 변경
EF에서 새로운 빈 database를 만들기 위해서 appsettings.json파일의 connection string에 있는 database이름을 수정하면 된다.
ContosoUniversity3으로 변경해보자. database이름을 변경하지 말고, 삭제해도 된다.
dotnet ef database drop
이름 변경 후, migration을 database에 적용해보자.
dotnet ef database update
test data 입력을 위해 app을 실행한다.
'Entity Framework Core' 카테고리의 다른 글
.NET Core MVC] EF Core - 7. DB 동시성 제어(1) tracking property (0) | 2023.08.29 |
---|---|
.NET Core MVC] EF Core - 6.related data CRUD (0) | 2023.08.28 |
.NET Core MVC] EF Core - 4.Migrations (0) | 2023.08.07 |
.NET Core MVC] EF Core - 3.정렬, 검색, 페이징 (0) | 2023.08.01 |
.NET Core MVC] EF Core - 2.CRUD (0) | 2023.07.31 |
댓글