Entity Framework Core 소개
객체와 표를 잇는 방식입니다.
형식으로 적고 SQL 은 맡깁니다
지금까지 할일 목록을 List<Todo> 에 담아 두었습니다.
프로그램을 끄면 사라집니다. 남기려면 데이터베이스에 담아야 하는데, 그러자면 표를
만들고 SQL 을 적어야 합니다.
Entity Framework Core 는 그 사이를 잇습니다. 우리는 C# 형식과 LINQ 로 적고, EF Core 가 그것을 표와 SQL 로 바꿉니다. 이런 것을 ORM(Object-Relational Mapping)이라고 부릅니다.
모든 프로젝트가 ORM 을 사용하는 것은 아닙니다. 저장 프로시저를 직접 적고 그 결과를 형식에 담는 방식도 널리 사용합니다. 질의를 손으로 다듬어야 하거나, 데이터베이스 쪽에 이미 규칙이 들어 있는 경우입니다. 이 트랙은 EF Core 를 다루지만, 그것이 유일한 방법은 아니라는 것을 알아 두십시오.
형식 하나와 DbContext 하나
담을 형식을 보통의 클래스로 적고, 그것을 다루는 자리를
DbContext 로 만듭니다.
public class Todo { public int Id { get; set; } public string Title { get; set; } = ""; public bool Done { get; set; } public int Priority { get; set; } } public class TodoContext(string path) : DbContext { public DbSet<Todo> Todos => Set<Todo>(); protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite($"Data Source={path}"); }
이 코드는 데이터베이스가 있어야 하므로 브라우저에서 실행할 수 없습니다. 아래에서 질의가 언제 실행되는지만 따로 실행해 볼 수 있습니다.
표를 적지 않았는데 만들어졌습니다. 규약을 따랐기 때문입니다.
Id라는 이름은 기본 키로 봅니다.TodoId도 같습니다.- 정수 기본 키는 자동으로 번호가 매겨집니다.
- 물음표 없는
string은 NOT NULL 입니다. 2.3에서 본 규칙이 여기에도 적용됩니다. DbSet속성 이름이 표 이름이 됩니다.
규약과 다르게 두려면 따로 적습니다. 3.2에서 봅니다.
LINQ 가 SQL 이 됩니다
읽을 때는 SQL 대신 LINQ 로 적습니다. 우리가 적은 것이 어떤 SQL 이 되는지
ToQueryString() 으로 볼 수 있습니다.
var 남은것 = db.Todos.Where(t => !t.Done).OrderByDescending(t => t.Priority); Console.WriteLine(남은것.ToQueryString()); foreach (var t in 남은것) Console.WriteLine($"{t.Id} {t.Title} ({t.Priority})");
거르기와 정렬이 SQL 안으로 들어갔습니다. 세 줄을 모두 가져와 C# 에서 거른 것이 아닙니다. 데이터베이스가 두 줄만 돌려주었습니다. 줄이 수십만 개일 때 이 차이가 큽니다.
처음에는 ToQueryString() 을 자주 찍어 보십시오.
LINQ 를 어떻게 적느냐에 따라 SQL 이 달라집니다. 어떤 것은 SQL 로
바뀌지 못해 자료를 모두 가져온 뒤 C# 에서 거르게 되는데, 그때도 오류는 나지
않습니다.
질의는 순회할 때 실행됩니다
db.Todos.Where(…) 라고 적은 순간에는
아무 일도 일어나지 않습니다. 질의를 담아 두었을 뿐이고, 순회할
때 SQL 이 나갑니다. 그래서 두 번 순회하면 두 번 나갑니다.
var 질의 = db.Todos.Where(t => !t.Done); // 여기서는 나가지 않습니다 foreach (var t in 질의) { } foreach (var t in 질의) { } var 담아둠 = 질의.ToList(); // 여기서 한 번 나갑니다 foreach (var t in 담아둠) { } foreach (var t in 담아둠) { }
같은 것을 보통의 List 로도 볼 수 있습니다. 아래를
실행해 언제 살펴보는지 확인해보십시오.
var 원본 = new List<Todo> { new("우유 사기", false), new("책 반납", true), new("세차", false) }; var 질의 = 원본.Where(t => { Console.WriteLine(" 살펴봅니다 : " + t.Title); return !t.Done; }); Console.WriteLine("질의를 만들었습니다."); Console.WriteLine("첫 번째 순회"); foreach (var t in 질의) Console.WriteLine(" 남음 : " + t.Title); Console.WriteLine("두 번째 순회"); foreach (var t in 질의) Console.WriteLine(" 남음 : " + t.Title); Console.WriteLine("담아 둔 뒤 두 번 순회"); var 담아둠 = 질의.ToList(); foreach (var t in 담아둠) { } foreach (var t in 담아둠) { } Console.WriteLine(" (담을 때 한 번만 살펴봅니다)"); record Todo(string Title, bool Done);
코드는 고쳐서 실행해 볼 수 있습니다. 처음 누를 때만 실행기를 내려받느라 잠시 걸립니다. 적은 코드는 서버로 나가지 않습니다.
"살펴봅니다" 와 "남음" 이 번갈아 나옵니다. 하나를 찾을 때마다 돌려주고 다음을 찾으러 갑니다. 미리 다 탐색해 두는 것이 아닙니다. EF Core 의 질의도 모양이 같고, 다만 살펴보는 일을 데이터베이스가 대신합니다.
가져온 것을 지켜보고 있습니다
읽어 온 객체의 속성을 바꾸면 SaveChanges() 가
바뀐 것만 골라 UPDATE 를 만듭니다. 무엇을 바꿨는지 우리가 적지
않아도 됩니다. EF Core 가 가져온 것을 기억해 두고 비교하기 때문입니다.
var 하나 = db.Todos.First(t => t.Title == "세차"); 하나.Done = true; Console.WriteLine("SaveChanges 전 : " + db.Entry(하나).State); db.SaveChanges(); Console.WriteLine("SaveChanges 후 : " + db.Entry(하나).State); // 고칠 일이 없다면 지켜보지 않게 할 수 있습니다 var 안함 = db.Todos.AsNoTracking().First(); Console.WriteLine("AsNoTracking : " + db.Entry(안함).State);
지켜보는 데에도 값이 듭니다. 가져온 것을 모두 기억해 두어야 하기 때문입니다.
화면에 보여 주기만 하고 고치지 않는 조회라면
AsNoTracking() 을 붙이는 편이 빠르고 메모리도 덜
사용합니다.
EF Core 와 EF 6 는 다릅니다
이름이 비슷한 Entity Framework 6 은 .NET Framework 시절의
것으로, EF Core 와 별개입니다. 1.1에서 본 ASP.NET 과 ASP.NET Core 의 관계와
같습니다. 인터넷 예제에서 System.Data.Entity 를
사용하는 것은 옛것입니다.
EF Core 의 버전은 .NET 을 따라갑니다. .NET 10 이면 EF Core 10 이고, 함께 나옵니다.
직접 해보기
위 실행 예제에서 첫 번째와 두 번째 순회가 같은 일을 되풀이합니다. 한 번만 살펴보도록 고쳐보세요.
표 이름을 Todos 가 아니라 TODO_LIST 로, 기본 키 속성 이름을 Id 가 아니라 Num 으로 두고 싶습니다. 어떻게 할지 짐작해 적어보세요(3.2에서 확인합니다).
아래 두 줄은 같은 결과를 냅니다. 그런데 하나는 줄이 늘수록 크게 느려집니다. 어느 쪽이고 왜 그런지 적어보세요.
① db.Todos.Where(t => !t.Done).Count() ② db.Todos.ToList().Where(t => !t.Done).Count()
ToList() 를 부른 자리에서 SQL 이 나갑니다. 그 뒤의 것은 무엇 위에서 도는 것입니까.- 형식과
DbContext를 적으면 EF Core 가 표와 SQL 을 만듭니다. Id·물음표 없는string같은 규약이 표의 모양을 정합니다.- 질의는 적을 때가 아니라 순회할 때 실행됩니다. 두 번 순회하면 두 번 나갑니다.
- 가져온 것을 지켜보고 있어
SaveChanges()가 바뀐 것만 고칩니다. 고칠 일이 없으면AsNoTracking()을 붙입니다. ToQueryString()으로 어떤 SQL 이 되는지 확인하십시오. 잘못 적어도 오류가 나지 않습니다.