Como atualizar apenas um campo usando o Entity Framework?


190

Aqui está a mesa

Comercial

UserId
UserName
Password
EmailAddress

e o código ..

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

26
Com Password, você quer dizer senha com hash, certo? :-)
Edward Brey

Respostas:


370

Resposta de Ladislav atualizada para usar o DbContext (introduzido no 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
Eu só consegui fazer esse código funcionar adicionando db.Configuration.ValidateOnSaveEnabled = false; antes de db.SaveChanges ()?
Jake de Drew

3
Que namespace para incluir a usar db.Entry(user).Property(x => x.Password).IsModified = true;e nãodb.Entry(user).Property("Password").IsModified = true;
Johan

5
Essa abordagem lança uma OptimisticConcurencyException quando a tabela possui um campo de carimbo de data / hora.
Maksim Vi.

9
Eu acho que vale a pena mencionar que, se você estiver usando, db.Configuration.ValidateOnSaveEnabled = false;poderá continuar validando o campo que está atualizando: #if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
221

2
Você precisa definir ValidateOnSaveEnabled como false se tiver exigido campos em sua tabela que você não está fornecendo durante a atualização.
Sal

54

Você pode informar ao EF quais propriedades devem ser atualizadas desta maneira:

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();
  }
}

ObjectStateManager não está disponível para DBContext
LoxLox

17

Você tem basicamente duas opções:

  • percorrer todo o caminho da EF, nesse caso, você faria
    • carregar o objeto com base no userIdfornecido - o objeto inteiro é carregado
    • atualize o passwordcampo
    • salve o objeto de volta usando o .SaveChanges()método do contexto

Nesse caso, cabe à EF como lidar com isso em detalhes. Acabei de testar isso e, no caso de alterar apenas um único campo de um objeto, o que o EF cria é basicamente o que você criaria manualmente também - algo como:

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

Portanto, a EF é inteligente o suficiente para descobrir quais colunas realmente mudaram e criará uma instrução T-SQL para lidar apenas com as atualizações necessárias.

  • você define um procedimento armazenado que faz exatamente o que precisa, no código T-SQL (basta atualizar a Passwordcoluna para o dado UserIde nada mais - basicamente é executado UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId) e cria uma importação de função para esse procedimento armazenado no seu modelo EF e você chama isso em vez de executar as etapas descritas acima

1
@ marc-s Na verdade, você não precisa carregar o objeto inteiro!
Arvand

14

No Entity Framework Core, Attachretorna a entrada, então tudo que você precisa é:

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

12

eu estou usando isso:

entidade:

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; }
}

código do acessador:

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
Eu recebo erros de validação de entidade quando tento isso, mas com certeza parece legal.
Devlord # /

Não funciona esse método !!!: talvez você precise fornecer mais detalhes de como usá-lo !!! - este é o erro: "Anexar uma entidade do tipo 'Domain.Job' falhou porque outra entidade do mesmo tipo já possui o mesmo valor de chave primária. Isso pode acontecer ao usar o método 'Attach' ou ao definir o estado de uma entidade para 'Inalterado' ou 'Modificado' se alguma entidade no gráfico tiver valores-chave conflitantes. Isso pode ocorrer porque algumas entidades são novas e ainda não receberam valores-chave gerados pelo banco de dados. "
Lucian Bumb

Perfec! Verifique minha resposta para ver uma abordagem flexível para qualquer modelo!
Kryp

10

Enquanto procurava uma solução para esse problema, encontrei uma variação na resposta de GONeale no blog de Patrick Desjardins :

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();
}

" Como você pode ver, assume como segundo parâmetro uma expressão de uma função. Isso permitirá usar esse método especificando em uma expressão Lambda qual propriedade atualizar. "

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

(Uma solução semelhante é também fornecida aqui: https://stackoverflow.com/a/5749469/2115384 )

O método que estou usando atualmente no meu próprio código , estendido para manipular também (Linq) expressões do tipo ExpressionType.Convert. Isso foi necessário no meu caso, por exemplo, com Guide outras propriedades do objeto. Esses foram 'agrupados' em um Convert () e, portanto, não são tratados por 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
Quando eu uso este que me dá seguinte erro, não é possível converter expressão Lambda para 'Expression <Func <RequestDetail, objeto >> []' Tipo, porque não é um tipo de delegado
Imran Rizvi

@ImranRizvi, você simplesmente precisa atualizar os parâmetros para: int público Update (entidade T, params Expression <Func <T, objeto >> [] propriedades) NOTA os params palavra-chave antes de expressão
dalcam

6

Estou atrasado para o jogo aqui, mas é assim que faço, passei um tempo procurando uma solução que me satisfizesse; isso produz umUPDATE APENAS declaração para os campos alterados, à medida que você define explicitamente o que eles são por meio de um conceito de "lista branca" que é mais seguro para impedir a injeção de formulários da Web de qualquer maneira.

Um trecho do meu repositório de dados 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;
}

Isso pode ser envolvido em uma tentativa ... captura, se você desejar, mas eu pessoalmente gosto que meu interlocutor saiba das exceções nesse cenário.

Seria chamado assim: (para mim, isso foi por meio de uma API da Web do ASP.NET):

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

2
Então, sua melhor solução é o que Elisa? Você deve declarar explicitamente quais propriedades você deseja atualizar (assim como a lista branca necessária para o UpdateModelcomando do ASP.NET MVC ), para garantir que a injeção de formulários de hackers não ocorra e que eles não possam atualizar os campos que não podem atualizar. Se, no entanto, alguém puder converter o array de strings para algum tipo de parâmetro de expressões lambda e trabalhar com ele no Update<T>, ótimo
GONeale

3
@ Goneale - Apenas de passagem. Alguém o quebrou usando Lambdas!
David Spence

1
@Elisa Ele pode ser aprimorado usando Func <T, List <object>> em vez de string []
Spongebob Camarada

Ainda mais tarde para o jogo, e talvez essa seja uma sintaxe muito mais recente, mas var entity=_context.Set<T>().Attach(item);seguida pelo entity.Property(propertyName).IsModified = true;loop deve funcionar.
Auspex

4

A estrutura de entidades rastreia suas alterações nos objetos que você consultou do banco de dados via DbContext. Por exemplo, se o nome da instância DbContext for dbContext

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

E como deve ser a visão neste caso?
Emanuela Colta

Isso está errado porque salvaria o objeto Usuário inteiro com a senha alterada.
amuliar

isso é verdade, mas o restante do objeto Usuário será o mesmo que anteriormente no contexto, a única coisa que possivelmente será diferente é a senha, portanto, essencialmente, ela só é atualizada.
Tomislav3008

3

Eu sei que esse é um tópico antigo, mas eu também estava procurando uma solução semelhante e decidi seguir com a solução @ Doku-so fornecida. Estou comentando para responder à pergunta feita por @Imran Rizvi, segui o link @ Doku-so, que mostra uma implementação semelhante. A pergunta de @Imran Rizvi foi que ele estava recebendo um erro usando a solução fornecida 'Não é possível converter a expressão Lambda para o tipo' Expressão> [] 'porque não é um tipo de delegado'. Eu queria oferecer uma pequena modificação que fiz na solução da @ Doku-so que corrige esse erro, caso alguém se depare com este post e decida usar a solução da @ Doku-so.

O problema é o segundo argumento no método Update,

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

Para chamar esse método usando a sintaxe fornecida ...

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

Você deve adicionar a palavra-chave 'params' na frente da segunda versão assim.

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

ou, se você não quiser alterar a assinatura do método, para chamar o método Update, adicione a palavra-chave ' new ', especifique o tamanho da matriz e, finalmente, use a sintaxe do inicializador do objeto de coleção para cada propriedade a ser atualizada, como visto abaixo.

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

No exemplo do @ Doku-so, ele está especificando uma matriz de Expressões, portanto você deve passar as propriedades para atualizar em uma matriz, por causa da matriz, você também deve especificar o tamanho da matriz. Para evitar isso, você também pode alterar o argumento da expressão para usar IEnumerable em vez de uma matriz.

Aqui está minha implementação da solução do @ 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();
    }

Uso:

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

@ Doku-so forneceu uma abordagem legal usando genéricos, usei o conceito para resolver meu problema, mas você simplesmente não pode usar a solução @ Doku-so como está e, tanto neste post como no post vinculado, ninguém respondeu às perguntas sobre erros de uso.


Eu estava trabalhando em sua solução quando passes programa de linha de entityEntry.State = EntityState.Unchanged;todos os valores atualizados em parâmetro entityEntryget revert, por isso há alterações são salvas, você pode por favor me ajude sobre ele, graças
sairfan

3

No EntityFramework Core 2.x, não há necessidade de Attach:

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

Tentei isso no SQL Server e crie um perfil:

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

O Find garante que as entidades já carregadas não acionem um SELECT e também anexa automaticamente a entidade, se necessário (nos documentos):

    ///     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

Combinando várias sugestões, proponho o seguinte:

    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;
        } 
    }

chamado por

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

Ou pela

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

Ou pela

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

Como disponibilizar esse método para outra classe, pode ser como o método de extensão?
Velkumar

no presente .NET NÚCLEO tutorial mostram melhores práticas usando (o novo) EF Core para propriedades de atualização específica em MVC. procure por 'TryUpdateModelAsync'.
12136 Guy

1
@Guy Awesome. Porém, mais uma vez "melhores práticas" da Microsoft é fazer algo diferente do que suas ferramentas de construção ...
Auspex

Esta é uma boa solução.
Timothy Macharia

1

Eu uso o ValueInjecternuget para injetar o Modelo de Ligação na Entidade de banco de dados usando o seguinte:

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();
}

Observe o uso da convenção personalizada que não atualiza as Propriedades se elas forem nulas do servidor.

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);
    }
}

Uso:

target.InjectFrom<NoNullsInjection>(source);

Value Injecter V2

Procure esta resposta

Embargo

Você não saberá se a propriedade foi intencionalmente limpa para nula OU se simplesmente não tinha nenhum valor. Em outras palavras, o valor da propriedade só pode ser substituído por outro valor, mas não limpo.


0

Eu estava procurando o mesmo e finalmente encontrei a solução

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

acredite em mim, funciona para mim como um encanto.


0

É isso que eu uso, usando o InjectNonNull personalizado (obj dest, obj src), que o torna totalmente flexível

[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
Isso adicionará uma nova linha. A questão é como atualizar um existente.
Edward Brey
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.