간단히 말해서 POSTing 래퍼 모델과 한 항목의 상태를 ‘수정 됨’으로 변경하는 동안 예외가 발생합니다. 상태를 변경하기 전에 상태는 ‘분리됨’으로 설정되지만 Attach ()를 호출하면 동일한 오류가 발생합니다. EF6을 사용하고 있습니다.
아래에서 내 코드를 찾으십시오 (모델명이 읽기 쉽게 변경되었습니다).
모델
// Wrapper classes
public class AViewModel
{
public A a { get; set; }
public List<B> b { get; set; }
public C c { get; set; }
}
제어 장치
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (!canUserAccessA(id.Value))
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
var aViewModel = new AViewModel();
aViewModel.A = db.As.Find(id);
if (aViewModel.Receipt == null)
{
return HttpNotFound();
}
aViewModel.b = db.Bs.Where(x => x.aID == id.Value).ToList();
aViewModel.Vendor = db.Cs.Where(x => x.cID == aViewModel.a.cID).FirstOrDefault();
return View(aViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(AViewModel aViewModel)
{
if (!canUserAccessA(aViewModel.a.aID) || aViewModel.a.UserID != WebSecurity.GetUserId(User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
if (ModelState.IsValid)
{
db.Entry(aViewModel.a).State = EntityState.Modified; //THIS IS WHERE THE ERROR IS BEING THROWN
db.SaveChanges();
return RedirectToAction("Index");
}
return View(aViewModel);
}
위와 같이
db.Entry(aViewModel.a).State = EntityState.Modified;
예외가 발생합니다.
동일한 유형의 다른 엔티티에 이미 동일한 기본 키 값이 있으므로 유형 ‘A’의 엔티티를 연결하지 못했습니다. 이는 ‘Attach’메서드를 사용하거나 그래프의 항목에 충돌하는 키 값이있는 경우 항목 상태를 ‘변경되지 않음’또는 ‘수정 됨’으로 설정할 때 발생할 수 있습니다. 일부 항목이 새 항목이고 아직 데이터베이스 생성 키 값을받지 못했기 때문일 수 있습니다. 이 경우 ‘Add’메소드 또는 ‘Added’항목 상태를 사용하여 그래프를 추적 한 다음 새 항목이 아닌 항목의 상태를 ‘Unchanged’또는 ‘Modified’로 적절하게 설정합니다.
아무도 내 코드에서 잘못된 것을 보거나 모델을 편집하는 동안 이러한 오류가 발생하는 상황을 이해합니까?
답변
문제 해결됨!
Attach
메서드는 잠재적으로 누군가를 도울 수 있지만 편집 GET 컨트롤러 기능에서로드되는 동안 문서가 이미 추적되고 있기 때문에이 상황에서는 도움이되지 않습니다. Attach는 똑같은 오류를 발생시킵니다.
여기서 발생한 문제 canUserAccessA()
는 객체 a의 상태를 업데이트하기 전에 A 엔티티를로드하는 함수로 인해 발생했습니다 . 이것은 추적 된 엔티티를 망쳐 놓고 객체의 상태를 Detached
.
해결책은 canUserAccessA()
내가로드하는 객체가 추적되지 않도록 수정 하는 것이 었습니다. AsNoTracking()
컨텍스트를 쿼리하는 동안 함수 를 호출해야합니다.
// User -> Receipt validation
private bool canUserAccessA(int aID)
{
int userID = WebSecurity.GetUserId(User.Identity.Name);
int aFound = db.Model.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
return (aFound > 0); //if aFound > 0, then return true, else return false.
}
어떤 이유로 나는 사용할 .Find(aID)
수 AsNoTracking()
없었지만 쿼리를 변경하여 동일한 결과를 얻을 수 있기 때문에 실제로는 중요하지 않습니다.
이것이 비슷한 문제를 가진 사람에게 도움이되기를 바랍니다!
답변
재미있게:
_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);
또는 여전히 일반적이지 않은 경우 :
_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);
내 문제를 원활하게 해결 한 것 같습니다.
답변
수정하려는 엔티티가 올바르게 추적되지 않아 수정 된 것으로 인식되지 않고 대신 추가 된 것 같습니다.
상태를 직접 설정하는 대신 다음을 수행하십시오.
//db.Entry(aViewModel.a).State = EntityState.Modified;
db.As.Attach(aViewModel.a);
db.SaveChanges();
또한 귀하의 코드에 잠재적 인 보안 취약점이 있음을 경고하고 싶습니다. 보기 모델에서 직접 엔터티를 사용하는 경우 제출 된 양식에 올바르게 이름이 지정된 필드를 추가하여 누군가 엔터티의 콘텐츠를 수정할 수있는 위험이 있습니다. 예를 들어 사용자가 이름이 “A.FirstName”인 입력 상자를 추가하고 엔티티에 해당 필드가 포함 된 경우 사용자가 정상적인 응용 프로그램 작동에서 변경할 수 없더라도 값은 viewmodel에 바인딩되어 데이터베이스에 저장됩니다. .
최신 정보:
앞서 언급 한 보안 취약점을 극복하려면 도메인 모델을 뷰 모델로 노출해서는 안되지만 대신 별도의 뷰 모델을 사용해야합니다. 그런 다음 작업은 AutoMapper와 같은 매핑 도구를 사용하여 도메인 모델에 다시 매핑 할 수있는 viewmodel을 수신합니다. 이렇게하면 사용자가 민감한 데이터를 수정하는 것을 방지 할 수 있습니다.
다음은 자세한 설명입니다.
답변
이 시도:
var local = yourDbContext.Set<YourModel>()
.Local
.FirstOrDefault(f => f.Id == yourModel.Id);
if (local != null)
{
yourDbContext.Entry(local).State = EntityState.Detached;
}
yourDbContext.Entry(applicationModel).State = EntityState.Modified;
답변
나에게 로컬 사본이 문제의 원인이었습니다. 이것은 그것을 해결했다
var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
if (local != null)
{
context.Entry(local).State = EntityState.Detached;
}
답변
제 경우는 MVC 앱에서 EF 컨텍스트에 직접 액세스 할 수 없었습니다.
따라서 엔티티 지속성을 위해 일종의 저장소를 사용하는 경우 명시 적으로로드 된 엔티티를 분리 한 다음 바인드 된 EntityState를 Modified로 설정하는 것이 적절할 수 있습니다.
샘플 (추상) 코드 :
MVC
public ActionResult(A a)
{
A aa = repo.Find(...);
// some logic
repo.Detach(aa);
repo.Update(a);
}
저장소
void Update(A a)
{
context.Entry(a).EntityState = EntityState.Modified;
context.SaveChanges();
}
void Detach(A a)
{
context.Entry(a).EntityState = EntityState.Detached;
}
답변
더 빨리 깨닫지 못한 것에 대해 약간 어리석은 느낌이 들지만 나는 이것에 대한 나의 경험을 공유 할 것이라고 생각했습니다.
컨트롤러에 주입 된 저장소 인스턴스와 함께 저장소 패턴을 사용하고 있습니다. 구체적인 리포지토리 IDisposable
는 컨트롤러에 의해 삭제 되는 리포지토리의 수명 동안 지속되는 ModelContext (DbContext)를 인스턴스화합니다 .
나에게 문제는 내 엔티티에 수정 된 스탬프 및 행 버전이 있으므로 인바운드 헤더와 비교하기 위해 먼저 가져 왔습니다. 물론 이것은 나중에 업데이트되는 엔티티를로드하고 추적했습니다.
수정 사항은 단순히 생성자에서 컨텍스트를 새로 작성하는 것에서 다음 메서드를 갖는 것으로 저장소를 변경하는 것입니다.
private DbContext GetDbContext()
{
return this.GetDbContext(false);
}
protected virtual DbContext GetDbContext(bool canUseCachedContext)
{
if (_dbContext != null)
{
if (canUseCachedContext)
{
return _dbContext;
}
else
{
_dbContext.Dispose();
}
}
_dbContext = new ModelContext();
return _dbContext;
}
#region IDisposable Members
public void Dispose()
{
this.Dispose(true);
}
protected virtual void Dispose(bool isDisposing)
{
if (!_isDisposed)
{
if (isDisposing)
{
// Clear down managed resources.
if (_dbContext != null)
_dbContext.Dispose();
}
_isDisposed = true;
}
}
#endregion
이를 통해 저장소 메소드는를 호출하여 사용할 때마다 컨텍스트 인스턴스를 갱신 GetDbContext
하거나 true를 지정하여 원하는 경우 이전 인스턴스를 사용할 수 있습니다.