Entity Framework를 사용하여 다른 삽입 논리가있는 경우 행 업데이트


179

누구든지 Entity Framework를 사용하여 "삽입 행이 있으면 삽입 행"논리를 구현하는 가장 효율적인 방법에 대한 제안이 있습니까?


2
이것은 저장 프로 시저에서 데이터베이스 엔진 수준에서 수행해야하는 작업입니다. 그렇지 않으면 트랜잭션에서 감지 / 업데이트 / 삽입을 래핑해야합니다.
Stephen Chung

1
@Stephen : 사실, 이것이 내가 한 일입니다. 감사.
Jonathan Wood

조나단, 당신의 질문은 나에게 매우 유용합니다. 저장 프로 시저로 전환 한 이유는 무엇입니까?
anar khalilov

2
@Anar : 더 쉬웠고 훨씬 더 효율적일 것으로 기대합니다.
Jonathan Wood

모든 테이블에 대해 스토어드 프로 시저를 작성해야합니까?
tofutim

답변:


174

연결된 객체 (동일한 컨텍스트 인스턴스에서로드 된 객체)로 작업하는 경우 간단히 다음을 사용할 수 있습니다.

if (context.ObjectStateManager.GetObjectStateEntry(myEntity).State == EntityState.Detached)
{
    context.MyEntities.AddObject(myEntity);
}

// Attached object tracks modifications automatically

context.SaveChanges();

객체의 키에 대한 지식을 사용할 수 있다면 다음과 같이 사용할 수 있습니다.

if (myEntity.Id != 0)
{
    context.MyEntities.Attach(myEntity);
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
    context.MyEntities.AddObject(myEntity);
}

context.SaveChanges();

ID로 객체의 존재를 결정할 수 없으면 조회 조회가 필요합니다.

var id = myEntity.Id;
if (context.MyEntities.Any(e => e.Id == id))
{
    context.MyEntities.Attach(myEntity);
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
    context.MyEntities.AddObject(myEntity);
}

context.SaveChanges();

감사. 내가 필요한 것 같습니다. 잠시 귀찮은 질문 하나해도 될까요? 일반적으로 컨텍스트를 짧은 using블록 에 넣습니다 . 컨텍스트를 잠시 동안 메모리에 두는 것이 괜찮습니까? 예를 들어, Windows 양식 수명 동안? 일반적으로 데이터베이스에 대한로드를 최소화하기 위해 데이터베이스 개체를 정리하려고합니다. EF 컨텍스트를 파괴하기 위해 대기하는 데 문제가 없습니까?
Jonathan Wood

이것을 확인하십시오 : stackoverflow.com/questions/3653009/… 객체 컨텍스트는 가능한 한 짧아야하지만 winforms 또는 wpf의 경우 컨텍스트가 발표자만큼 오래 지속될 수 있습니다. 연결된 질문에는 winforms에서 nhibernate 세션을 사용하는 방법에 대한 msdn 기사 링크가 포함되어 있습니다. 컨텍스트에 동일한 접근 방식을 사용할 수 있습니다.
Ladislav Mrnka

1
그러나 객체 목록 으로이 작업을 수행 해야하는 경우 ... 데이터베이스에 동일한 ID를 가진 행 목록이 있고 존재하는 경우 교체하거나 삽입하지 않으면 삽입하고 싶습니다. 어떻게해야합니까? 감사!
Phoenix_uy

1
이 답변은 멋지지만 업데이트 시이 문제가 발생합니다. 동일한 키를 가진 객체가 이미 ObjectStateManager에 있습니다. ObjectStateManager는 동일한 키를 사용하여 여러 오브젝트를 추적 할 수 없습니다.
존 줌 브룸

1
업데이트를 수행하기 전에 키를 검색하기 위해 기존 객체를 가져 오는 데 약간의 문제가있는 것 같습니다. 해당 조회 오브젝트를 분리하면 먼저 수정하는 데 도움이되었습니다.
John Zumbrum

33

Entity Framework 4.3부터 AddOrUpdate네임 스페이스 에는 메소드가 있습니다 System.Data.Entity.Migrations.

public static void AddOrUpdate<TEntity>(
    this IDbSet<TEntity> set,
    params TEntity[] entities
)
where TEntity : class

어떤 의사에 의해 :

SaveChanges가 호출 될 때 키로 엔티티를 추가하거나 업데이트합니다. 데이터베이스 용어의 "upsert"작업과 같습니다. 이 방법은 마이그레이션을 사용하여 데이터를 시드 할 때 유용 할 수 있습니다.


@ Smashing1978의견에 답하기 위해 @Colin에서 제공하는 링크에서 관련 부품을 붙여 넣습니다.

AddOrUpdate의 작업은 개발 중에 데이터를 시드 할 때 복제본을 만들지 않도록하는 것입니다.

먼저, 키 (첫 번째 매개 변수)로 제공 한 항목이 AddOrUpdate에 제공된 맵핑 된 열 값과 일치하는 레코드를 찾기 위해 데이터베이스에서 쿼리를 실행합니다. 따라서 이것은 시합에 대한 약간 느슨한 끈적이지만 시딩 설계 시간 데이터에는 완벽하게 좋습니다.

더 중요한 것은 일치하는 항목이 발견되면 업데이트가 모두 업데이트되고 AddOrUpdate에없는 항목은 모두 제거한다는 것입니다.

즉, 외부 서비스에서 데이터를 가져 와서 기본 키로 기존 값을 삽입하거나 업데이트하고 소비자에 대한 로컬 데이터는 읽기 전용입니다. AddOrUpdate현재 6 개월 이상 프로덕션 환경 에서 사용 하고 있습니다. 문제 없습니다.


7
System.Data.Entity.Migrations 네임 스페이스에는 코드 기반 마이그레이션 및 해당 구성과 관련된 클래스가 포함되어 있습니다. 마이그레이션이 아닌 엔터티 AddOrUpdates에 대한 리포지토리에서 이것을 사용해서는 안되는 이유가 있습니까?
매트 Lengenfelder

10
AddOrUpdate 메소드를주의하십시오 : thedatafarm.com/data-access/…
Colin

1
이 기사에서는 AddOrUpdate를 사용해서는 안되는 이유를 설명합니다. michaelgmccarthy.com/2016/08/24/…
Nolmë Informatique

11

마법은 전화 할 때 발생 SaveChanges()하며 전류에 따라 다릅니다 EntityState. 엔터티가 EntityState.Added이면 데이터베이스에 추가되고, 있으면 엔터티 EntityState.Modified에서 업데이트됩니다. 따라서 InsertOrUpdate()다음과 같이 메소드를 구현할 수 있습니다 .

public void InsertOrUpdate(Blog blog) 
{ 
    using (var context = new BloggingContext()) 
    { 
        context.Entry(blog).State = blog.BlogId == 0 ? 
                                   EntityState.Added : 
                                   EntityState.Modified; 

        context.SaveChanges(); 
    } 
}

EntityState에 대한 추가 정보

Id = 0새로운 엔티티인지 여부를 확인할 수 없으면 Ladislav Mrnka답변을 확인하십시오 .


8

동일한 컨텍스트를 사용하고 엔티티를 분리하지 않는 경우 다음과 같이 일반 버전을 만들 수 있습니다.

public void InsertOrUpdate<T>(T entity, DbContext db) where T : class
{
    if (db.Entry(entity).State == EntityState.Detached)
        db.Set<T>().Add(entity);

    // If an immediate save is needed, can be slow though
    // if iterating through many entities:
    db.SaveChanges(); 
}

db 물론 클래스 필드가 될 수도 있고, 메소드를 정적으로 확장 할 수도 있지만 이것이 기본입니다.


4

Ladislav의 대답은 가까웠지만 EF6 (데이터베이스 우선)에서 작동하도록 몇 가지 수정 작업을 수행해야했습니다. onAddOrUpdate 메소드를 사용하여 데이터 컨텍스트를 확장했으며 지금까지는 분리 된 객체에서 잘 작동하는 것으로 보입니다.

using System.Data.Entity;

[....]

public partial class MyDBEntities {

  public void AddOrUpdate(MyDBEntities ctx, DbSet set, Object obj, long ID) {
      if (ID != 0) {
          set.Attach(obj);
          ctx.Entry(obj).State = EntityState.Modified;
      }
      else {
          set.Add(obj);
      }
  }
[....]

AddOrUpdate는 System.Data.Entity.Migrations에 확장 메소드로도 존재하므로 내가 당신이라면 자신의 메소드에 동일한 메소드 이름을 재사용하는 것을 피할 것입니다.
AF Julact

2

내 의견으로는 Entity Framework Code에 대해 새로 릴리스 된 EntityGraphOperations를 사용 하면 그래프에서 모든 엔티티의 상태를 정의하기 위해 반복적 인 코드를 작성하지 않아도 됩니다. 나는이 제품의 저자입니다. 그리고 github , code-project ( 단계별 데모 포함 및 샘플 프로젝트 다운로드 준비 완료)nuget에 게시했습니다. .

그것은 것이다 자동으로 개체의 상태를 설정AddedModified. 더 이상 존재하지 않는 경우 삭제할 엔티티를 수동으로 선택합니다.

예를 들면 :

내가 Person물건을 가지고 있다고 가정 해 봅시다 . Person많은 전화, 문서, 배우자가있을 수 있습니다.

public class Person
{
     public int Id { get; set; }
     public string FirstName { get; set; }
     public string LastName { get; set; }
     public string MiddleName { get; set; }
     public int Age { get; set; }
     public int DocumentId {get; set;}

     public virtual ICollection<Phone> Phones { get; set; }
     public virtual Document Document { get; set; }
     public virtual PersonSpouse PersonSpouse { get; set; }
}

그래프에 포함 된 모든 엔티티의 상태를 결정하고 싶습니다.

context.InsertOrUpdateGraph(person)
       .After(entity =>
       {
            // Delete missing phones.
            entity.HasCollection(p => p.Phones)
               .DeleteMissingEntities();

            // Delete if spouse is not exist anymore.
            entity.HasNavigationalProperty(m => m.PersonSpouse)
                  .DeleteIfNull();
       });

또한 아시다시피 전화 엔터티의 상태를 정의하는 동안 고유 키 속성이 역할을 수행 할 수 있습니다. 그러한 특별한 목적을 위해 우리는 ExtendedEntityTypeConfiguration<>클래스를 상속받습니다 EntityTypeConfiguration<>. 이러한 특수 구성을 사용하려면에서 ExtendedEntityTypeConfiguration<>대신 매핑 클래스를 상속해야합니다 EntityTypeConfiguration<>. 예를 들면 다음과 같습니다.

public class PhoneMap: ExtendedEntityTypeConfiguration<Phone>
    {
        public PhoneMap()
        {
             // Primary Key
             this.HasKey(m => m.Id);
              
             // Unique keys
             this.HasUniqueKey(m => new { m.Prefix, m.Digits });
        }
    }

그게 다야.


2

다른 것을 삽입하면 둘 다 업데이트

public void InsertUpdateData()
{
//Here TestEntities is the class which is given from "Save entity connection setting in web.config"
TestEntities context = new TestEntities();

var query = from data in context.Employee
            orderby data.name
            select data;

foreach (Employee details in query)
{
    if (details.id == 1)
    {
        //Assign the new values to name whose id is 1
        details.name = "Sanjay";
        details. Surname="Desai";
        details.address=" Desiwadi";
    }
    else if(query==null)
    {
        details.name="Sharad";
        details.surname=" Chougale ";
        details.address=" Gargoti";
    }
}

//Save the changes back to database.
context.SaveChanges();
}

나는이 접근법을 사용했지만 (query == null) if (query == null)
Patrick

2

기존 행을 모두 확인하십시오.

    public static void insertOrUpdateCustomer(Customer customer)
    {
        using (var db = getDb())
        {

            db.Entry(customer).State = !db.Customer.Any(f => f.CustomerId == customer.CustomerId) ? EntityState.Added : EntityState.Modified;
            db.SaveChanges();

        }

    }

1

@LadislavMrnka 답변의 대안. 이것은 Entity Framework 6.2.0의 경우입니다.

특정 DbSet항목과 업데이트 또는 생성해야하는 항목이있는 경우 :

var name = getNameFromService();

var current = _dbContext.Names.Find(name.BusinessSystemId, name.NameNo);
if (current == null)
{
    _dbContext.Names.Add(name);
}
else
{
    _dbContext.Entry(current).CurrentValues.SetValues(name);
}
_dbContext.SaveChanges();

그러나 DbSet단일 기본 키 또는 복합 기본 키가 있는 일반에도 사용할 수 있습니다 .

var allNames = NameApiService.GetAllNames();
GenericAddOrUpdate(allNames, "BusinessSystemId", "NameNo");

public virtual void GenericAddOrUpdate<T>(IEnumerable<T> values, params string[] keyValues) where T : class
{
    foreach (var value in values)
    {
        try
        {
            var keyList = new List<object>();

            //Get key values from T entity based on keyValues property
            foreach (var keyValue in keyValues)
            {
                var propertyInfo = value.GetType().GetProperty(keyValue);
                var propertyValue = propertyInfo.GetValue(value);
                keyList.Add(propertyValue);
            }

            GenericAddOrUpdateDbSet(keyList, value);
            //Only use this when debugging to catch save exceptions
            //_dbContext.SaveChanges();
        }
        catch
        {
            throw;
        }
    }
    _dbContext.SaveChanges();
}

public virtual void GenericAddOrUpdateDbSet<T>(List<object> keyList, T value) where T : class
{
    //Get a DbSet of T type
    var someDbSet = Set(typeof(T));

    //Check if any value exists with the key values
    var current = someDbSet.Find(keyList.ToArray());
    if (current == null)
    {
        someDbSet.Add(value);
    }
    else
    {
        Entry(current).CurrentValues.SetValues(value);
    }
}

-1

수정

public static void InsertOrUpdateRange<T, T2>(this T entity, List<T2> updateEntity) 
        where T : class
        where T2 : class
        {
            foreach(var e in updateEntity)
            {
                context.Set<T2>().InsertOrUpdate(e);
            }
        }


        public static void InsertOrUpdate<T, T2>(this T entity, T2 updateEntity) 
        where T : class
        where T2 : class
        {
            if (context.Entry(updateEntity).State == EntityState.Detached)
            {
                if (context.Set<T2>().Any(t => t == updateEntity))
                {
                   context.Set<T2>().Update(updateEntity); 
                }
                else
                {
                    context.Set<T2>().Add(updateEntity);
                }

            }
            context.SaveChanges();
        }

2
다른 답변을 게시하는 대신 편집 을 사용하십시오
Suraj Rao
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.