분리되었지만 동일한 객체를 사용하여 연결된 객체를 업데이트 할 수 있습니까?


10

외부 API에서 영화 데이터를 검색합니다. 첫 번째 단계에서는 각 영화를 긁어 내 데이터베이스에 삽입합니다. 두 번째 단계에서는 API의 "변경 사항"API를 사용하여 정기적으로 데이터베이스를 업데이트하여 정보를 변경 한 영화를 확인할 수 있습니다.

내 ORM 레이어는 엔터티 프레임 워크입니다. Movie 클래스는 다음과 같습니다.

class Movie
{
    public virtual ICollection<Language> SpokenLanguages { get; set; }
    public virtual ICollection<Genre> Genres { get; set; }
    public virtual ICollection<Keyword> Keywords { get; set; }
}

문제는 영화를 업데이트해야 할 때 발생합니다. 데이터베이스는 추적중인 객체와 업데이트 API 호출에서 수신 한 새로운 객체를 다른 객체로 간주하여 무시 .Equals()합니다.

업데이트 된 동영상으로 데이터베이스를 업데이트하려고하면 기존 동영상을 업데이트하는 대신 데이터베이스를 삽입하기 때문에 문제가 발생합니다.

나는 언어와 함께이 문제를 가지고 있었고 내 해결책은 첨부 된 언어 객체를 검색하고 컨텍스트에서 분리하고 PK를 업데이트 된 객체로 이동하여 컨텍스트에 첨부하는 것이 었습니다. 때 SaveChanges()지금 실행, 그것은 본질적으로 대체됩니다.

Movie객체에 대한이 접근 방식을 계속 하면 영화, 언어, 장르 및 키워드를 분리하고 데이터베이스에서 각각을 조회하고 ID를 전송하고 새로운 물건.

더 우아하게 처리 할 수있는 방법이 있습니까? 이상적으로는 업데이트 된 영화를 컨텍스트에 전달하고 Equals()메소드를 기반으로 업데이트 할 올바른 영화를 선택하고 모든 필드와 각 복잡한 객체 를 업데이트하도록하고 싶습니다 . 자체의 Equals()메소드를 기반으로 기존 레코드를 다시 사용 하고 다음을 삽입하십시오. 아직 존재하지 않습니다.

.Update()연결된 모든 객체를 검색하는 조합으로 사용할 수있는 각 복잡한 객체에 메소드를 제공하여 분리 / 연결을 건너 뛸 수는 있지만 기존의 모든 단일 객체를 검색하여 업데이트해야합니다.


엔티티를 교체 / 분리 / 일치 / 부착하지 않고 API 데이터에서 추적 된 엔티티를 업데이트하고 변경 사항을 저장할 수없는 이유는 무엇입니까?
Si-N

@ Si-N : 그렇다면 정확히 어떻게 확장 할 수 있습니까?
Jeroen Vannevel

자 이제 마지막 단락을 추가하는 것이 더 합리적입니다. 엔티티를 업데이트하기 전에 검색하지 않으려 고합니까? 영화 수업에서 PK가 될 수있는 것은 없습니까? 외부 API의 영화를 엔티티와 어떻게 일치 시킵니까? 어쨌든 한 번의 db 호출로 업데이트 해야하는 모든 엔티티를 검색 할 수 있습니다. 업데이트 할 영화 수에 대해 이야기하지 않는 한 큰 성능 저하를 유발하지 않는 간단한 솔루션이 아닐까요?
Si-N

내 영화 클래스에는 PK id가 있으며 외부 API의 영화는 필드를 사용하여 로컬 영화와 일치합니다 tmdbid. 영화, 장르, 언어, 키워드 등에 관한 것이므로 한 번의 호출로 업데이트해야하는 모든 항목을 검색 할 수 없습니다. 각 항목에는 PK가 있으며 데이터베이스에 이미있을 수 있습니다.
Jeroen Vannevel

답변:


8

원하는 것을 찾지 못했지만 기존의 select-detach-update-attach 시퀀스보다 개선되었습니다.

확장 방법을 AddOrUpdate(this DbSet)사용하면 내가 원하는 것을 정확하게 수행 할 수 있습니다.없는 경우 삽입하고 기존 값을 찾으면 업데이트하십시오. 실제로 seed()마이그레이션과 함께 메소드 에서만 사용되는 것을 보았으므로 이것을 더 빨리 사용하지 못했습니다 . 사용하지 말아야 할 이유가 있으면 알려주십시오.

유용한 점 : 평등을 결정하는 방법을 구체적으로 선택할 수있는 과부하가 있습니다. 여기서는 내 것을 사용할 수 TMDbId있었지만 대신 내 자신의 ID를 모두 무시하고 대신 TMDbId에서 PK를 사용하기로 선택했습니다 DatabaseGeneratedOption.None. 적절한 경우 각 하위 컬렉션에서도이 방법을 사용합니다.

소스 의 흥미로운 부분 :

internalSet.InternalContext.Owner.Entry(existing).CurrentValues.SetValues(entity);

이것이 데이터가 실제로 업데이트되는 방식입니다.

남은 것은 AddOrUpdate이것에 의해 영향을 받고 싶은 각 객체를 호출 하는 것입니다.

public void InsertOrUpdate(Movie movie)
{
    _context.Movies.AddOrUpdate(movie);
    _context.Languages.AddOrUpdate(movie.SpokenLanguages.ToArray());
    // Other objects/collections
    _context.SaveChanges();
}

업데이트해야 할 객체의 각 부분을 수동으로 지정해야하기 때문에 원하는만큼 깨끗하지 않지만 얻을 수있는 한 가깝습니다.

관련 읽기 : /programming/15336248/entity-framework-5-updating-a-record


최신 정보:

내 테스트가 충분히 엄격하지 않은 것으로 나타났습니다. 이 기술을 사용한 후에 새 언어가 추가되었지만 동영상에 연결되지 않은 것을 알았습니다. 다 대다 테이블에서. 이것은 알려진 것이나 우선 순위가 낮은 것으로 보이며 내가 아는 한 수정되지 않았습니다.

결국 나는 Update(T)각 유형에 대한 메소드 가 있고이 일련의 이벤트를 따르는 접근 방식으로 가기로 결정했습니다 .

  • 새 객체에서 컬렉션 반복
  • 각 컬렉션의 각 항목에 대해 데이터베이스에서 찾아보십시오.
  • 존재하는 경우이 Update()방법을 사용 하여 새 값으로 업데이트하십시오.
  • 존재하지 않는 경우 적절한 DbSet에 추가하십시오.
  • 첨부 된 오브젝트를 리턴하고 루트 오브젝트의 콜렉션을 첨부 된 오브젝트의 콜렉션으로 바꾸십시오.
  • 루트 객체 찾기 및 업데이트

수동 작업이 많고 추악하기 때문에 몇 가지 리팩토링을 더 진행할 것이지만 이제는 더 엄격한 시나리오에서 작동해야한다고 테스트했습니다.


더 정리 한 후에는이 방법을 사용합니다 :

private IEnumerable<T> InsertOrUpdate<T, TKey>(IEnumerable<T> entities, Func<T, TKey> idExpression) where T : class
{
    foreach (var entity in entities)
    {
        var existingEntity = _context.Set<T>().Find(idExpression(entity));
        if (existingEntity != null)
        {
            _context.Entry(existingEntity).CurrentValues.SetValues(entity);
            yield return existingEntity;
        }
        else
        {
            _context.Set<T>().Add(entity);
            yield return entity;
        }
    }
    _context.SaveChanges();
}

이를 통해 다음과 같이 호출하고 기본 컬렉션을 삽입 / 업데이트 할 수 있습니다.

movie.Genres = new List<Genre>(InsertOrUpdate(movie.Genres, x => x.TmdbId));

검색된 값을 원래 루트 객체에 다시 할당하는 방법에 주목하십시오. 이제는 연결된 각 객체에 연결됩니다. 루트 객체 (동영상) 업데이트는 동일한 방식으로 수행됩니다.

var localMovie = _context.Movies.SingleOrDefault(x => x.TmdbId == movie.TmdbId);
if (localMovie == null)
{
    _context.Movies.Add(movie);
} 
else
{
    _context.Entry(localMovie).CurrentValues.SetValues(movie);
}

1-M 관계에서 삭제를 어떻게 처리합니까? 예를 들어, 1-Movie는 여러 언어를 가질 수 있습니다. 언어 중 하나가 제거되면 코드에서 제거됩니까? 그것은 당신의 솔루션만을 삽입하고 또는 업데이트를 나타납니다 (삭제하지 않습니다?)
joedotnot

0

당신이 다른 분야로 다루고 있기 때문에 id그리고 tmbid, 내가 API를 업데이트하는 키워드 등 장르, 언어, 같은 정보를 모두 단일 및 별도의 인덱스를 만들 ... 그리고 인덱스를 호출하고 수집보다는 정보를 확인하는 것이 좋습니다 Movie 클래스의 특정 객체에 대한 전체 정보


1
나는 여기서 생각의 기차를 따르지 않습니다. 확장 할 수 있습니까? 외부 API는 전적으로 통제 할 수 없습니다.
Jeroen Vannevel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.