Entity Framework 5 레코드 업데이트


870

ASP.NET MVC3 환경에서 Entity Framework 5 내에서 레코드를 편집 / 업데이트하는 다양한 방법을 탐색했지만 지금까지 필요한 모든 상자를 선택하지는 않습니다. 이유를 설명하겠습니다.

장단점을 언급 할 세 가지 방법을 찾았습니다.

방법 1-원본 레코드로드, 각 속성 업데이트

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    original.BusinessEntityId = updatedUser.BusinessEntityId;
    original.Email = updatedUser.Email;
    original.EmployeeId = updatedUser.EmployeeId;
    original.Forename = updatedUser.Forename;
    original.Surname = updatedUser.Surname;
    original.Telephone = updatedUser.Telephone;
    original.Title = updatedUser.Title;
    original.Fax = updatedUser.Fax;
    original.ASPNetUserId = updatedUser.ASPNetUserId;
    db.SaveChanges();
}    

찬성

  • 변경할 속성을 지정할 수 있습니다
  • 뷰는 모든 속성을 포함 할 필요는 없습니다

단점

  • 원본을로드 한 후 업데이트하기위한 데이터베이스의 2 x 쿼리

방법 2-원본 레코드로드, 변경된 값 설정

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();
}

찬성

  • 수정 된 속성 만 데이터베이스로 전송됩니다

단점

  • 뷰는 모든 속성을 포함해야합니다
  • 원본을로드 한 후 업데이트하기위한 데이터베이스의 2 x 쿼리

방법 3-업데이트 된 레코드 첨부 및 상태를 EntityState.Modified로 설정

db.Users.Attach(updatedUser);
db.Entry(updatedUser).State = EntityState.Modified;
db.SaveChanges();

찬성

  • 업데이트 할 데이터베이스의 1 x 쿼리

단점

  • 변경할 속성을 지정할 수 없습니다
  • 보기에는 모든 속성이 포함되어야합니다

질문

너희들에게 내 질문; 이 목표를 달성 할 수있는 확실한 방법이 있습니까?

  • 변경할 속성을 지정할 수 있습니다
  • 조회수는 모든 속성 (예 : 비밀번호)을 포함 할 필요는 없습니다.
  • 업데이트 할 데이터베이스의 1 x 쿼리

나는 이것이 지적해야 할 사소한 일이라는 것을 알고 있지만 이것에 대한 간단한 해결책이 누락되었을 수 있습니다. 방법이 아니라면 우선합니다 ;-)


13
ViewModels와 좋은 매핑 엔진을 사용 하시겠습니까? 뷰를 채우고 업데이트 할 수있는 "업데이트 할 속성"만 있습니다. 업데이트에 대한 두 가지 쿼리가 여전히 있지만 (원본 + 업데이트) 쿼리를 "Con"이라고 부르지는 않습니다. 이것이 유일한 성능 문제라면, 당신은 행복한 사람입니다.)
Raphaël Althaus

감사합니다 @ RaphaëlAlthaus, 매우 유효한 포인트. 이 작업을 수행 할 수는 있지만 여러 테이블에 대해 CRUD 작업을 만들어야하므로 모델과 직접 작업하여 각 모델에 대해 n-1 ViewModel을 만들 수있는 방법을 찾고 있습니다.
Stokedout

3
글쎄, 현재 프로젝트 (많은 엔터티도 마찬가지)에서 우리는 Models 작업을 시작했으며 ViewModels 작업 시간을 잃을 것이라고 생각했습니다. 우리는 이제 ViewModels로 갔으며, 시작할 때 (무시할 수없는) 인프라 작업을 통해 훨씬 훨씬 더 명확하고 유지 관리가 쉬워졌습니다. 그리고 더 안전합니다 (악성 "숨겨진 필드"또는 이와 유사한 것들에 대해 걱정할 필요가 없습니다)
Raphaël Althaus

1
DropDownLists를 채우는 더 이상 (유쾌한) ViewBag가 없습니다 (거의 모든 CRU (D)보기에 적어도 하나의 DropDownList가 있습니다 ...)
Raphaël Althaus

나는 당신이 옳다고 생각합니다. 예, ViewBag는 때때로 조금 더러워 보입니다. 나는 보통 Dino Esposito의 블로그에 따라 한 걸음 더 나아가서 Tad 벨트와 버팀대도 InputModels을 만들지 만 꽤 잘 작동합니다. 모델 당 2 개의 추가 모델을 의미합니다
-doh

답변:


681

당신이 찾고있는:

db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();

59
안녕하세요 @Ladislav Mrnka, 모든 속성을 한 번에 업데이트하려면 아래 코드를 사용할 수 있습니까? db.Departments.Attach (부서); db.Entry (department) .State = EntityState.Modified; db.SaveChanges ();
Foyzul Karim 2016

23
@Foysal : 그렇습니다.
Ladislav Mrnka

5
이 접근법의 문제점 중 하나는 심각한 PITA 인 db.Entry ()를 조롱 할 수 없다는 것입니다. EF는 다른 곳에서 합리적으로 좋은 조롱 이야기를 가지고 있습니다-그들이 말할 수있는 한, 그들은 여기에 하나도 없다는 것이 꽤 성가시다.
Ken Smith

23
@Foysal Doing context.Entry (entity) .State = EntityState.Modified만으로는 첨부 할 필요가 없습니다. 수정 된대로 자동으로 첨부됩니다 ...
HelloWorld

4
@ Sandman4는 다른 모든 속성이 있어야하며 현재 값으로 설정되어야 함을 의미합니다. 일부 응용 프로그램 설계에서는 이것이 불가능합니다.
Dan Esparza

176

나는 받아 들인 대답을 정말로 좋아합니다. 나는 이것에 접근하는 또 다른 방법이 있다고 생각합니다. 뷰에 포함하고 싶지 않은 속성 목록이 매우 짧으므로 엔터티를 업데이트 할 때 속성이 생략된다고 가정 해 봅시다. 이 두 필드가 비밀번호와 SSN이라고 가정 해 봅시다.

db.Users.Attach(updatedUser);

var entry = db.Entry(updatedUser);
entry.State = EntityState.Modified;

entry.Property(e => e.Password).IsModified = false;
entry.Property(e => e.SSN).IsModified = false;   

db.SaveChanges();   

이 예에서는 사용자 테이블과보기에 새 필드를 추가 한 후에 비즈니스 로직을 그대로 둘 수 있습니다.


SSN 속성 값을 지정하지 않으면 여전히 IsModified를 false로 설정하더라도 모델 규칙에 따라 속성의 유효성을 검사해도 오류가 발생합니다. 따라서 속성이 NOT NULL로 표시되면 null과 다른 값을 설정하지 않으면 실패합니다.
RolandoCC

해당 필드가 양식에 없으므로 오류가 발생하지 않습니다. 업데이트하지 않을 필드는 그대로두고, 첨부하여 전달 된 양식을 사용하여 데이터베이스에서 항목을 가져 와서 해당 필드가 수정되지 않았다고 입력하십시오. 모델 유효성 검사는 컨텍스트가 아닌 ModelState에서 제어됩니다. 이 예는 기존 사용자를 참조하므로 "updatedUser"입니다. SSN이 필수 필드 인 경우 SSN이 처음 작성되었을 때 존재했을 것입니다.
smd

4
올바르게 이해하면 "updatedUser"는 이미 FirstOrDefault () 또는 이와 유사한 것으로 채워진 개체의 인스턴스이므로 변경된 속성 만 업데이트하고 다른 속성은 ISModified = false로 설정합니다. 이것은 잘 작동합니다. 그러나 내가하려고하는 일은 업데이트 전에 FirstOrDefault ()를 만들지 않고 먼저 객체를 채우지 않고 객체를 업데이트하는 것입니다. 필요한 모든 필드에 값을 지정하지 않으면 해당 속성에 ISModified = false를 설정하더라도 오류가 발생합니다. entry.Property (e => e.columnA) .IsModified = false; 이 줄이 없으면 ColumnA는 실패합니다.
RolandoCC

당신이 설명하는 것은 새로운 엔티티를 만드는 것입니다. 이것은 업데이트에만 적용됩니다.
smd

1
RolandoCC, db.Configuration.ValidateOnSaveEnabled = false; db.SaveChanges () 이전에;
Wilky

28
foreach(PropertyInfo propertyInfo in original.GetType().GetProperties()) {
    if (propertyInfo.GetValue(updatedUser, null) == null)
        propertyInfo.SetValue(updatedUser, propertyInfo.GetValue(original, null), null);
}
db.Entry(original).CurrentValues.SetValues(updatedUser);
db.SaveChanges();

이것은 정말 좋은 해결책 인 것 같습니다-무스 또는 소란이 없습니다. 속성을 수동으로 지정할 필요가 없으며 모든 OP 글 머리 기호를 고려합니다. 투표권이 더 이상없는 이유가 있습니까?
nocarrier

하지만 그렇지 않습니다. 그것은 데이터베이스에 대한 하나 이상의 히트, 가장 큰 "단점"중 하나를 가지고 있습니다. 이 답변과 함께 원본을로드해야합니다.
smd

1
@ smd 왜 데이터베이스에 두 번 이상 충돌한다고 말합니까? SetValues ​​()를 사용하는 것이 그 효과를 갖지 않는 한 일어나는 것은 보이지 않지만 그것이 사실처럼 보이지는 않습니다.
의회

@parliament 나는 그것을 쓸 때 내가 잠들었을 것 같아요. 사과. 실제 문제는 의도 된 null 값을 재정의하는 것입니다. 업데이트 된 사용자가 더 이상 무언가를 참조하지 않으면 지우려는 경우 원래 값으로 바꾸는 것이 옳지 않습니다.
smd

22

스캐 폴딩으로 생성 된 업데이트 방법과 비슷한 추가 업데이트 방법을 리포지토리 기본 클래스에 추가했습니다. 전체 개체를 "수정"으로 설정하는 대신 개별 속성 집합을 설정합니다. (T는 클래스 일반 매개 변수입니다.)

public void Update(T obj, params Expression<Func<T, object>>[] propertiesToUpdate)
{
    Context.Set<T>().Attach(obj);

    foreach (var p in propertiesToUpdate)
    {
        Context.Entry(obj).Property(p).IsModified = true;
    }
}

그리고 예를 들어

public void UpdatePasswordAndEmail(long userId, string password, string email)
{
    var user = new User {UserId = userId, Password = password, Email = email};

    Update(user, u => u.Password, u => u.Email);

    Save();
}

나는 데이터베이스 여행을 좋아한다. 그러나 속성 집합이 반복되는 것을 피하기 위해 뷰 모델 로이 작업을 수행하는 것이 좋습니다. 뷰 모델 검사기의 유효성 검사 메시지를 도메인 프로젝트로 가져 오는 것을 피하는 방법을 모르기 때문에 아직 수행하지 않았습니다.


Aha ... 뷰 모델을위한 별도의 프로젝트와 뷰 모델을 사용하는 리포지토리를위한 별도의 프로젝트.
Ian Warburton

11
public interface IRepository
{
    void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class;
}

public class Repository : DbContext, IRepository
{
    public void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class
    {
        Set<T>().Attach(obj);
        propertiesToUpdate.ToList().ForEach(p => Entry(obj).Property(p).IsModified = true);
        SaveChanges();
    }
}

왜 DbContext.Attach (obj); DbContext.Entry (obj) .State = EntityState.Modified;
nelsontruran

이것은 set업데이트 문의 부분을 제어합니다 .
Tanveer Badar

4

옵션 목록에 추가하기 만하면됩니다. 데이터베이스에서 객체를 가져오고 Auto Mapper 와 같은 자동 매핑 도구를 사용 하여 변경하려는 레코드 부분을 업데이트 할 수도 있습니다.


3

사용 사례에 따라 위의 모든 솔루션이 적용됩니다. 이것은 내가 보통하는 방법입니다 :

서버 측 코드 (예 : 배치 프로세스)의 경우 일반적으로 엔티티를로드하고 동적 프록시로 작업합니다. 일반적으로 배치 프로세스에서는 서비스가 실행될 때 어쨌든 데이터를로드해야합니다. 시간을 절약하기 위해 find 메소드를 사용하는 대신 데이터를 일괄로드하려고합니다. 프로세스에 따라 낙관적 또는 비관적 동시성 제어를 사용합니다 (일반적인 SQL 문으로 일부 레코드를 잠글 필요가있는 병렬 실행 시나리오를 제외하고는 항상 낙관적입니다). 코드와 시나리오에 따라 영향을 거의 0으로 줄일 수 있습니다.

클라이언트 측 시나리오의 경우 몇 가지 옵션이 있습니다.

  1. 뷰 모델을 사용하십시오. 모델에는 UpdateStatus (unmodified-inserted-updated-deleted) 속성이 있어야합니다. 사용자 조치 (삽입-업데이트-삭제)에 따라이 열에 올바른 값을 설정하는 것은 클라이언트의 책임입니다. 서버는 원래 값에 대해 db를 쿼리하거나 클라이언트가 변경된 행과 함께 원래 값을 서버에 보내야합니다. 서버는 원래 값을 첨부하고 각 행에 대해 UpdateStatus 열을 사용하여 새 값을 처리하는 방법을 결정해야합니다. 이 시나리오에서는 항상 낙관적 동시성을 사용합니다. 이것은 insert-update-delete 문만 수행하고 선택은 수행하지 않지만 그래프를 걷고 엔티티를 업데이트하려면 영리한 코드가 필요할 수 있습니다 (시나리오-응용 프로그램에 따라 다름). 매퍼는 CRUD 논리를 도울 수는 있지만 처리하지는 않습니다.

  2. 이 복잡성을 대부분 숨기는 breeze.js와 같은 라이브러리를 사용하고 (1에서 설명) 사용 사례에 맞게 조정하십시오.

그것이 도움이되기를 바랍니다.

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