ti-enxame.com

Como atualizar apenas um campo usando o Entity Framework?

Aqui é a mesa

suários

UserId
UserName
Password
EmailAddress

e o código ..

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

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

Você pode dizer ao EF quais propriedades precisam 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();
  }
}
52
Ladislav Mrnka

Você tem basicamente duas opções:

  • vá pela EF até o fim, nesse caso, você faria
    • carrega o objeto com base no userId fornecido - o objeto inteiro é carregado
    • atualize o campo password
    • salve o objeto de volta usando o método .SaveChanges() do contexto

Neste caso, cabe à EF como lidar com isso em detalhes. Eu acabei de testar isso, e no caso eu apenas mudo um único campo de um objeto, o que o EF cria é basicamente o que você criaria manualmente - algo como:

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

Portanto, a EF é inteligente o bastante para descobrir quais colunas realmente foram alteradas 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 você precisa, em código T-SQL (apenas atualiza a coluna Password para o dado UserId e nada mais - basicamente executa UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId) e você cria uma função importada para aquele procedimento armazenado em seu modelo EF e você chama essa função em vez de executar as etapas descritas acima
15
marc_s

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 de acesso:

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();
10
groggyjava

Enquanto procurava uma solução para este problema, encontrei uma variação na resposta da GONeale através de 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, toma como segundo parâmetro uma expressão de uma função. Isso permitirá usar este método especificando em uma expressão Lambda qual propriedade será atualizada. "

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

(Uma solução semelhante também é dada 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 Guid e outras propriedades do objeto. Aqueles foram "envolvidos" em um Convert () e, portanto, não manipulados 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();
}
8
Doku-so

Estou atrasado para o jogo aqui, mas é assim que estou fazendo isso, passei algum tempo procurando uma solução que me satisfizesse; isso produz uma instrução UPDATE APENAS para os campos que são alterados, já que você define explicitamente o que eles são através de um conceito de "lista branca" que é mais seguro para evitar 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 poderia ser embrulhado em um try..catch se você assim o desejasse, mas eu pessoalmente gosto do meu chamador para saber sobre as exceções neste cenário.

Seria chamado de algo assim (para mim, isso foi feito através de uma API da Web ASP.NET):

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

No Entity Framework Core, Attach retorna 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();
6
Edward Brey

Eu sei que este é um segmento antigo, mas eu também estava procurando por uma solução semelhante e decidiu ir com a solução @ Doku-tão fornecido. Estou comentando para responder a pergunta feita por @Imran Rizvi, segui @ Doku-so link que mostra uma implementação semelhante. A pergunta de @Iran Rizvi foi que ele estava recebendo um erro usando a solução fornecida 'Não é possível converter a expressão Lambda para Tipo' Expression> [] 'porque não é um tipo de delegado'. Eu queria oferecer uma pequena modificação que fiz à solução do @Doku-so que corrige este erro no caso de alguém se deparar com este post e decidir usar a solução do @Doku-so.

A questão é 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 do segundo arugment como tal.

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

ou se você não quer mudar a assinatura do método, então para chamar o método Update você precisa adicionar o ' new 'palavra-chave, especifique o tamanho da matriz e, finalmente, use a sintaxe do inicializador do objeto de coleção para cada propriedade para atualizar conforme mostrado 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, então 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á a 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-tão fornecido uma abordagem legal usando genéricos, eu usei o conceito para resolver o meu problema, mas você simplesmente não pode usar a solução @ Doku-so como é e tanto neste post e no post vinculado ninguém respondeu as perguntas de erro de uso.

3
null

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

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

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 perfilá-lo:

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

Localizar garante que as entidades já carregadas não acionem um SELECT e também anexem automaticamente a entidade, se necessário (a partir dos 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
Alexei

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.

1
Burhan Ul Haqq Zahir

Eu uso o ValueInjecter nuget para injetar o Binding Model na Entidade do 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 no 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);

Valor Injecter V2

Lookup esta resposta

Embargo

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

1
Korayem

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 por

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

Ou por

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