Entity Framework를 사용하여 하나의 필드 만 업데이트하는 방법은 무엇입니까?


189

여기 테이블이 있습니다

사용자

UserId
UserName
Password
EmailAddress

그리고 코드 ..

public void ChangePassword(int userId, string password){
//code to update the password..
}

26
하여 Password, 당신은 바로, 해시 된 암호를 의미? :-)
Edward Brey

답변:


370

Ladislav의 답변은 DbContext를 사용하도록 업데이트되었습니다 (EF 4.1에서 도입).

public void ChangePassword(int userId, string password)
{
  var user = new User() { Id = userId, Password = password };
  using (var db = new MyEfContextName())
  {
    db.Users.Attach(user);
    db.Entry(user).Property(x => x.Password).IsModified = true;
    db.SaveChanges();
  }
}

55
db.Configuration.ValidateOnSaveEnabled = false를 추가해야만이 코드를 작동시킬 수있었습니다. db.SaveChanges () 전에?
Jake Drew

3
사용 db.Entry(user).Property(x => x.Password).IsModified = true;하지 않을 네임 스페이스db.Entry(user).Property("Password").IsModified = true;
Johan

5
이 방법은 테이블에 타임 스탬프 필드가있는 경우 OptimisticConcurencyException을 발생시킵니다.
Maksim Vi.

9
나는 당신이 사용하고 있다면 db.Configuration.ValidateOnSaveEnabled = false;당신이 업데이트하고있는 필드를 계속 검증하고 싶을 수도 있다고 언급 할 가치가 있다고 생각합니다 .if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Ziul

2
업데이트 중에 제공하지 않는 테이블에 필드가 필요한 경우 ValidateOnSaveEnabled를 false로 설정해야합니다.
Sal

54

다음과 같은 방법으로 EF에 어떤 속성을 업데이트해야하는지 알 수 있습니다.

public void ChangePassword(int userId, string password)
{
  var user = new User { Id = userId, Password = password };
  using (var context = new ObjectContext(ConnectionString))
  {
    var users = context.CreateObjectSet<User>();
    users.Attach(user);
    context.ObjectStateManager.GetObjectStateEntry(user)
      .SetModifiedProperty("Password");
    context.SaveChanges();
  }
}


17

기본적으로 두 가지 옵션이 있습니다.

  • 끝까지 EF로 가십시오.
    • userId제공된 것을 기준으로 객체를로드-전체 객체가로드됩니다.
    • password필드를 업데이트
    • 컨텍스트 .SaveChanges()메소드를 사용하여 오브젝트를 다시 저장하십시오.

이 경우이를 자세히 처리하는 방법은 EF에 달려 있습니다. 방금 이것을 테스트했으며 객체의 단일 필드 만 변경하는 경우 EF가 만드는 것은 수동으로 만드는 것입니다.

`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`

따라서 EF는 실제로 변경된 열을 파악할 수있을 정도로 똑똑하며 실제로 필요한 업데이트 만 처리하기 위해 T-SQL 문을 만듭니다.

  • T-SQL 코드에서 필요한 것을 정확하게 수행하는 저장 프로 시저를 정의하고 ( Password주어진 열을 업데이트하고 UserId다른 것은 기본적으로 실행하지 않음 UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId) EF 모델에서 해당 저장 프로 시저에 대한 함수 가져 오기를 작성하고 이것을 호출합니다. 위에서 설명한 단계를 수행하는 대신 기능

1
@ marc-s 실제로 전체 객체를로드 할 필요는 없습니다!
Arvand

14

Entity Framework Core Attach에서 항목을 반환하므로 필요한 것은 다음과 같습니다.

var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();

12

나는 이것을 사용하고있다 :

실재:

public class Thing 
{
    [Key]
    public int Id { get; set; }
    public string Info { get; set; }
    public string OtherStuff { get; set; }
}

dbcontext :

public class MyDataContext : DbContext
{
    public DbSet<Thing > Things { get; set; }
}

접근 자 코드 :

MyDataContext ctx = new MyDataContext();

// FIRST create a blank object
Thing thing = ctx.Things.Create();

// SECOND set the ID
thing.Id = id;

// THIRD attach the thing (id is not marked as modified)
db.Things.Attach(thing); 

// FOURTH set the fields you want updated.
thing.OtherStuff = "only want this field updated.";

// FIFTH save that thing
db.SaveChanges();

1
이것을 시도 할 때 엔터티 유효성 검사 오류가 발생하지만 멋지게 보입니다.
devlord

이 방법으로 작동하지 않습니다! -이것은 오류입니다. "같은 유형의 다른 엔터티에 이미 기본 키 값이 동일하기 때문에 'Domain.Job'유형의 엔터티 연결에 실패했습니다. 'Attach'방법을 사용하거나 엔터티의 상태를 설정할 때 발생할 수 있습니다. 그래프의 엔티티에 충돌하는 키 값이있는 경우 '변경되지 않음'또는 '수정 됨'으로 변경하십시오. 이는 일부 엔티티가 새롭고 아직 데이터베이스 생성 키 값을 수신하지 않았기 때문일 수 있습니다. "
Lucian Bumb

완벽 해! 모든 모델에 대한 유연한 접근 방식을 보려면 내 답변을 확인하십시오!
kryp December

10

이 문제에 대한 해결책을 찾는 동안 Patrick Desjardins의 블로그를 통해 GONeale의 답변에서 변형이 발견되었습니다 .

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
  DatabaseContext.Entry(entity).State = EntityState.Unchanged;
  foreach (var property in properties)
  {
    var propertyName = ExpressionHelper.GetExpressionText(property);
    DatabaseContext.Entry(entity).Property(propertyName).IsModified = true;
  }
  return DatabaseContext.SaveChangesWithoutValidation();
}

" 보시다시피, 두 번째 매개 변수로 함수의 표현식을 취합니다. 그러면 업데이트 할 특성을 Lambda 표현식에 지정하여이 메소드를 사용할 수 있습니다. "

...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);

( https://stackoverflow.com/a/5749469/2115384 ) 다소 비슷한 솔루션이 제공됩니다.

현재 내 코드에서 사용하고있는 메소드 는 (Linq) 유형의 표현식도 처리하도록 확장되었습니다 ExpressionType.Convert. 이것은 필자의 경우 Guid와 다른 객체 속성 과 함께 필요했습니다 . 그것들은 Convert ()에 '포장'되어 처리되지 않았습니다 System.Web.Mvc.ExpressionHelper.GetExpressionText.

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
    DbEntityEntry<T> entry = dataContext.Entry(entity);
    entry.State = EntityState.Unchanged;
    foreach (var property in properties)
    {
        string propertyName = "";
        Expression bodyExpression = property.Body;
        if (bodyExpression.NodeType == ExpressionType.Convert && bodyExpression is UnaryExpression)
        {
            Expression operand = ((UnaryExpression)property.Body).Operand;
            propertyName = ((MemberExpression)operand).Member.Name;
        }
        else
        {
            propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
        }
        entry.Property(propertyName).IsModified = true;
    }

    dataContext.Configuration.ValidateOnSaveEnabled = false;
    return dataContext.SaveChanges();
}

1
이것을 사용할 때 다음과 같은 오류가 발생합니다. Lambda 식을 대리자 형식이 아니기 때문에 'Expression <Func <RequestDetail, object >> []'형식으로 변환 할 수 없습니다
Imran Rizvi

@ImranRizvi, 당신은 단순히 다음과 같이 매개 변수를 업데이트해야합니다 : public int Update (T entity, params Expression <Func <T, object >> [] properties) NOTE 표현식 전에 params 키워드
dalcam

6

나는 여기서 게임에 늦었지만, 이것이 내가하는 일이다. 이렇게하면 UPDATE웹 양식 삽입을 방지하기 위해보다 안전한 "화이트리스트"개념을 통해 필드를 명시 적으로 정의하므로 변경된 필드에 대해서만 명령문 이 생성 됩니다.

내 ISession 데이터 저장소에서 발췌 한 내용 :

public bool Update<T>(T item, params string[] changedPropertyNames) where T 
  : class, new()
{
    _context.Set<T>().Attach(item);
    foreach (var propertyName in changedPropertyNames)
    {
        // If we can't find the property, this line wil throw an exception, 
        //which is good as we want to know about it
        _context.Entry(item).Property(propertyName).IsModified = true;
    }
    return true;
}

원하는 경우 try..catch로 묶을 수 있지만이 시나리오의 예외에 대해 발신자가 개인적으로 알고 싶습니다.

다음과 같은 방식으로 호출됩니다 (나에게 이것은 ASP.NET 웹 API를 통해 이루어졌습니다).

if (!session.Update(franchiseViewModel.Franchise, new[]
    {
      "Name",
      "StartDate"
  }))
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));

2
그래서 당신의 더 나은 해결책은 엘리사 무엇입니까? ASP.NET MVC UpdateModel명령에 필요한 화이트리스트와 같이 업데이트 할 수있는 속성을 명시 적으로 명시해야합니다. 이렇게하면 해커 양식 삽입이 발생하지 않고 업데이트 할 수없는 필드를 업데이트 할 수 없습니다. 그러나 누군가가 문자열 배열을 일종의 람다 식 매개 변수로 변환하고 그와 함께 사용할 수 있다면 Update<T>위대한
GONeale

3
@GONeale-그냥지나갑니다. 누군가 Lambdas를 사용하여 금이갔습니다!
David Spence

1
@Elisa string [] 대신 Func <T, List <object >>를 사용하여 향상시킬 수 있습니다.
Spongebob Comrade

심지어 게임 후반에, 아마도 이것은 훨씬 더 최근의 구문이지만 , 루프에서 var entity=_context.Set<T>().Attach(item);계속 entity.Property(propertyName).IsModified = true;작동합니다.
Auspex

4

엔티티 프레임 워크는 DbContext를 통해 데이터베이스에서 조회 한 오브젝트의 변경 사항을 추적합니다. 예를 들어 DbContext 인스턴스 이름이 dbContext 인 경우

public void ChangePassword(int userId, string password){
     var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
     user.password = password;
     dbContext.SaveChanges();
}

이 경우 뷰는 어떻게 보입니까?
Emanuela Colta

암호가 변경된 사용자 개체 전체를 저장하기 때문에 이것은 잘못된 것입니다.
amuliar

즉, 나머지 User 개체는 이전 컨텍스트와 동일하지만 암호 만 다르므로 본질적으로 암호 만 업데이트됩니다.
Tomislav3008

3

나는 이것이 오래된 스레드라는 것을 알고 있지만 비슷한 솔루션을 찾고 있었고 @ Doku-so 제공 솔루션으로 가기로 결정했습니다. @Imran Rizvi가 묻는 질문에 대답하기 위해 댓글을 달고 있으며 유사한 구현을 보여주는 @ Doku-so 링크를 따랐습니다. @Imran Rizvi의 질문은 제공된 솔루션 'Landda식이 대리자 형식이 아니기 때문에 Lambda 식을'Expression> [] '유형으로 변환 할 수 없습니다. 다른 사람 이이 게시물을 발견하고 @ Doku-so의 솔루션을 사용하기로 결정한 경우이 오류를 수정하는 @ Doku-so의 솔루션에 작은 수정을 제공하고 싶었습니다.

문제는 Update 메서드의 두 번째 인수입니다.

public int Update(T entity, Expression<Func<T, object>>[] properties). 

제공된 구문을 사용하여이 메소드를 호출하려면 ...

Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn); 

두 번째 arugment 앞에 'params'키워드를 추가해야합니다.

public int Update(T entity, params Expression<Func<T, object>>[] properties)

또는 메소드 서명을 변경하지 않으려는 경우 Update 메소드를 호출하려면 ' new '키워드 를 추가 하고 배열의 크기를 지정한 다음 마지막으로 표시된대로 각 특성에 대해 콜렉션 오브젝트 이니셜 라이저 구문을 사용하십시오 이하.

Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });

@ Doku-so의 예에서 그는 Expressions 배열을 지정하므로 배열로 업데이트 할 속성을 전달해야합니다. 배열 때문에 배열의 크기도 지정해야합니다. 이를 피하기 위해 배열 대신 IEnumerable을 사용하도록 expression 인수를 변경할 수도 있습니다.

다음은 @ Doku-so의 솔루션 구현입니다.

public int Update<TEntity>(LcmsEntities dataContext, DbEntityEntry<TEntity> entityEntry, params Expression<Func<TEntity, object>>[] properties)
     where TEntity: class
    {
        entityEntry.State = System.Data.Entity.EntityState.Unchanged;

        properties.ToList()
            .ForEach((property) =>
            {
                var propertyName = string.Empty;
                var bodyExpression = property.Body;
                if (bodyExpression.NodeType == ExpressionType.Convert
                    && bodyExpression is UnaryExpression)
                {
                    Expression operand = ((UnaryExpression)property.Body).Operand;
                    propertyName = ((MemberExpression)operand).Member.Name;
                }
                else
                {
                    propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
                }

                entityEntry.Property(propertyName).IsModified = true;
            });

        dataContext.Configuration.ValidateOnSaveEnabled = false;

        return dataContext.SaveChanges();
    }

용법:

this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId);

@ Doku-so는 generic을 사용하여 멋진 접근 방식을 제공했지만 개념을 사용하여 문제를 해결했지만 @ Doku-so의 솔루션을 그대로 사용할 수 없으며이 게시물과 링크 된 게시물 모두에서 아무도 사용 오류 질문에 대답하지 못했습니다.


프로그램이 entityEntry.State = EntityState.Unchanged;매개 변수의 모든 업데이트 된 값을 entityEntry되돌릴 때 솔루션을 작업 중이므로 변경 사항이 저장되지 않으므로 도움을 줄 수 있습니까
sairfan

3

EntityFramework Core 2.x에서는 다음이 필요하지 않습니다 Attach.

 // get a tracked entity
 var entity = context.User.Find(userId);
 entity.someProp = someValue;
 // other property changes might come here
 context.SaveChanges();

이것을 SQL Server에서 시도하고 프로파일 링했습니다.

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [User] SET [someProp] = @p0
WHERE [UserId] = @p1;
SELECT @@ROWCOUNT;

',N'@p1 int,@p0 bit',@p1=1223424,@p0=1

찾기는 이미로드 된 엔티티가 SELECT를 트리거하지 않도록하고 필요한 경우 (문서에서) 엔티티를 자동으로 첨부합니다.

    ///     Finds an entity with the given primary key values. If an entity with the given primary key values
    ///     is being tracked by the context, then it is returned immediately without making a request to the
    ///     database. Otherwise, a query is made to the database for an entity with the given primary key values
    ///     and this entity, if found, is attached to the context and returned. If no entity is found, then
    ///     null is returned.

1

몇 가지 제안을 결합하여 다음을 제안합니다.

    async Task<bool> UpdateDbEntryAsync<T>(T entity, params Expression<Func<T, object>>[] properties) where T : class
    {
        try
        {
            var entry = db.Entry(entity);
            db.Set<T>().Attach(entity);
            foreach (var property in properties)
                entry.Property(property).IsModified = true;
            await db.SaveChangesAsync();
            return true;
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("UpdateDbEntryAsync exception: " + ex.Message);
            return false;
        } 
    }

에 의해 호출

UpdateDbEntryAsync(dbc, d => d.Property1);//, d => d.Property2, d => d.Property3, etc. etc.);

또는

await UpdateDbEntryAsync(dbc, d => d.Property1);

또는

bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;

이 방법을 다른 클래스에서 사용 가능하게 만드는 방법은 확장 방법과 같을 수 있습니까?
Velkumar

에서 이 .NET CORE는 개별지도 가 MVC에서 업데이트 특정 속성에 (새로운) EF 코어를 사용하는 가장 좋은 방법을 보여줍니다. 'TryUpdateModelAsync'를 찾으십시오.
Guy

1
@Guy 최고입니다. 그러나 Microsoft의 "모범 사례"는 도구가 구축 한 것 이외의 작업을 수행하는 것입니다.
Auspex

이것은 좋은 해결책입니다.
디모데 마카 리아

1

내가 사용하는 ValueInjecter다음과 같은 사용하여 데이터베이스 엔티티로 모델을 바인딩 주입 nuget :

public async Task<IHttpActionResult> Add(CustomBindingModel model)
{
   var entity= await db.MyEntities.FindAsync(model.Id);
   if (entity== null) return NotFound();

   entity.InjectFrom<NoNullsInjection>(model);

   await db.SaveChangesAsync();
   return Ok();
}

서버에서 속성이 null 인 경우 속성을 업데이트하지 않는 사용자 지정 규칙 사용에 유의하십시오.

ValueInjecter v3 +

public class NoNullsInjection : LoopInjection
{
    protected override void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp)
    {
        if (sp.GetValue(source) == null) return;
        base.SetValue(source, target, sp, tp);
    }
}

용법:

target.InjectFrom<NoNullsInjection>(source);

가치 인젝터 V2

이 답변을 찾아보십시오

경고

속성이 의도적으로 null로 지워 졌는지 또는 값이 없는지 알 수 없습니다. 즉, 속성 값은 다른 값으로 만 바꿀 수 있지만 지울 수는 없습니다.


0

나는 똑같은 것을 찾고 마침내 해결책을 찾았다.

using (CString conn = new CString())
{
    USER user = conn.USERs.Find(CMN.CurrentUser.ID);
    user.PASSWORD = txtPass.Text;
    conn.SaveChanges();
}

나를 위해 매력처럼 작동합니다.


0

이것은 내가 사용하는 것입니다, 사용자 정의 InjectNonNull (obj dest, obj src)을 사용하여 완전히 유연하게 만듭니다.

[HttpPost]
public async Task<IActionResult> Post( [FromQuery]Models.Currency currency ) {
  if ( ModelState.IsValid ) {
    // find existing object by Key
    Models.Currency currencyDest = context.Currencies.Find( currency.Id ); 

    context.Currencies.Attach( currencyDest );

    // update only not null fields
    InjectNonNull( currencyDest, currency );

    // save
    await context.SaveChangesAsync( );
  }  
  return Ok();
}

// Custom method
public static T InjectNonNull<T>( T dest, T src ) {
  foreach ( var propertyPair in PropertyLister<T, T>.PropertyMap ) {
    var fromValue = propertyPair.Item2.GetValue( src, null );
    if ( fromValue != null && propertyPair.Item1.CanWrite ) {
       propertyPair.Item1.SetValue( dest, fromValue, null );
    }
  }
  return dest;
}

-1
public async Task<bool> UpdateDbEntryAsync(TEntity entity, params Expression<Func<TEntity, object>>[] properties)
{
    try
    {
        this.Context.Set<TEntity>().Attach(entity);
        EntityEntry<TEntity> entry = this.Context.Entry(entity);
        entry.State = EntityState.Modified;
        foreach (var property in properties)
            entry.Property(property).IsModified = true;
        await this.Context.SaveChangesAsync();
        return true;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

-7
public void ChangePassword(int userId, string password)
{
  var user = new User{ Id = userId, Password = password };
  using (var db = new DbContextName())
  {
    db.Entry(user).State = EntityState.Added;
    db.SaveChanges();
  }
}

1
새로운 행이 추가됩니다. 문제는 기존 업데이트 방법입니다.
Edward Brey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.