Entity Framework가 자식 개체를 저장 / 삽입하지 못하도록하려면 어떻게해야합니까?


102

엔터티 프레임 워크로 엔터티를 저장할 때 자연스럽게 지정된 엔터티 만 저장하려고 할 것이라고 가정했습니다. 그러나 해당 엔터티의 하위 엔터티도 저장하려고합니다. 이로 인해 모든 종류의 무결성 문제가 발생합니다. 저장하려는 엔터티 만 저장하고 모든 자식 개체를 무시하도록 EF를 강제하는 방법은 무엇입니까?

속성을 수동으로 null로 설정하면 "작업 실패 : 하나 이상의 외래 키 속성이 null이 아니기 때문에 관계를 변경할 수 없습니다."라는 오류가 발생합니다. 자식 개체를 특별히 null로 설정하여 EF가 그대로두기 때문에 이것은 매우 비생산적입니다.

자식 개체를 저장 / 삽입하지 않는 이유는 무엇입니까?

이것은 의견에서 앞뒤로 논의되고 있으므로 왜 내 자식 개체를 그대로두기를 원하는지에 대한 정당성을 제공하겠습니다.

내가 빌드중인 애플리케이션에서 EF 개체 모델은 데이터베이스에서로드되지 않고 플랫 파일을 구문 분석하는 동안 채우는 데이터 개체로 사용됩니다. 자식 개체의 경우 이러한 개체 중 다수는 부모 테이블의 다양한 속성을 정의하는 조회 테이블을 참조합니다. 예를 들어 기본 엔티티의 지리적 위치입니다.

이러한 개체를 직접 채웠으므로 EF는 이러한 개체를 새 개체로 가정하고 부모 개체와 함께 삽입해야합니다. 그러나 이러한 정의는 이미 존재하며 데이터베이스에 중복을 생성하고 싶지 않습니다. EF 개체를 사용하여 조회를 수행하고 주 테이블 엔터티에 외래 키를 채 웁니다.

실제 데이터 인 자식 개체를 사용하더라도 부모를 먼저 저장하고 기본 키를 가져와야합니다. 그렇지 않으면 EF가 엉망인 것 같습니다. 이것이 약간의 설명을 제공하기를 바랍니다.


내가 아는 한 자식 개체를 null로 설정해야합니다.
요한

안녕 요한. 작동하지 않습니다. 컬렉션을 null로 설정하면 오류가 발생합니다. 내가 어떻게하는지에 따라 키가 null이거나 I 컬렉션이 수정되었다고 불평합니다. 당연히 그런 것들은 사실이지만, 나는 의도적으로 그렇게했기 때문에 만지지 말아야 할 물건은 그대로 두었습니다.
마크 MICALLEF

그것은 완전히 도움이되지 않습니다.
마크 MICALLEF

@Euphoric 자식 개체를 변경하지 않는 경우에도 EF는 기본적으로 개체를 삽입하고 무시하거나 업데이트하지 않습니다.
요한

나를 정말로 짜증나게하는 것은 내가 그 객체들을 실제로 무효화하기 위해 나가면, 내가 그것들을 내버려두기를 원한다는 것을 깨닫기보다는 불평한다는 것입니다. 이러한 자식 개체는 모두 선택 사항이므로 (데이터베이스에서 nullable) EF가 해당 개체가 있다는 것을 잊도록 강제하는 방법이 있습니까? 즉, 컨텍스트 또는 캐시를 어떻게 든 제거합니까?
마크 MICALLEF

답변:


56

내가 아는 한 두 가지 옵션이 있습니다.

옵션 1)

모든 자식 개체가 Null이면 EF가 아무것도 추가하지 않도록합니다. 또한 데이터베이스에서 아무것도 삭제하지 않습니다.

옵션 2)

다음 코드를 사용하여 자식 개체를 컨텍스트에서 분리 된 것으로 설정합니다.

 context.Entry(yourObject).State = EntityState.Detached

List/는 분리 할 수 ​​없습니다 Collection. 목록을 반복하고 목록의 각 항목을 이렇게 분리해야합니다.

foreach (var item in properties)
{
     db.Entry(item).State = EntityState.Detached;
}

안녕하세요 Johan, 컬렉션 중 하나를 분리하려고 시도했는데 다음 오류가 발생했습니다. 엔티티 유형 HashSet`1은 현재 컨텍스트에 대한 모델의 일부가 아닙니다.
마크 MICALLEF

@MarkyMark 컬렉션을 분리하지 마십시오. 컬렉션을 반복하고 객체에 대한 객체를 분리해야합니다 (지금 내 대답을 업데이트하겠습니다).
Johan

11
불행히도 모든 것을 분리하는 루프 목록이 있어도 EF는 여전히 일부 관련 테이블에 삽입하려고 시도하는 것 같습니다. 이 시점에서 저는 EF를 추출하고 적어도 현명하게 동작하는 SQL로 다시 전환 할 준비가되었습니다. 얼마나 고통 스러운가.
마크 MICALLEF

2
context.Entry (yourObject) .State를 추가하기 전에 사용할 수 있습니까?
Thomas Klammer

1
@ mirind4 상태를 사용하지 않았습니다. 객체를 삽입하면 모든 자식이 null인지 확인합니다. 업데이트시 나는 차일없이 먼저 객체를 얻습니다.
Thomas Klammer

41

간단히 말해서 외래 키를 사용하면 하루를 절약 할 수 있습니다.

학교 법인과 도시 법인 이 있다고 가정 하고 이것은 도시에 많은 학교가 있고 학교가 도시에 속하는 다 대일 관계입니다. 그리고 도시가 이미 조회 테이블에 존재하므로 새 학교를 삽입 할 때 도시가 다시 삽입되는 것을 원하지 않습니다.

처음에는 다음과 같이 엔티티를 정의 할 수 있습니다.

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    [Required]
    public City City { get; set; }
}

그리고 다음 과 같이 School 삽입을 수행 할 수 있습니다 (이미 newItem에 City 속성이 할당되어 있다고 가정 ).

public School Insert(School newItem)
{
    using (var context = new DatabaseContext())
    {
        context.Set<School>().Add(newItem);
        // use the following statement so that City won't be inserted
        context.Entry(newItem.City).State = EntityState.Unchanged;
        context.SaveChanges();
        return newItem;
    }
}

위의 접근 방식은이 경우 완벽하게 작동 할 수 있지만, 더 명확하고 유연한 외래 키 접근 방식을 선호합니다 . 아래 업데이트 된 솔루션을 참조하십시오.

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    [ForeignKey("City_Id")]
    public City City { get; set; }

    [Required]
    public int City_Id { get; set; }
}

이러한 방식으로 학교 에 외래 키 City_Id 가 있고 City 엔티티를 참조하도록 명시 적으로 정의합니다 . 따라서 School 삽입과 관련하여 다음을 수행 할 수 있습니다.

    public School Insert(School newItem, int cityId)
    {
        if(cityId <= 0)
        {
            throw new Exception("City ID no provided");
        }

        newItem.City = null;
        newItem.City_Id = cityId;

        using (var context = new DatabaseContext())
        {
            context.Set<School>().Add(newItem);
            context.SaveChanges();
            return newItem;
        }
    }

이 경우 새 레코드 의 City_Id 를 명시 적으로 지정하고 그래프 에서 City 를 제거하여 EF가 School 과 함께 컨텍스트에 추가하지 않도록합니다 .

첫인상에서는 외래 키 접근 방식이 더 복잡해 보이지만 다 대다 관계를 삽입 할 때이 사고 방식을 사용하면 많은 시간을 절약 할 수 있습니다 (학교와 학생 관계, 학생 City 속성이 있음) 등등.

이것이 당신에게 도움이되기를 바랍니다.


좋은 대답, 많이 도왔습니다! 그러나 [Range(1, int.MaxValue)]속성을 사용하여 City_Id에 대해 최소값 1을 지정하는 것이 더 좋지 않을까요?
Dan Rayson

꿈처럼 !! 정말 감사합니다!
CJH

이렇게하면 호출자의 School 개체에서 값이 제거됩니다. SaveChanges가 null 탐색 속성 값을 다시로드합니까? 그렇지 않으면 호출자가 해당 정보가 필요한 경우 Insert () 메서드를 호출 한 후 City 개체를 다시로드해야합니다. 이것은 내가 자주 사용하는 패턴이지만 누군가가 좋은 패턴을 가지고 있다면 여전히 더 나은 패턴에 열려 있습니다.
Lopsided

1
이것은 나에게 정말 도움이되었습니다. EntityState.Unchaged는 단일 트랜잭션으로 저장 한 큰 개체 그래프에서 조회 테이블 외래 키를 나타내는 개체를 할당하는 데 정확히 필요한 것입니다. EF Core는 덜 직관적 인 오류 메시지 IMO를 발생시킵니다. 성능상의 이유로 거의 변경되지 않는 룩업 테이블을 캐시했습니다. 룩업 테이블 객체의 PK가 데이터베이스에있는 것과 동일하기 때문에 변경되지 않은 것을 알고 기존 항목에 FK를 할당하기 만하면된다고 가정합니다. 대신 조회 테이블 개체를 새 개체로 삽입하려고합니다.
tnk479

nulling 또는 상태를 변경되지 않음으로 설정하는 조합이 나를 위해 작동하지 않습니다. EF는 부모의 스칼라 필드를 업데이트하려는 경우 새 자식 레코드를 삽입해야한다고 주장합니다.
BobRz

21

그냥 가게 a를 변경하려는 경우 부모 객체와 그의를 보관하지 마십시오 변경 자식 개체, 왜 그냥 다음을 수행하지 :

using (var ctx = new MyContext())
{
    ctx.Parents.Attach(parent);
    ctx.Entry(parent).State = EntityState.Added;  // or EntityState.Modified
    ctx.SaveChanges();
}

첫 번째 줄은 상위 개체와 종속 하위 개체의 전체 그래프를 Unchanged상태 의 컨텍스트에 연결 합니다.

두 번째 줄은 부모 개체의 상태 만 변경하고 자식은 그대로 둡니다 Unchanged.

새로 생성 된 컨텍스트를 사용하므로 데이터베이스에 다른 변경 사항이 저장되지 않습니다.


2
이 대답은 누군가가 나중에 와서 자식 개체를 추가하더라도 기존 코드를 손상시키지 않는다는 장점이 있습니다. 이것은 다른 것들이 명시 적으로 자식 개체를 제외하도록 요구하는 "옵트 인"솔루션입니다.
Jim

더 이상 코어에서 작동하지 않습니다. "첨부 : 도달 가능한 엔티티에 상점 생성 키가 있고 키 값이 할당되지 않은 경우를 제외하고 모든 도달 가능한 엔티티를 첨부합니다. 이들은 추가 된 것으로 표시됩니다." 아이들도 새로운 경우 추가됩니다.
mmix

14

제안 된 솔루션 중 하나는 동일한 데이터베이스 컨텍스트에서 탐색 속성을 할당하는 것입니다. 이 솔루션에서는 데이터베이스 컨텍스트 외부에서 할당 된 탐색 속성이 대체됩니다. 그림은 다음 예를 참조하십시오.

class Company{
    public int Id{get;set;}
    public Virtual Department department{get; set;}
}
class Department{
    public int Id{get; set;}
    public String Name{get; set;}
}

데이터베이스에 저장 :

 Company company = new Company();
 company.department = new Department(){Id = 45}; 
 //an Department object with Id = 45 exists in database.    

 using(CompanyContext db = new CompanyContext()){
      Department department = db.Departments.Find(company.department.Id);
      company.department = department;
      db.Companies.Add(company);
      db.SaveChanges();
  }

Microsoft는 이것을 기능으로 등록하지만 나는 이것이 성가시다. 회사 개체와 연결된 부서 개체에 이미 데이터베이스에있는 ID가있는 경우 EF가 회사 개체를 데이터베이스 개체와 연결하지 않는 이유는 무엇입니까? 우리가 연합을 스스로 돌봐야하는 이유는 무엇입니까? 새 개체를 추가하는 동안 탐색 속성을 관리하는 것은 데이터베이스 작업을 SQL에서 C #으로 이동하는 것과 같이 개발자에게 번거 롭습니다.


나는 EF가 ID를 제공 할 때 새로운 기록을 만들려고 시도하는 것이 어리석은 일이라는 것에 100 % 동의합니다. 선택 목록 옵션을 실제 개체로 채우는 방법이 있다면 우리는 사업을 할 것입니다.
T3.0

11

먼저 EF에서 엔터티를 업데이트하는 두 가지 방법이 있음을 알아야합니다.

  • 부착 된 개체

위에서 설명한 방법 중 하나를 사용하여 개체 컨텍스트에 연결된 개체의 관계를 변경할 때 Entity Framework는 외래 키, 참조 및 컬렉션을 동기화 상태로 유지해야합니다.

  • 연결이 끊긴 개체

연결이 끊어진 개체로 작업하는 경우 동기화를 수동으로 관리해야합니다.

내가 빌드중인 애플리케이션에서 EF 개체 모델은 데이터베이스에서로드되지 않고 플랫 파일을 구문 분석하는 동안 채우는 데이터 개체로 사용됩니다.

즉, 연결이 끊어진 개체로 작업하고 있지만 독립 연결을 사용하는지 외래 키 연결을 사용하는지 명확하지 않습니다 .

  • 더하다

    기존 자식 개체 (데이터베이스에있는 개체)와 함께 새 엔터티를 추가 할 때 자식 개체가 EF에서 추적되지 않으면 자식 개체가 다시 삽입됩니다. 먼저 자식 개체를 수동으로 연결하지 않는 한.

      db.Entity(entity.ChildObject).State = EntityState.Modified;
      db.Entity(entity).State = EntityState.Added;
  • 최신 정보

    엔터티를 수정 된 것으로 표시하면 모든 스칼라 속성이 업데이트되고 탐색 속성은 무시됩니다.

      db.Entity(entity).State = EntityState.Modified;

그래프 차이

연결이 끊어진 객체로 작업 할 때 코드를 단순화하려면 diff 라이브러리그래프로 표시해 볼 수 있습니다 .

다음은 소개, Introducing GraphDiff for Entity Framework Code First-Allowing automatic updates of a graph of detached entities .

샘플 코드

  • 엔티티가 없으면 삽입하고 그렇지 않으면 업데이트합니다.

      db.UpdateGraph(entity);
  • 그것은, 그렇지 않으면 업데이트 존재하지 않는 개체 삽입 존재, 그렇지 않으면 갱신하지 않는 경우 자식 개체 삽입.

      db.UpdateGraph(entity, map => map.OwnedEntity(x => x.ChildObject));

3

이를 수행하는 가장 좋은 방법은 데이터 컨텍스트에서 SaveChanges 함수를 재정의하는 것입니다.

    public override int SaveChanges()
    {
        var added = this.ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Added);

        // Do your thing, like changing the state to detached
        return base.SaveChanges();
    }

2

프로필을 저장하려고 할 때 동일한 문제가 있습니다. 이미 테이블 인사말과 프로필을 새로 만들었습니다. 프로필을 삽입하면 인사말에도 삽입됩니다. 그래서 savechanges () 전에 이렇게 시도했습니다.

db.Entry (Profile.Salutation) .State = EntityState.Unchanged;


1

이것은 나를 위해 일했습니다.

// temporarily 'detach' the child entity/collection to have EF not attempting to handle them
var temp = entity.ChildCollection;
entity.ChildCollection = new HashSet<collectionType>();

.... do other stuff

context.SaveChanges();

entity.ChildCollection = temp;

0

우리가 한 일은 dbset에 부모를 추가하기 전에 부모로부터 자식 컬렉션의 연결을 끊고 나중에 작업 할 수 있도록 기존 컬렉션을 다른 변수로 푸시 한 다음 현재 자식 컬렉션을 새로운 빈 컬렉션으로 교체하는 것입니다. 자식 컬렉션을 null / 아무것도 설정하지 못하는 것 같았습니다. 그런 다음 부모를 dbset에 추가하십시오. 이렇게하면 원하는 때까지 자식이 추가되지 않습니다.


0

나는 그것이 오래된 게시물이라는 것을 알고 있지만 코드 우선 접근 방식을 사용하는 경우 매핑 파일에서 다음 코드를 사용하여 원하는 결과를 얻을 수 있습니다.

Ignore(parentObject => parentObject.ChildObjectOrCollection);

이것은 기본적으로 EF에게 모델에서 "ChildObjectOrCollection"속성을 제외하여 데이터베이스에 매핑되지 않도록합니다.


"무시"는 어떤 컨텍스트에서 사용됩니까? 추정 된 맥락에 존재하지 않는 것 같습니다.
T3.0

0

Entity Framework Core 3.1.0을 사용하여 비슷한 문제가 발생했습니다. 내 저장소 논리는 매우 일반적입니다.

이것은 나를 위해 일했습니다.

builder.Entity<ChildEntity>().HasOne(c => c.ParentEntity).WithMany(l =>
       l.ChildEntity).HasForeignKey("ParentEntityId");

"ParentEntityId"는 하위 엔티티의 외래 키 열 이름입니다. 이 메서드에 위에서 언급 한 코드 줄을 추가했습니다.

protected override void OnModelCreating(ModelBuilder builder)...
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.