ASP.NET MVC-동일한 유형의 다른 엔티티가 이미 동일한 기본 키 값을 가지고 있기 때문에 'MODELNAME'유형의 엔티티를 연결하지 못했습니다.


122

간단히 말해서 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'로 적절하게 설정합니다.

아무도 내 코드에서 잘못된 것을 보거나 모델을 편집하는 동안 이러한 오류가 발생하는 상황을 이해합니까?


설정하기 전에 엔티티를 첨부 해 보셨습니까 EntityState? 귀하의 엔터티는 게시 요청에서
비롯되었으므로

나는이 일을 시도하고 그 결과는 정확히 동일 :( 어떤 이유로 컨텍스트 임은 새 항목을 생성하지만, 임 그냥 기존의 것을 업데이트하는 생각 ...
크리스 Ciszak

오류가 발생하기 전에 'a'의 상태를 확인하고이 개체의 상태는 '분리'이지만 db.As.Attach (aViewModel.a)를 호출하면 정확히 동일한 메시지가 발생합니까? 어떤 아이디어?
Chris Ciszak

5
방금 업데이트를 봤습니다. 컨텍스트 수명 범위를 어떻게 설정 했습니까? 요청에 따른 것입니까? 경우 db인스턴스가 당신의 두 가지 작업 사이의 동일 당신의 품목이 GET 방식 (다음 컨텍스트에 의해 추적)에 의해로드 될 때, 그것은 당신의 문제를 설명 할 수 있고, 기업은 이전에 가져온로는 POST 방법의 하나를 인식하지 못할 수 있습니다 .
Réda Mattar 2014

1
canUserAccessA()엔티티를 직접로드 합니까 아니면 다른 엔티티의 관계로로드 합니까 ?
CodeCaster 2014

답변:


155

문제 해결됨!

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()없었지만 쿼리를 변경하여 동일한 결과를 얻을 수 있기 때문에 실제로는 중요하지 않습니다.

이것이 비슷한 문제를 가진 사람에게 도움이되기를 바랍니다!


10
약간 깔끔하고 성능이 뛰어납니다 : if (db.As.AsNoTracking (). Any (x => x.aID == aID && x.UserID == userID))
Brent

11
참고 : 당신이 필요로 using System.Data.Entity;사용 AsNoTracking().
Maxime

제 경우에는 엔티티 ID를 제외한 필드 만 업데이트하면 정상적으로 작동합니다. var entity = context.Find (entity_id); entity.someProperty = newValue; context.Entry (entity) .Property (x => x.someProperty) .IsModified = true; context.SaveChanges ();
Anton Lyhin 2015

3
대규모 도움. 내 FirstOrDefault () 전에 .AsNoTracking ()을 추가했고 작동했습니다.
coggicc

110

재미있게:

_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);

또는 여전히 일반적이지 않은 경우 :

_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);

내 문제를 원활하게 해결 한 것 같습니다.


1
연결이 끊긴 앱에서 사용자 지정 조인 테이블을 사용하여 다 대다 레코드를 업데이트해야하는 시나리오에서 완벽하게 작동했습니다. 데이터베이스에서 가져온 엔티티에도 참조 오류 등이 발생했습니다. "context.Entry (score) .State = System.Data.Entity.EntityState.Modified;"를 사용하고있었습니다. 그러나 이것은 마침내 작동했습니다! 감사합니다!!
firecape

5
작동합니다. 내가 이미 noTracking을 수행하고 있었기 때문에 notracking 부착 및 사용에 대한 다른 모든 제안은 실패했습니다. 솔루션에 감사드립니다.
Khainestar

3
이것은 동일한 작업 단위 내에서 상위 및 하위 엔티티 를 업데이트하는 동안 저에게 효과적 이었습니다. 많은 감사
이안

55
찾는 사람 AddOrUpdate을 위해 System.Data.Entity.Migrations네임 스페이스 의 확장 메서드입니다 .

1
@Artyomska 불행히도 나는 모른다.
guneysus jul.

15

수정하려는 엔티티가 올바르게 추적되지 않아 수정 된 것으로 인식되지 않고 대신 추가 된 것 같습니다.

상태를 직접 설정하는 대신 다음을 수행하십시오.

//db.Entry(aViewModel.a).State = EntityState.Modified;
db.As.Attach(aViewModel.a); 
db.SaveChanges();

또한 귀하의 코드에 잠재적 인 보안 취약점이 있음을 경고하고 싶습니다. 보기 모델에서 직접 엔터티를 사용하는 경우 제출 된 양식에 올바르게 이름이 지정된 필드를 추가하여 누군가 엔터티의 콘텐츠를 수정할 수있는 위험이 있습니다. 예를 들어 사용자가 이름이 "A.FirstName"인 입력 상자를 추가하고 엔티티에 해당 필드가 포함 된 경우 사용자가 정상적인 응용 프로그램 작동에서 변경할 수 없더라도 값은 viewmodel에 바인딩되어 데이터베이스에 저장됩니다. .

최신 정보:

앞서 언급 한 보안 취약점을 극복하려면 도메인 모델을 뷰 모델로 노출해서는 안되지만 대신 별도의 뷰 모델을 사용해야합니다. 그런 다음 작업은 AutoMapper와 같은 매핑 도구를 사용하여 도메인 모델에 다시 매핑 할 수있는 viewmodel을 수신합니다. 이렇게하면 사용자가 민감한 데이터를 수정하는 것을 방지 할 수 있습니다.

다음은 자세한 설명입니다.

http://www.stevefenton.co.uk/Content/Blog/Date/201303/Blog/Why-You-Never-Expose-Your-Domain-Model-As-Your-MVC-Model/


3
안녕하세요 Kaspars, 입력 해 주셔서 감사합니다. Attach 메서드는 내 질문에 언급 된 것과 동일한 오류를 발생시킵니다. 문제는 canUserAccessA () 함수가 위에서 본 CodeCaster뿐만 아니라 엔티티를로드한다는 것입니다. 그러나 보안에 관한 귀하의 제안에 매우 관심이 있습니다. 그러한 행동을 방지하기 위해 무엇을해야하는지 제안 해 주시겠습니까?
Chris Ciszak

보안 취약성을 방지하는 방법에 대한 추가 정보로 내 답변을 업데이트했습니다.
Kaspars Ozols 2014

13

이 시도:

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;

11

나에게 로컬 사본이 문제의 원인이었습니다. 이것은 그것을 해결했다

var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
                if (local != null)
                {
                    context.Entry(local).State = EntityState.Detached;
                }

10

제 경우는 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;
}

컨텍스트 엔티티 상태를 참조하기 위해 리포지토리를 사용하지 않았지만 이것은 저에게 효과적이었습니다.
Eckert

3

더 빨리 깨닫지 못한 것에 대해 약간 어리석은 느낌이 들지만 나는 이것에 대한 나의 경험을 공유 할 것이라고 생각했습니다.

컨트롤러에 주입 된 저장소 인스턴스와 함께 저장소 패턴을 사용하고 있습니다. 구체적인 리포지토리 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를 지정하여 원하는 경우 이전 인스턴스를 사용할 수 있습니다.


2

더 복잡한 데이터 패턴을 기반으로 문제를 설명하고 여기서 이해하기가 어려웠 기 때문에이 답변을 추가했습니다.

나는 상당히 간단한 응용 프로그램을 만들었습니다. 이 오류는 Edit POST 작업 내에서 발생했습니다. 조치가 ViewModel을 입력 매개 변수로 허용했습니다. ViewModel을 사용하는 이유는 레코드를 저장하기 전에 계산을하기 위해서입니다.

작업이와 같은 유효성 검사를 통과하면 if(ModelState.IsValid)ViewModel의 값을 완전히 새로운 Entity 인스턴스로 프로젝션하는 것이 잘못되었습니다. 업데이트 된 데이터를 저장하기 위해 새 인스턴스를 생성 한 다음 이러한 인스턴스를 저장해야한다고 생각했습니다.

나중에 깨달은 것은 데이터베이스에서 레코드를 읽어야한다는 것입니다.

Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID);

이 개체를 업데이트했습니다. 이제 모든 것이 작동합니다.


2

로컬 var 에이 문제가 있었고 다음과 같이 분리했습니다.

if (ModelState.IsValid)
{
    var old = db.Channel.Find(channel.Id);
    if (Request.Files.Count > 0)
    {
        HttpPostedFileBase objFiles = Request.Files[0];
        using (var binaryReader = new BinaryReader(objFiles.InputStream))
        {
            channel.GateImage = binaryReader.ReadBytes(objFiles.ContentLength);
        }

    }
    else
        channel.GateImage = old.GateImage;
    var cat = db.Category.Find(CatID);
    if (cat != null)
        channel.Category = cat;
    db.Entry(old).State = EntityState.Detached; // just added this line
    db.Entry(channel).State = EntityState.Modified;
    await db.SaveChangesAsync();
    return RedirectToAction("Index");
}
return View(channel);

동일한 키를 가진로드 된 개체의 문제 원인이므로 먼저 해당 개체를 분리하고 동일한 키를 가진 두 개체 간의 충돌을 피하기 위해 업데이트를 수행합니다.


@Artjom B 문제는 동일한 키를 가진로드 된 개체의 원인이므로 먼저 해당 개체를 분리하고 동일한 키를 가진 두 개체 간의 충돌을 피하기 위해 업데이트를 수행합니다
lvl4fi4

2

비슷한 문제가 발생했습니다. 2-3 일 동안 조사한 후 EF가 변경 사항을 추적하지 않고 개체가 연결되지 않는 한 변경 사항이 없다고 가정하므로 ".AsNoTracking"을 제거해야합니다. 또한 .AsNoTracking을 사용하지 않으면 EF는 저장 / 업데이트 할 개체를 자동으로 인식하므로 첨부 / 추가를 사용할 필요가 없습니다.


2

AsNoTracking()쿼리를받는 위치를 사용하십시오 .

  var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();

2

이 오류가 발생했습니다.

  • 두 방법, A & B는 단일 제어기 모두는 ApplicationDbContext의 동일한 인스턴스를 사용 하고
  • 방법 A 호출 된 방법 B
    private ApplicationDbContext db;
    // api methods
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return methodB()
    }

    public JsonResult methodB(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return new JsonResult();
    }

나는 using 문을 갖고 로컬 db2 에만 의존하도록 방법 B를 변경했다 . 후:

    private ApplicationDbContext db;    
    // api methods    
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return methodB()
    }

    public JsonResult methodB(string id){
        using (var db2 = new ApplicationDbContext())
        {
            Resource resource = db2.Resources.Find(id);
            db2.Entry(resource).State = EntityState.Modified;
            db2.SaveChanges();
        }
        return new JsonResult();
    }

1

Luke Puplett이 말한 것과 유사하게 컨텍스트를 적절하게 처리하거나 생성하지 않으면 문제가 발생할 수 있습니다.

제 경우에는 다음과 같은 컨텍스트를 허용하는 클래스가 있습니다 ContextService.

public class ContextService : IDisposable
{
    private Context _context;

    public void Dispose()
    {
        _context.Dispose();
    }
    public ContextService(Context context)
    {
        _context = context;
    }
//... do stuff with the context

내 컨텍스트 서비스에는 인스턴스화 된 엔티티 객체를 사용하여 엔티티를 업데이트하는 함수가 있습니다.

        public void UpdateEntity(MyEntity myEntity, ICollection<int> ids)
        {
            var item = _context.Entry(myEntity);
            item.State = EntityState.Modified;
            item.Collection(x => x.RelatedEntities).Load();
            myEntity.RelatedEntities.Clear();
            foreach (var id in ids)
            {
                myEntity.RelatedEntities.Add(_context.RelatedEntities.Find(id));
            }
            _context.SaveChanges();
        }

이 모든 것이 괜찮 았고 서비스를 초기화 한 컨트롤러가 문제였습니다. 내 컨트롤러는 원래 다음과 같이 보였습니다.

    private static NotificationService _service = 
        new NotificationService(new NotificationContext());
    public void Dispose()
    {
    }

나는 이것을 이것을 변경했고 오류가 사라졌습니다.

    private static NotificationService _service;
    public TemplateController()
    {
        _service = new NotificationService(new NotificationContext());
    }
    public void Dispose()
    {
        _service.Dispose();
    }

1

이 문제는 또한 중에 알 수있는 ViewModelEntityModel(하여 매핑 AutoMapper하고 포함하려는 등) context.Entry().Statecontext.SaveChanges()문제를 해결할 아래와 같이 이러한 블록을 사용. 블록을 사용할 때도 사용되어야하므로 context.SaveChanges()바로 뒤에 사용하는 대신 두 번 사용 하는 방법입니다 if-block.

public void Save(YourEntity entity)
{
    if (entity.Id == 0)
    {
        context.YourEntity.Add(entity);
        context.SaveChanges();
    }
    else
    {
        using (var context = new YourDbContext())
        {
            context.Entry(entity).State = EntityState.Modified;
            context.SaveChanges(); //Must be in using block
        }
    }            
}

도움이 되었기를 바랍니다...


1

비슷한 경우에 내가 한 일.

이 상황은 동일한 개체가 이미 컨텍스트에 존재했음을 의미합니다.

엔터티가 컨텍스트에 있는지 먼저 ChangeTracker에서 확인

var trackedEntries=GetContext().ChangeTracker.Entries<YourEntityType>().ToList();

var isAlreadyTracked =
                    trackedEntries.Any(trackedItem => trackedItem.Entity.Id ==myEntityToSave.Id);

존재한다면

  if (isAlreadyTracked)
            {
                myEntityToSave= trackedEntries.First(trackedItem => trackedItem.Entity.Id == myEntityToSave.Id).Entity;
            } 

else
{
//Attach or Modify depending on your needs
}

1

상태를 업데이트하여 문제를 해결하도록 관리합니다. 동일한 레코드에 대한 찾기 또는 다른 쿼리 작업을 트리거 할 때 수정 된 상태로 업데이트되었으므로 상태를 분리됨으로 설정해야 업데이트 변경을 실행할 수 있습니다.

     ActivityEntity activity = new ActivityEntity();
      activity.name="vv";
    activity.ID = 22 ; //sample id
   var savedActivity = context.Activities.Find(22);

            if (savedActivity!=null)
            {
                context.Entry(savedActivity).State = EntityState.Detached;
                context.SaveChanges();

                activity.age= savedActivity.age;
                activity.marks= savedActivity.marks; 

                context.Entry(activity).State = EntityState.Modified;
                context.SaveChanges();
                return activity.ID;
            }

1

이 문제를 "사용"블록으로 해결합니다.

using (SqlConnection conn = new SqlConnection(connectionString))

    {

       // stuff to do with data base
    }

    // or if you are using entity framework 
    using (DataBaseEntity data = new DataBaseEntity)
{

    }

여기에서 아이디어를 얻을 수 있습니다. https://social.msdn.microsoft.com/Forums/sqlserver/es-ES/b4b350ba-b0d5-464d-8656-8c117d55b2af/problema-al-modificar-en-entity-framework?forum = vcses 는 스페인어로되어 있습니다 (두 번째 답변 찾기)


단지 조심하고 당신이 그것을하지 않는 경우에만 오류 엔티티 프레임 워크 엔티티 객체가 IEntityChangeTracker의 여러 인스턴스에서 참조 할 수 없습니다 얻을 것이다 특별히 당신이 엔티티 프레임 워크를 사용하는 경우, 데이터베이스 conexion의 한 인스턴스를 사용
Suzume

1

다음과 같은 추가 방법을 사용할 수 있습니다.

_dbContext.Entry(modelclassname).State = EntityState.Added;

그러나 많은 경우에 한 번에 둘 이상의 모델을 사용하려는 경우 엔티티가 이미 다른 엔티티에 연결되어 있으므로 작동하지 않습니다. 따라서 그 당시 단순히 객체를 서로 마이그레이션하는 ADDOrUpdate Entity Migration 메서드를 사용할 수 있으며 결과적으로 오류가 발생하지 않습니다.

_dbContext.Set<modelclassname>().AddOrUpdate(yourmodel);

0

모든 상태 지우기

dbContextGlobalERP.ChangeTracker.Entries (). Where (e => e.Entity! = null) .ToList (). ForEach (e => e.State = EntityState.Detached);

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