Entity Framework
EF의 최적화 기법
수색…
AsNoTracking 사용
나쁜 예 :
var location = dbContext.Location
.Where(l => l.Location.ID == location_ID)
.SingleOrDefault();
return location;
위 코드는 단순히 엔티티를 수정하거나 추가하지 않고 반환하기 만하므로 추적 비용을 피할 수 있습니다.
좋은 예 :
var location = dbContext.Location.AsNoTracking()
.Where(l => l.Location.ID == location_ID)
.SingleOrDefault();
return location;
AsNoTracking()
함수를 사용할 때 엔티티가 컨텍스트에 의해 추적되지 않는다는 것을 명시 적으로 Entity Framework에 알립니다. 이는 데이터 저장소에서 많은 양의 데이터를 검색 할 때 특히 유용 할 수 있습니다. 그러나 추적되지 않은 항목을 변경하려면 SaveChanges
를 호출하기 전에 첨부해야합니다.
필수 데이터로드 중
코드에서 자주 볼 수있는 한 가지 문제는 모든 데이터를로드하는 것입니다. 이렇게하면 서버의로드가 크게 증가합니다.
모델에 10 개의 필드가있는 "위치"라는 모델이 있지만 모든 필드가 동시에 필요하지는 않습니다. 해당 모델의 'LocationName'매개 변수 만 원한다고 가정 해 봅시다.
나쁜 예
var location = dbContext.Location.AsNoTracking()
.Where(l => l.Location.ID == location_ID)
.SingleOrDefault();
return location.Name;
좋은 예
var location = dbContext.Location
.Where(l => l.Location.ID == location_ID)
.Select(l => l.LocationName);
.SingleOrDefault();
return location;
"좋은 예"의 코드는 'LocationName'만 가져오고 다른 것은 가져 오지 않습니다.
이 예에서는 엔티티가 구체화되지 않으므로 AsNoTracking()
은 필요하지 않습니다. 아무래도 추적 할 수있는 것이 없습니다.
익명 형식으로 더 많은 필드를 가져 오는 중
var location = dbContext.Location
.Where(l => l.Location.ID == location_ID)
.Select(l => new { Name = l.LocationName, Area = l.LocationArea })
.SingleOrDefault();
return location.Name + " has an area of " + location.Area;
앞의 예제와 마찬가지로 'LocationName'과 'LocationArea'필드 만 데이터베이스에서 검색되며 익명 유형은 원하는만큼의 값을 가질 수 있습니다.
가능하면 메모리가 아니라 데이터베이스에서 쿼리를 실행하십시오.
텍사스에 얼마나 많은 카운티가 있는지 세고 싶다고합시다.
var counties = dbContext.States.Single(s => s.Code == "tx").Counties.Count();
쿼리는 정확하지만 비효율적입니다. States.Single(…)
은 데이터베이스에서 상태를로드합니다. 다음 Counties
는 두 번째 쿼리에서 모든 필드가있는 254 개 카운티를 모두로드합니다. .Count()
는로드 된 Counties
컬렉션의 메모리 에서 수행 됩니다 .
우리는 필요하지 않은 많은 데이터를로드했으며 더 잘할 수 있습니다.
var counties = dbContext.Counties.Count(c => c.State.Code == "tx");
여기에서는 하나의 쿼리 만 수행합니다.이 쿼리는 SQL에서 개수와 조인으로 변환됩니다. 데이터베이스에서 카운트 만 반환합니다. 반환되는 행, 필드 및 객체 생성을 저장했습니다.
IQueryable<T>
대 IEnumerable<T>
의 컬렉션 유형을 살펴보면 쿼리가 어디에 만들어 졌는지 쉽게 알 수 있습니다.
여러 쿼리를 비동기 및 병렬로 실행
비동기 쿼리를 사용할 때 동시에 여러 쿼리를 실행할 수 있지만 같은 컨텍스트에서는 실행할 수 없습니다. 한 쿼리의 실행 시간이 10 초인 경우 나쁜 예제의 시간은 20 초가되고 좋은 예제의 시간은 10 초가됩니다.
나쁜 예
IEnumerable<TResult1> result1;
IEnumerable<TResult2> result2;
using(var context = new Context())
{
result1 = await context.Set<TResult1>().ToListAsync().ConfigureAwait(false);
result2 = await context.Set<TResult1>().ToListAsync().ConfigureAwait(false);
}
좋은 예
public async Task<IEnumerable<TResult>> GetResult<TResult>()
{
using(var context = new Context())
{
return await context.Set<TResult1>().ToListAsync().ConfigureAwait(false);
}
}
IEnumerable<TResult1> result1;
IEnumerable<TResult2> result2;
var result1Task = GetResult<TResult1>();
var result2Task = GetResult<TResult2>();
await Task.WhenAll(result1Task, result2Task).ConfigureAwait(false);
var result1 = result1Task.Result;
var result2 = result2Task.Result;
변경 내용 추적 및 프록시 생성 비활성화
데이터 만 가져오고 수정하지 않으려는 경우 변경 내용 추적 및 프록시 생성을 해제 할 수 있습니다. 이렇게하면 성능이 향상되고 지연로드가 방지됩니다.
나쁜 예 :
using(var context = new Context())
{
return await context.Set<MyEntity>().ToListAsync().ConfigureAwait(false);
}
좋은 예 :
using(var context = new Context())
{
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ProxyCreationEnabled = false;
return await context.Set<MyEntity>().ToListAsync().ConfigureAwait(false);
}
특히 컨텍스트의 생성자 내에서이 속성을 해제하는 것이 일반적입니다. 특히 솔루션 전체에서 이러한 속성을 설정하려는 경우에는 특히 그렇습니다.
public class MyContext : DbContext
{
public MyContext()
: base("MyContext")
{
Configuration.AutoDetectChangesEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
//snip
}
스텁 엔터티 작업
Product
와 Category
가 다 대다 관계에 있다고 가정 해보십시오.
public class Product
{
public Product()
{
Categories = new HashSet<Category>();
}
public int ProductId { get; set; }
public string ProductName { get; set; }
public virtual ICollection<Category> Categories { get; private set; }
}
public class Category
{
public Category()
{
Products = new HashSet<Product>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
우리가 추가하려면 Category
A와 Product
, 우리는 제품을로드하고 그에 카테고리를 추가 할 Categories
예를 들어, :
나쁜 예 :
var product = db.Products.Find(1);
var category = db.Categories.Find(2);
product.Categories.Add(category);
db.SaveChanges();
( db
는 DbContext
서브 클래스).
그러면 Product
과 Category
사이의 접합 테이블에 하나의 레코드가 작성됩니다. 그러나이 테이블에는 두 개의 Id
값만 들어 있습니다. 하나의 작은 레코드를 만들기 위해 두 개의 엔티티를 모두로드하는 것은 리소스 낭비입니다.
보다 효율적인 방법은 최소한의 데이터 만 포함하는 스텁 엔티티 , 즉 메모리에 생성 된 엔티티 객체를 사용하는 것입니다.이 엔티티 객체는 대개 Id
값만 포함합니다. 이것은 다음과 같습니다.
좋은 예 :
// Create two stub entities
var product = new Product { ProductId = 1 };
var category = new Category { CategoryId = 2 };
// Attach the stub entities to the context
db.Entry(product).State = System.Data.Entity.EntityState.Unchanged;
db.Entry(category).State = System.Data.Entity.EntityState.Unchanged;
product.Categories.Add(category);
db.SaveChanges();
최종 결과는 동일하지만 데이터베이스에 대한 두 번의 라운드 트립을 피할 수 있습니다.
중복 방지
연결이 이미 존재하는지 여부를 확인하려는 경우 싼 쿼리로 충분합니다. 예 :
var exists = db.Categories.Any(c => c.Id == 1 && c.Products.Any(p => p.Id == 14));
다시 말하지만 전체 엔티티를 메모리로로드하지는 않습니다. 사실상 접합부 테이블을 조회하고 부울 만 리턴합니다.