[c#] DbSet.Attach (entity) 대 DbContext.Entry (entity) .State = EntityState.Modified

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

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

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

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



답변

를 수행 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.

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

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


답변

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


답변

추가로 (표시된 답변에) 와 (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


답변