Entity Framework Core IQueryable과 IEnumerable

냐옹·2024년 4월 9일
0

.NET

목록 보기
6/31

Entity Framework Core
IQueryable과 IEnumerable
LINQ나 람다를 쓰다보면 이 두 인터페이스를 많이 마주치게 되는데, 잘 알아두어야 할 것 같다. 먼저IEnumerable은 순회가능이다. 그리고 IQueryable은 쿼리가능하다는 건데, 실제로 메모리에 올라간 상태는 아니다.
예를 들어서 var linqResult = from 덩어리~~ select ~~ 어쩌구 해서 만들어진 linqResult는 링큐의 결과로서 IEnumerable을 구현한 개체이다. 그리고 from 뒤에 나오는 덩어리는 IQueryable 인터페이스를 구현한 개체이다. 링큐나 람다를 사용할 일이 많으므로, IQueryable을 구현한 개체가 어떤 것들이 있는지 살펴보겠다.
먼저 EF core에서 DBContext 에서 반환되는 DbSet는 쿼리 가능하다. 그리고 컬렉션이 쿼리 가능하면 좋겠다는 생각을 하겠는데 가능하다. 다만 변환이 필요하다. System.Linq 네임스페이스의 Queryable 클래스에 AsQueryable이라는 확장 메서드가 있다. 이 메서드를 사용하면 IEnumerable를 구현하는 메소리 내 컬렉션을 IQueryable로 변환할 수 있다. 변환된 컬렉션은 LINQ to Objects 쿼리의 표현식 트리를 생성하지만, 실제 쿼리 실행은 여전히 메모리에서 이루어진다.
List numbers = new List{1,2,3};
IQueryable queryableNumbers = numbers.AsQueryable();

즉시로딩, 지연로딩, 명시적로딩
즉시로딩 eager loading 전략을 사용하면 엔티티를 조회할 때 해당 엔티티와 관련된 다른 엔티티나 컬렉션도 즉히 함께 로딩된다. 이것은 주로 EF Core에서 Include()를 사용하여 구현한다. 즉시로딩을 사용하는 목적은 두가지이다. 첫번째는 성능 최적화, 두번째는 복잡한 데이터 구조 처리이다.
성능최적화 필요한 모든 데이터를 단일 쿼리를 통해 한번에 가져옴으로써, 여러번에 쿼리를 수행하는 것보다 데이터베이스에 대한 요청횟수를 줄일 수 있다.
복잡한 데이터구조처리
복잡한 데이터구조를 다룰때, 관련된 엔티티들을 미리 로드해두면 로직을 구현하기가 훨씬 쉽다.
메모리 사용량이 급격하게 많이 사용될 수 있다. 그러므로 필요한 데이터만 즉시로딩한다.

지연로딩 lazy loading 은 ORM에서 사용되는 데이터 로딩 전략 중 하나로, 연관된 엔티티나 컬렉션이 실제로 접근될 때까지 데이터로드를 지연시킨다. 지연로딩을 사용하면 초기 쿼리 실행시에 필요하지 않은 데이터를 로드하지 않아서 성능을 최적화할 수 있지만, 잘못쓰면 오히려 성능 저하가 유발된다.
장점으로는 초기쿼리 성능개선 / 메모리 효율성 / 사용편의성(개발자가 복잡한 쿼리 로직을 작성하지 않고도, 필요할 때 자동으로 관련 데이터를 로드할 수 있다. 그래서 개발 과정이 단순화된다. )가 있고
단점으로는 즉시로딩과 다르게 단일쿼리가 아니다. N+1쿼리라고 하는데, 각 엔티티의 관련 데이터를 접근할 때마다 별도의 쿼리가 실행되기 때문에 많은 수의 연관 데이터에 접근하는 경우에 예상치 못한 많은 쿼리가 발생할 수 있다.
*. EF core 에서는 프록시 기반의 지연로딩을 지원하는데, 필요한 Nuget 패키지를 설치해야하고, 모델의 네비게이션 프로퍼티를 virtual로 선언해야 한다.

명시적 로딩 explicit loading은 EF core에서 제공하는 데이터 로딩 전략 중 하나로 연관된 엔티티나 컬렉션을 즉시로딩하거나 지연로딩하지 않는다. 개발자가 명시적으로 요청할 때만 데이터를 로드한다. 명시적 로딩은 특정 시점에 따른 로딩을 개발자가 제어하기 때문에 이를 통해서 조건부 로딩이 가능하다. 이는 성능 최적화로 이어진다.
영향 받은 데이터 수 출력
일단 데이터베이스 컨텍스트 DbContext 같은 경우에 일회용이며, 단일 작업단위 원칙에 따라서 설계되었다. 때문에 using한정자를 잘 사용해서 컨텍스트가 잘 닫히게 해야하는데, 성능을 신경쓰지 않는 작업이다. 하면 MARS multiple active result sets 를 설정해주면 되긴하다. ( 근데 왠만하면 안하는게 낫겠지? )
DbContext.SaveChanges() 는 정수를 반환하는데, 이 메서드의 실행으로 영향을 받은 항목의 수이다.

DbContext 데이터 저장
문서 참고 데이터 저장 - EF Core | Microsoft Learn
삭제 추가 수정 어떤 작업을 하던 마지막에 디비에 저장하는건 한번만 하면 된다.

동시성 토큰
[Timestamp] 어트리뷰트는 EF Core에서 엔터티의 버전을 관리하는데에 사용된다. 이 필드는 데이터베이스 트랜잭션동안에 자동으로 변경되며, 주로 동시성 관리에 활용된다.

동시성 관리 : 이 어트리뷰트는 엔터티가 마지막으로 읽힌 시점 이후에 데이터가 변경 되었는지 여부를 확인하는데에 사용된다. 이는 여러 사용자 또는 프로세스가 동일한 데이터에 동시에 접근하려고 할때 발생할 수 있는 동시성 충돌을 방지하는데에 도움이 된다.

처음에 엔터티를 저장할때 Version 필드는 자동으로 값이 생성되고 저장된다.
엔터티를 업데이트할 때마다, 이 Version 필드는 새로운 값으로 자동 업데이트 된다. 이것은 데이터베이스의 각 트랜잭션이 고유한 버전 값을 갖게 함으로써, 데이터가 마지막으로 읽힌 이후 변경되었는지 여부를 추적하는데 사용된다.
데이터를 업데이트하려고 시도할때, EF Core는 현재 Version 값과 데이터베이스에 저장된 Version 값이 일치하는지 확인한다. 만약에 값이 일치하지 않으면 다른 사용자 또는 프로세스가 이미 해당 엔터티를 변경했음을 의미하고 DbUpdateConcurrencyException 예외가 발생하여 어플리케이션에서 이를 처리할 수 있다.

public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }

[Timestamp]
public byte[] Version { get; set; }

}

var person = context.People.Single(b => b.FirstName == "John");
person.FirstName = "Paul";
context.SaveChanges();
가 실제로 쿼리에서
UPDATE [People] SET [FirstName] = @p0
WHERE [PersonId] = @p1 AND [Version] = @p2;
이렇게 들어간다. 보면 Version에 대한 내용이 들어가 있다.

0개의 댓글