DbSet.Attach (entity) 대 DbContext.Entry (entity) .State = EntityState.Modified


115

분리 된 시나리오에있을 때 클라이언트에서 dto를 가져 와서 엔티티에 매핑하여 저장하면 다음을 수행합니다.

context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();

다음은 무엇입니까 DbSet.Attach(entity)

또는 EntityState.Modified가 이미 엔터티를 첨부 할 때 .Attach 메서드를 사용해야하는 이유는 무엇입니까?


더 나은 버전 정보를 추가하는 것이 좋습니다. 이것이 새로운 질문을 할 가치가 있는지 확실하지 않습니다.
Henk Holterman

답변:


278

를 수행 context.Entry(entity).State = EntityState.Modified;하면 엔티티를에 첨부 할뿐만 아니라 DbContext전체 엔티티를 더티로 표시하는 것입니다. 즉 context.SaveChanges(), 을 수행 하면 EF가 엔터티의 모든 필드를 업데이트하는 업데이트 문을 생성합니다 .

이것은 항상 바람직한 것은 아닙니다.

반면에 DbSet.Attach(entity)엔티티를 더티로 표시 하지 않고 컨텍스트에 첨부합니다 . 하는 것과 동일합니다.context.Entry(entity).State = EntityState.Unchanged;

이러한 방식으로 연결할 때 엔터티의 속성 업데이트를 진행하지 않는 한 다음에을 호출 할 때 context.SaveChanges()EF는이 엔터티에 대한 데이터베이스 업데이트를 생성하지 않습니다.

항목을 업데이트 할 계획이더라도 항목에 많은 속성 (db 열)이 있지만 몇 개만 업데이트하려는 경우을 수행 한 DbSet.Attach(entity)다음 몇 개의 속성 만 업데이트하는 것이 유리할 수 있습니다. 업데이트가 필요합니다. 이렇게하면 EF에서보다 효율적인 업데이트 문이 생성됩니다. EF는 수정 한 속성 만 업데이트합니다 (반대로 context.Entry(entity).State = EntityState.Modified;모든 속성 / 열이 업데이트 됨).

관련 문서 : 추가 / 첨부 및 엔티티 상태 .

코드 예

다음 엔티티가 있다고 가정 해 보겠습니다.

public class Person
{
    public int Id { get; set; } // primary key
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

코드가 다음과 같은 경우 :

context.Entry(personEntity).State = EntityState.Modified;
context.SaveChanges();

생성 된 SQL은 다음과 같습니다.

UPDATE person
SET FirstName = 'whatever first name is',
    LastName = 'whatever last name is'
WHERE Id = 123; -- whatever Id is.

위의 update 문이 실제로 값을 변경했는지 여부에 관계없이 모든 열을 업데이트하는 방법에 유의하십시오.

반대로, 코드가 다음과 같이 "일반"첨부를 사용하는 경우 :

context.People.Attach(personEntity); // State = Unchanged
personEntity.FirstName = "John"; // State = Modified, and only the FirstName property is dirty.
context.SaveChanges();

그러면 생성 된 업데이트 문이 다릅니다.

UPDATE person
SET FirstName = 'John'
WHERE Id = 123; -- whatever Id is.

당신이 볼 수 있듯이, 업데이트 문은 단지 당신이 컨텍스트에 엔티티를 부착 한 후 실제로 변경된 값을 업데이트합니다. 테이블의 구조에 따라 성능에 긍정적 인 영향을 미칠 수 있습니다.

이제 어떤 옵션이 더 나은지는 전적으로 수행하려는 작업에 달려 있습니다.


1
EF는 이러한 방식으로 WHERE 절을 생성하지 않습니다. new (예 : new Entity ())로 만든 엔티티를 첨부하고 수정하도록 설정 한 경우 낙관적 잠금으로 인해 모든 원래 필드를 설정해야합니다. UPDATE 쿼리에서 생성 된 WHERE 절은 일반적으로 모든 원본 필드 (ID뿐만 아니라)를 포함하므로 그렇게하지 않으면 EF에서 동시성 예외가 발생합니다.
bubi

3
@budi : 의견을 보내 주셔서 감사합니다. 확실히하기 위해 다시 테스트했으며 기본 엔터티의 WHERE경우 기본 키만 포함하고 동시성 검사없이 절을 사용하여 설명한대로 동작합니다 . 동시성 검사를하려면 열을 동시성 토큰 또는 rowVersion으로 명시 적으로 구성해야합니다. 이 경우 WHERE절에는 모든 필드가 아닌 기본 키와 동시성 토큰 열만 있습니다. 테스트 결과가 다르게 표시되면 이에 대해 듣고 싶습니다.
sstan

어떻게 동적으로 마녀 속성이 수정되었는지 찾을 수 있습니까?
Navid_pdp11

2
@ Navid_pdp11 DbContext.Entry(person).CurrentValuesDbContext.Entry(person).OriginalValues.
Shimmy Weitzhandler

주제에서 약간 벗어 났을 수도 있지만 리포지토리 패턴을 사용하는 경우 모든 모델에 DB에 새 레코드를 삽입하는 동안 추적되지 않은 상태에 있어야하는 엔티티가 있으므로 모든 모델에 대한 리포지토리를 만들어야하므로 가질 수 없습니다. 삽입하는 동안 컨텍스트에 엔티티를 첨부하는 일반 저장소. 이것을 어떻게 가장 잘 처리합니까?
jayasurya_j

3

DbSet.Update메서드 를 사용할 때 Entity Framework는 엔터티의 모든 속성을로 표시 EntityState.Modified하므로 추적합니다. 모든 속성이 아닌 일부 속성 만 변경하려면을 사용하십시오 DbSet.Attach. 이 메서드는 모든 EntityState.Unchanged속성을 만들므로 업데이트 할 속성을 만들어야합니다 EntityState.Modified. 따라서 앱이에 도달하면 DbContext.SaveChanges수정 된 속성 만 작동합니다.


0

추가로 (표시된 답변에) 와 (EF Core에서) 사이에 중요한 차이점 이 있습니다 .context.Entry(entity).State = EntityState.Unchangedcontext.Attach(entity)

나는 그것을 더 잘 이해하기 위해 몇 가지 테스트를 수행했기 때문에 (따라서 일반적인 참조 테스트도 포함됨) 이것이 내 테스트 시나리오입니다.

  • EF Core 3.1.3을 사용했습니다.
  • 나는 사용했다 QueryTrackingBehavior.NoTracking
  • 매핑에 속성 만 사용했습니다 (아래 참조).
  • 주문을 받고 주문을 업데이트하기 위해 다른 컨텍스트를 사용했습니다.
  • 모든 테스트에 대해 전체 db를 지 웠습니다.

다음은 모델입니다.

public class Order
{
    public int Id { get; set; }
    public string Comment { get; set; }
    public string ShippingAddress { get; set; }
    public DateTime? OrderDate { get; set; }
    public List<OrderPos> OrderPositions { get; set; }
    [ForeignKey("OrderedByUserId")]
    public User OrderedByUser { get; set; }
    public int? OrderedByUserId { get; set; }
}

public class OrderPos
{
    public int Id { get; set; }
    public string ArticleNo { get; set; }
    public int Quantity { get; set; }
    [ForeignKey("OrderId")]
    public Order Order { get; set; }
    public int? OrderId { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

다음은 데이터베이스의 (원본) 테스트 데이터입니다. 여기에 이미지 설명 입력

주문을 받으려면 :

order = db.Orders.Include(o => o.OrderPositions).Include(o => o.OrderedByUser).FirstOrDefault();

이제 테스트 :

EntityState사용한 간단한 업데이트 :

db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1

첨부로 간단한 업데이트 :

db.Attach(order);
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 1 Call:
// UPDATE [OrderPositions] SET [ArticleNo] = 'K-1234' WHERE [Id] = 1
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555 (NEW)', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 1

EntityState로 하위 ID를 변경하여 업데이트 하십시오 .

db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.Id = 3; // will be IGNORED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].Id = 3; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1

Attach를 사용하여 Child-Id를 변경하여 업데이트 :

db.Attach(order);
order.ShippingAddress = "Germany"; // would be UPDATED
order.OrderedByUser.Id = 3; // will throw EXCEPTION
order.OrderedByUser.FirstName = "William (CHANGED)"; // would be UPDATED
order.OrderPositions[0].Id = 3; // will throw EXCEPTION
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // would be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // would be INSERTED
db.SaveChanges();
// Throws Exception: The property 'Id' on entity type 'User' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.)

참고 :이 경우 Id가 변경되었거나 원래 값으로 설정되었는지에 관계없이 예외가 발생합니다. Id의 상태가 "변경됨"으로 설정되어 있고 허용되지 않는 것처럼 보입니다 (기본 키이기 때문에).

Child-Id를 새로 변경하여 업데이트합니다 (EntityState와 Attach간에 차이 없음).

db.Attach(order); // or db.Entry(order).State = EntityState.Unchanged;
order.OrderedByUser = new User();
order.OrderedByUser.Id = 3; // // Reference will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED (on User 3)
db.SaveChanges();
// Will generate SQL in 2 Calls:
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 3

참고 : 새 항목없이 EntityState로 업데이트 (위)의 차이점을 참조하십시오. 이번에는 새 사용자 인스턴스로 인해 이름이 업데이트됩니다.

EntityState로 참조 ID를 변경하여 업데이트합니다 .

db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUserId = 3; // will be UPDATED
order.OrderedByUser.Id = 2; // will be IGNORED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].Id = 3; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1

Attach를 사용 하여 참조 ID를 변경하여 업데이트 합니다 .

db.Attach(order);
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUserId = 3; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED (on FIRST User!)
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 1 Call:
// UPDATE [OrderPositions] SET [ArticleNo] = 'K-1234' WHERE [Id] = 1
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555 (NEW)', 1, 5)
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 1

참고 : 참고는 사용자 3으로 변경되지만, 또한 사용자 1이 업데이트됩니다, 나는이 때문에이 추측 order.OrderedByUser.Id(여전히 1의) 변경되지 않습니다.

결론 EntityState를 사용하면 더 많은 제어가 가능하지만 하위 속성 (두 번째 수준)을 직접 업데이트해야합니다. Attach를 사용하면 모든 것을 업데이트 할 수 있지만 (모든 수준의 속성으로 추측) 참조를 주시해야합니다. 예를 들어 사용자 (OrderedByUser)가 드롭 다운 인 경우 드롭 다운을 통해 값을 변경하면 전체 사용자 개체를 덮어 쓸 수 있습니다. 이 경우 참조 대신 원래 dropDown-Value를 덮어 씁니다.

나에게 가장 좋은 경우는 OrderedByUser와 같은 개체를 null로 설정하고 order.OrderedByUserId를 새 값으로 만 설정하는 것입니다 (EntityState 또는 Attach 여부에 관계없이).

이것이 도움이되기를 바랍니다. 많은 텍스트임을 알고 있습니다 .D

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