C # 엔터티 프레임 워크 : .Find와 .Include를 모델 개체에 결합하려면 어떻게해야합니까?


145

mvcmusicstore 연습 자습서를하고 있습니다. 앨범 관리자 용 스캐 폴드를 만들 때 무언가를 발견했습니다 (삭제 편집 추가).

코드를 우아하게 작성하고 싶기 때문에 이것을 작성하는 깔끔한 방법을 찾고 있습니다.

참고로 나는 상점을보다 일반적인 것으로 만들고 있습니다.

앨범 = 항목

장르 = 카테고리

아티스트 = 브랜드

MVC에서 생성 된 인덱스를 검색하는 방법은 다음과 같습니다.

var items = db.Items.Include(i => i.Category).Include(i => i.Brand);

삭제 항목을 검색하는 방법은 다음과 같습니다.

Item item = db.Items.Find(id);

첫 번째 항목은 모든 항목을 다시 가져 와서 항목 모델 내부의 카테고리 및 브랜드 모델을 채 웁니다. 두 번째는 카테고리와 브랜드를 채우지 않습니다.

두 번째 것을 작성하여 내부에서 (바람직하게는 1 줄) 내용을 채우고 채우는 방법은 무엇입니까?

Item item = db.Items.Find(id).Include(i => i.Category).Include(i => i.Brand);

누군가가 일반적으로 in.net-core 로이 작업을 수행 해야하는 경우 내 대답을 참조하십시오.
johnny 5

답변:


162

Include()먼저 사용 하고 결과 쿼리에서 단일 객체를 검색 해야 합니다.

Item item = db.Items
              .Include(i => i.Category)
              .Include(i => i.Brand)
              .SingleOrDefault(x => x.ItemId == id);

24
후자를 사용하는 것이 좋습니다 (SingleOrDefault) ToList는 먼저 모든 항목 을 검색 한 다음 하나를 선택합니다
Sander Rijken

5
복합 기본 키가 있고 관련 찾기 과부하를 사용하는 경우이 오류가 발생합니다.
jhappoldt

78
이것은 작동하지만 "찾기"와 "SingleOrDefault"를 사용하는 것에는 차이가 있습니다. "Find"메서드는 로컬 추적 저장소 (있는 경우)에서 개체를 반환하여 데이터베이스로의 왕복을 피합니다. "SingleOrDefault"를 사용하면 쿼리가 데이터베이스에 강제로 저장됩니다.
Iravanchi

3
@이라 반치가 맞습니다. 이것은 사용자에게 효과가 있었지만 작동과 부작용은 내가 아는 한 찾기와 동일하지 않습니다.
mwilson

3
찾기를 사용하지 않으므로 실제로 ops 질문에 대답하지 않습니다. 찾기
Paul Swetz

73

Dennis의 답변은 Include및을 사용 하고 SingleOrDefault있습니다. 후자는 데이터베이스에 라운드 트립합니다.

대안 은 관련 엔티티의 명시 적로드를 위해 Find와 함께 를 사용 Load하는 것입니다.

MSDN 예 아래 :

using (var context = new BloggingContext()) 
{ 
  var post = context.Posts.Find(2); 

  // Load the blog related to a given post 
  context.Entry(post).Reference(p => p.Blog).Load(); 

  // Load the blog related to a given post using a string  
  context.Entry(post).Reference("Blog").Load(); 

  var blog = context.Blogs.Find(1); 

  // Load the posts related to a given blog 
  context.Entry(blog).Collection(p => p.Posts).Load(); 

  // Load the posts related to a given blog  
  // using a string to specify the relationship 
  context.Entry(blog).Collection("Posts").Load(); 
}

물론 Find해당 엔티티가 컨텍스트에 의해 이미로드 된 경우 상점에 요청하지 않고 즉시 리턴합니다.


30
이 방법은 Find엔터티가 존재하는 경우 엔터티 자체에 대한 DB 왕복이 없습니다. 그러나, 당신은 당신이하고있는 각 관계에 대해 왕복 여행을 Load하는 반면, SingleOrDefault와의 조합은 Include모든 것을 한 번에로드합니다.
Iravanchi

SQL 프로파일 러에서 2를 비교할 때 Find / Load가 내 경우에 더 좋았습니다 (1 : 1 관계). @ Iravanchi : 내가 1 : m 관계를 가졌다면 그것이 상점을 m 번이라고 불렀 을까요? ... 그렇게 말이되지 않기 때문입니다.
Learner

3
1 : m 관계가 아니라 여러 관계. Load함수 를 호출 할 때마다 호출이 리턴 될 때 관계가 채워 져야합니다. 따라서 Load여러 관계에 대해 여러 번 전화 하면 매번 왕복이 발생합니다. 단일 관계인 경우에도 Find메소드가 메모리에서 엔티티를 찾지 못하면 두 번의 왕복 Find을 수행 Load합니다. 그러나 Include. SingleOrDefault접근 방식은 내가 아는 한 한 번에 엔티티와 관계를 가져옵니다 (그러나 확실하지 않음)
Iravanchi

1
컬렉션과 참조를 다르게 취급하지 않고 Include 디자인을 따를 수 있다면 좋을 것입니다. 따라서 Expression <Func <T, object >>의 선택적 컬렉션 (예 : _repo.GetById (id, x => x.MyCollection))을 사용하는 GetById () 파사드를 만드는 것이 더 어렵습니다.
Derek Greer

4
귀하의 게시물에 대한 언급은 다음과 같습니다. msdn.microsoft.com/en-us/data/jj574232.aspx#explicit
Hossein

1

IQueryable을 DbSet으로 캐스트해야합니다.

var dbSet = (DbSet<Item>) db.Set<Item>().Include("");

return dbSet.Find(id);


dbSet에 .Find 또는 .FindAsync가 없습니다. 이것이 EF Core입니까?
Thierry

6 EF 코어도 EF가
라파엘 R. 수자

희망이 있고 "InvalidCastException"
ZX9

0

나를 위해 일하지 않았다. 그러나 나는 이런 식으로 해결했습니다.

var item = db.Items
             .Include(i => i.Category)
             .Include(i => i.Brand)
             .Where(x => x.ItemId == id)
             .First();

그게 괜찮은 해결책인지 모르겠다. 하지만 데니스가 준 다른 하나는 .SingleOrDefault(x => x.ItemId = id);


4
Dennis의 솔루션도 작동해야합니다. 아마도 당신은 double 대신 SingleOrDefault(x => x.ItemId = id)잘못된 단일 때문에이 오류가 있습니까? ===
Slauma

6
예, = = ==를 사용한 것처럼 보입니다. 문법 실수;)
Ralph N

나는 ==와 =를 모두 시도했지만 여전히 .SingleOrDefault (x => x.ItemId = id)에서 오류가 발생했습니다. = / 내 코드에서 잘못된 것이 틀림 없다. 그러나 내가 한 방식은 나쁜 방법입니까? 어쩌면 나는 Dennis가 그의 코드에서 singel =을 가지고 있다는 것을 이해하지 못할 수도 있습니다.
요한

0

찾기로 필터링하는 쉬운 방법은 없습니다. 그러나 기능을 복제 할 수있는 가까운 방법을 찾았지만 솔루션에 대한 몇 가지 사항에 유의하십시오.

이 솔루션을 사용하면 .net-core의 기본 키를 몰라도 일반적으로 필터링 할 수 있습니다

  1. 찾기는 엔터티가 데이터베이스를 쿼리하기 전에 추적에있는 경우 엔터티를 가져 오기 때문에 근본적으로 다릅니다.

  2. 또한 사용자가 기본 키를 몰라도 개체별로 필터링 할 수 있습니다.

  3. 이 솔루션은 EntityFramework Core를위한 것입니다.

  4. 컨텍스트에 액세스해야합니다

기본 키로 필터링하는 데 도움이되는 몇 가지 확장 방법이 있습니다.

    public static IReadOnlyList<IProperty> GetPrimaryKeyProperties<T>(this DbContext dbContext)
    {
        return dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
    }

    //TODO Precompile expression so this doesn't happen everytime
    public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
    {
        var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var body = keyProperties
            // e => e.PK[i] == id[i]
            .Select((p, i) => Expression.Equal(
                Expression.Property(parameter, p.Name),
                Expression.Convert(
                    Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
                    p.ClrType)))
            .Aggregate(Expression.AndAlso);
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }

    public static Expression<Func<T, object[]>> GetPrimaryKeyExpression<T>(this DbContext context)
    {
        var keyProperties = context.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var keyPropertyAccessExpression = keyProperties.Select((p, i) => Expression.Convert(Expression.Property(parameter, p.Name), typeof(object))).ToArray();
        var selectPrimaryKeyExpressionBody = Expression.NewArrayInit(typeof(object), keyPropertyAccessExpression);

        return Expression.Lambda<Func<T, object[]>>(selectPrimaryKeyExpressionBody, parameter);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this DbSet<TEntity> dbSet, DbContext context, object[] id)
        where TEntity : class
    {
        return FilterByPrimaryKey(dbSet.AsQueryable(), context, id);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this IQueryable<TEntity> queryable, DbContext context, object[] id)
        where TEntity : class
    {
        return queryable.Where(context.FilterByPrimaryKeyPredicate<TEntity>(id));
    }

이러한 확장 방법이 있으면 다음과 같이 필터링 할 수 있습니다.

query.FilterByPrimaryKey(this._context, id);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.