ProgramingTip

동일한 키를 가진 개체가 ObjectStateManager에 이미 있습니다.

bestdevel 2020. 10. 29. 08:24
반응형

동일한 키를 가진 개체가 ObjectStateManager에 이미 있습니다. ObjectStateManager는 동일한 키로 여러 개체를 추적 할 수 없습니다.


일반 리포지토리 패턴과 EF5를 사용하여 표시하고 edmx로 데이터를 사용하여 데이터베이스로 업데이트 할 때 문제가 발생합니다.

DbContextRepository.cs의 내 업데이트는 다음과 가변적입니다.

public override void Update(T entity)
{
    if (entity == null)
        throw new ArgumentException("Cannot add a null entity.");

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached)
    {
        _context.Set<T>().Attach(entity);
        entry.State = EntityState.Modified;
    }
}

내 저장소로 돌아가는 내 AddressService.cs에서 다음이 있습니다.

 public int Save(vw_address address)
{
    if (address.address_pk == 0)
    {
        _repo.Insert(address);
    }
    else
    {
        _repo.Update(address);
    }

    _repo.SaveChanges();

    return address.address_pk;
}

Attach 및 EntityState.Modified에 도달하면 오류가 발생합니다.

동일한 키를 가진 개체가 ObjectStateManager에 이미 있습니다. ObjectStateManager는 동일한 키를 가진 여러 개체를 추적 할 수 없습니다.

나는 스택과 인터넷에서 많은 제안을 제안하고 해결하는 어떤 것도 생각하지 않습니다. 모든 해결 방법을 주시면 감사하겠습니다.

감사합니다!


편집 : Find대신 사용되는 원래 답변 Local.SingleOrDefault. @Juan의 Save방법 과 함께 작동 했지만 데이터베이스에 불필요한 쿼리를 유발할 수 있으며 else일부는 실행되지 않았을 수 있습니다 (찾기가 이미 데이터베이스를 쿼리하고 엔티티를 찾지 못했기 때문에 업데이트 할 수 없기 때문에 다른 부분을 실행하면 예외가 발생 함 ). . 문제를 찾아 준 @BenSwayne에게 감사드립니다.

동일한 키를 가진 엔터티가 이미 시나리오에서 추적 확인하고 현재 엔터티를 연결하는 대신 해당 엔터티를 수정해야합니다.

public override void Update(T entity) where T : IEntity {
    if (entity == null) {
        throw new ArgumentException("Cannot add a null entity.");
    }

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached) {
        var set = _context.Set<T>();
        T attachedEntity = set.Local.SingleOrDefault(e => e.Id == entity.Id);  // You need to have access to key

        if (attachedEntity != null) {
            var attachedEntry = _context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        } else {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}  

보시다시피 주요 문제는 SingleOrDefault메소드가 가수를 찾기 위해 키를 있다는 것입니다. 키를 노출하는 간단한 인터페이스를 생성 ( IEntity예 :) 방식으로 처리하려는 모든 엔터티에서 구현할 수 있습니다.


인터페이스 또는 속성을 추가하여 자동 생성 할 수 있습니다. 그래서 이것은 위의 답변 중 일부에서 실제로 약간입니다 (그래서 신용은 Ladislav Mrnka로 이동합니다). 이 나에게 간단한 해결책을 제공했습니다.

다수의 정수 키를 업데이트 메소드에 func를 추가했습니다.

public void Update(TEntity entity, Func<TEntity, int> getKey)
{
    if (entity == null) {
        throw new ArgumentException("Cannot add a null entity.");
    }

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached) {
        var set = _context.Set<T>();
        T attachedEntity = set.Find.(getKey(entity)); 

        if (attachedEntity != null) {
            var attachedEntry = _context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        } else {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}  

그런 다음 코드를 호출 할 때 ..

repository.Update(entity, key => key.myId);

반사를 통해 실제로 Id를 검색 할 수 있습니다. 아래 예를 참조하십시오.

        var entry = _dbContext.Entry<T>(entity);

        // Retreive the Id through reflection
        var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);

        if (entry.State == EntityState.Detached)
        {
            var set = _dbContext.Set<T>();
            T attachedEntity = set.Find(pkey);  // access the key
            if (attachedEntity != null)
            {
                var attachedEntry = _dbContext.Entry(attachedEntity);
                attachedEntry.CurrentValues.SetValues(entity);
            }
            else
            {
                entry.State = EntityState.Modified; // attach the entity
            }
        }


@ serj-sagan 다음과 같이해야합니다.

** YourDb는 DbContext에서 파생 된 클래스 집합니다.

public abstract class YourRepoBase<T> where T : class
{
    private YourDb _dbContext;
    private readonly DbSet<T> _dbset;

    public virtual void Update(T entity)
    {
        var entry = _dbContext.Entry<T>(entity);

        // Retreive the Id through reflection
        var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);

        if (entry.State == EntityState.Detached)
        {
           var set = _dbContext.Set<T>();
           T attachedEntity = set.Find(pkey);  // access the key
           if (attachedEntity != null)
           {
               var attachedEntry = _dbContext.Entry(attachedEntity);
               attachedEntry.CurrentValues.SetValues(entity);
           }
           else
           {
              entry.State = EntityState.Modified; // attach the entity
           }
       }
    }

}


@Sergey의 답변을 기반으로 한 또 다른 솔루션은 다음과 가변적입니다.

private void Update<T>(T entity, Func<T, bool> predicate) where T : class
{
    var entry = Context.Entry(entity);
    if (entry.State == EntityState.Detached)
    {
        var set = Context.Set<T>();
        T attachedEntity = set.Local.SingleOrDefault(predicate); 
        if (attachedEntity != null)
        {
            var attachedEntry = Context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        }
        else
        {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}

그리고 다음과 같이 부를 것입니다.

Update(EntitytoUpdate, key => key.Id == id)

리플렉션없이 인터페이스를 사용하지 않는 경우에는 기능 대리 튼 사용하여 데이터베이스에서 엔터티를 사용할 수 있습니다. 위의 업데이트 된 샘플은 다음과 가변합니다.

private void Update<T>(T entity, Func<ObservableCollection<T>, T> locatorMap) where T : class
{
    var entry = Context.Entry(entity);
    if (entry.State == EntityState.Detached)
    {
        var set = Context.Set<T>();
        T attachedEntity = locatorMap(set.Local); 

        if (attachedEntity != null)
        {
            var attachedEntry = Context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        }
        else
        {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}

다음과 같이 호출합니다.

Update(EntitytoUpdate, p => p.SingleOrDefault(a => a.Id == id))

컨텍스트를 AsNoTracking ()으로 설정하면 aspmvc가 메모리의 엔티티에 대한 변경 사항을 추적하는 것을 중지합니다 (어쨌든 웹에서 원하는 것입니다).

_dbContext.Products.AsNoTracking().Find(id);  

http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web 에서 이에 대한 자세한 내용을 읽을 것을 권장합니다 . -신청


엔티티가 발견 분리 (참조 attachedEntity라디의 솔루션)와 수정 된 일이 잘 나를 위해 일한 재 부착.

그 뒤에있는 이유는 간단합니다. 만약 무언가가 불변이라면 그것이 속한 곳에서 (전체적으로, 엔티티로) 원하는 것으로 교체하십시오.

이를 수행하는 방법의 예는 다음과 같습니다.

var set = this.Set<T>();
if (this.Entry(entity).State == EntityState.Detached)
{
    var attached = set.Find(id);
    if (attached != null) { this.Entry(attached).State = EntityState.Detached; }
    this.Attach(entity);
}

set.Update(entity);

물론이 스 니펫이 일반 메소드의 일부임을 쉽게 알아낼 수 있으므로 T템플릿 매개 변수 인, 및 Set<T>().


위의 답변은 EF 4.1+ 일 수 있습니다. 4.0의 경우이 간단한 방법을 시도하십시오 ... 실제로 테스트하지는 않았지만 변경 사항을 첨부하고 저장했습니다.

    public void UpdateRiskInsight(RiskInsight item)
    {
        if (item == null)
        {
            throw new ArgumentException("Cannot add a null entity.");
        }

        if (item.RiskInsightID == Guid.Empty)
        {
            _db.RiskInsights.AddObject(item);
        }
        else
        {
            item.EntityKey = new System.Data.EntityKey("GRC9Entities.RiskInsights", "RiskInsightID", item.RiskInsightID);
            var entry = _db.GetObjectByKey(item.EntityKey) as RiskInsight;
            if (entry != null)
            {
                _db.ApplyCurrentValues<RiskInsight>("GRC9Entities.RiskInsights", item);
            }

        }

        _db.SaveChanges();

    }

fBLL = new FornecedorBLL();알군 장소 에서 물체를 설치하는 것을 잊었을 수 있습니다.

참고 URL : https://stackoverflow.com/questions/12585664/an-object-with-the-same-key-already-exists-in-the-objectstatemanager-the-object

반응형