Come aggiornare solo un campo usando Entity Framework?


190

Ecco il tavolo

utenti

UserId
UserName
Password
EmailAddress

e il codice ..

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

26
Con Password, vuoi dire la password hash, giusto? :-)
Edward Brey,

Risposte:


370

La risposta di Ladislav è stata aggiornata per utilizzare DbContext (introdotto in 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
Sono stato in grado di far funzionare questo codice solo aggiungendo db.Configuration.ValidateOnSaveEnabled = false; prima di db.SaveChanges ()?
Jake Drew l'

3
Quale spazio dei nomi includere db.Entry(user).Property(x => x.Password).IsModified = true;e non usaredb.Entry(user).Property("Password").IsModified = true;
Johan

5
Questo approccio genera un OptimisticConcurencyException quando la tabella ha un campo timestamp.
Maksim Vi.

9
Penso che valga la pena ricordare che se stai usando db.Configuration.ValidateOnSaveEnabled = false;potresti voler continuare a convalidare il campo che stai aggiornando:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Ziul

2
È necessario impostare ValidateOnSaveEnabled su false se nella tabella sono richiesti campi che non vengono forniti durante l'aggiornamento
Sal

54

Puoi dire a EF quali proprietà devono essere aggiornate in questo modo:

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 non è disponibile per DBContext
LoxLox

17

Hai sostanzialmente due opzioni:

  • andare fino in fondo fino in fondo, in quel caso, lo faresti
    • carica l'oggetto in base a quanto userIdfornito: l'intero oggetto viene caricato
    • aggiorna il passwordcampo
    • salvare l'oggetto indietro usando il .SaveChanges()metodo del contesto

In questo caso, spetta a EF come gestirlo in dettaglio. Ho appena provato questo, e nel caso cambio solo un singolo campo di un oggetto, ciò che EF crea è praticamente anche quello che creeresti manualmente - qualcosa del tipo:

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

Quindi EF è abbastanza intelligente da capire quali colonne sono effettivamente cambiate e creerà un'istruzione T-SQL per gestire solo gli aggiornamenti che sono effettivamente necessari.

  • tu definisci una procedura memorizzata che fa esattamente ciò di cui hai bisogno, nel codice T-SQL (aggiorna la Passwordcolonna per il dato UserIde nient'altro - praticamente esegue UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId) e crei una funzione di importazione per quella procedura memorizzata nel tuo modello EF e la chiami funzione invece di eseguire i passaggi descritti sopra

1
@ marc-s In realtà non devi caricare l'intero oggetto!
Arvand,

14

In Entity Framework Core, Attachrestituisce la voce, quindi tutto ciò che serve è:

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

12

sto usando questo:

entità:

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

codice accessorio:

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
Ottengo errori di convalida dell'entità quando provo questo, ma sembra sicuramente bello.
Devlord,

Questo metodo non funziona !!!: forse devi fornire maggiori dettagli su come usarlo !!! - questo è l'errore: "Collegamento di un'entità di tipo 'Domain.Job' non riuscito perché un'altra entità dello stesso tipo ha già lo stesso valore di chiave primaria. Ciò può accadere quando si utilizza il metodo 'Attach' o si imposta lo stato di un'entità su "Invariato" o "Modificato" se alcune entità nel grafico hanno valori chiave in conflitto. Ciò può essere dovuto al fatto che alcune entità sono nuove e non hanno ancora ricevuto valori chiave generati dal database. "
Lucian Bumb,

Perfec! Controlla la mia risposta per vedere l'approccio flessibile per qualsiasi modello!
Kryp

10

Durante la ricerca di una soluzione a questo problema, ho trovato una variazione sulla risposta di GONeale attraverso il blog di 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();
}

" Come puoi vedere, prende come secondo parametro un'espressione di una funzione. Ciò consentirà di utilizzare questo metodo specificando in un'espressione Lambda quale proprietà aggiornare. "

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

(Una soluzione in qualche modo simile è anche fornita qui: https://stackoverflow.com/a/5749469/2115384 )

Il metodo che sto attualmente usando nel mio codice , esteso per gestire anche (Linq) espressioni di tipo ExpressionType.Convert. Questo era necessario nel mio caso, ad esempio con Guide altre proprietà dell'oggetto. Quelli erano "racchiusi" in un Convert () e quindi non gestiti da 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 lo uso mi dà il seguente errore, Impossibile convertire l'espressione Lambda in Tipo 'Expression <Func <RequestDetail, object >> []' perché non è un tipo delegato
Imran Rizvi,

@ImranRizvi, devi semplicemente aggiornare i parametri a: aggiornamento int pubblico (entità T, espressione Params <Func <T, oggetto >> [] proprietà) NOTA la parola chiave params prima dell'espressione
dalcam

6

Sono in ritardo al gioco qui, ma è così che lo sto facendo, ho trascorso un po 'a cercare una soluzione di cui ero soddisfatto; questo produce una UPDATEdichiarazione SOLO per i campi che vengono modificati, poiché si definisce esplicitamente ciò che sono attraverso un concetto di "lista bianca" che è più sicuro per impedire comunque l'iniezione di moduli web.

Un estratto dal mio repository di dati 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;
}

Questo potrebbe essere racchiuso in un tentativo..catch se lo desideri, ma personalmente mi piace il mio chiamante per conoscere le eccezioni in questo scenario.

Sarebbe chiamato in questo modo (per me, questo era tramite un'API Web ASP.NET):

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

2
Quindi la tua soluzione migliore è quello che Elisa? Dovresti dichiarare esplicitamente quali proprietà permetti di aggiornare (proprio come la lista bianca richiesta per il UpdateModelcomando ASP.NET MVC ), in questo modo assicurati che l'iniezione del modulo hacker non possa avvenire e che non possano aggiornare i campi che non sono autorizzati ad aggiornare. Se tuttavia qualcuno può convertire l'array string in una sorta di parametro delle espressioni lambda e lavorare con esso nel Update<T>, fantastico
GONeale

3
@GONeale - Solo di passaggio. Qualcuno l' ha risolto usando Lambdas!
David Spence,

1
@Elisa Può essere migliorato usando Func <T, Elenco <oggetto>> invece di stringa []
Compagno Spongebob

Anche più tardi al gioco, e forse questa è una sintassi molto più recente, ma var entity=_context.Set<T>().Attach(item);seguita dal entity.Property(propertyName).IsModified = true;ciclo dovrebbe funzionare.
Auspex,

4

Entity framework tiene traccia delle modifiche sugli oggetti sottoposti a query dal database tramite DbContext. Ad esempio se il nome dell'istanza DbContext è dbContext

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

E come dovrebbe essere la vista in questo caso?
Emanuela Colta,

Questo è sbagliato perché salverebbe l'intero oggetto utente con la password modificata.
amuliar

questo è vero, ma il resto dell'oggetto Utente sarà lo stesso di prima nel contesto, l'unica cosa che potrebbe essere diversa è la password, quindi essenzialmente è solo l'aggiornamento della password.
Tomislav3008,

3

So che questo è un vecchio thread ma stavo anche cercando una soluzione simile e ho deciso di andare con la soluzione @ Doku-così fornita. Sto commentando per rispondere alla domanda posta da @Imran Rizvi, ho seguito il link @ Doku-so che mostra un'implementazione simile. La domanda di @Imran Rizvi era che stava ottenendo un errore usando la soluzione fornita "Impossibile convertire l'espressione Lambda in Tipo" Espressione> [] "perché non è un tipo delegato". Volevo offrire una piccola modifica che ho apportato alla soluzione di @ Doku-so che corregge questo errore nel caso in cui qualcun altro si imbattesse in questo post e decidesse di usare la soluzione di @ Doku-so.

Il problema è il secondo argomento nel metodo di aggiornamento,

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

Per chiamare questo metodo utilizzando la sintassi fornita ...

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

È necessario aggiungere la parola chiave 'params' davanti al secondo arugment in questo modo.

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

o se non si desidera modificare la firma del metodo, quindi per chiamare il metodo di aggiornamento è necessario aggiungere la parola chiave ' new ', specificare la dimensione dell'array, quindi utilizzare infine la sintassi dell'inizializzatore dell'oggetto di raccolta per ogni proprietà da aggiornare come visto sotto.

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

Nell'esempio di @ Doku-so sta specificando una matrice di espressioni, quindi è necessario passare le proprietà per l'aggiornamento in una matrice, a causa della matrice è necessario specificare anche la dimensione della matrice. Per evitare ciò, è anche possibile modificare l'argomento expression per utilizzare IEnumerable anziché un array.

Ecco la mia implementazione della soluzione di @ 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-così ha fornito un approccio interessante usando i generici, ho usato il concetto per risolvere il mio problema, ma non puoi usare la soluzione di @ Doku-so così com'è e sia in questo post che nel post collegato nessuno ha risposto alle domande sull'errore di utilizzo.


Stavo lavorando alla tua soluzione quando il programma passa alla riga entityEntry.State = EntityState.Unchanged;tutti i valori aggiornati nel parametro entityEntryvengono ripristinati, quindi non vengono salvate modifiche, puoi aiutarci, grazie
sairfan

3

In EntityFramework Core 2.x non è necessario Attach:

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

Ho provato questo in SQL Server e profilandolo:

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

Trova assicura che le entità già caricate non attivino un SELECT e inoltre allega automaticamente l'entità se necessario (dai documenti):

    ///     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 diversi suggerimenti, propongo quanto segue:

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

chiamato da

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

O di

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

O di

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

Come rendere questo metodo disponibile per altre classi, può essere come il metodo di estensione?
Velkumar,

in questo tutorial .NET CORE mostrano le migliori pratiche usando (il nuovo) EF Core per aggiornare proprietà specifiche in MVC. cerca "TryUpdateModelAsync".
Guy

1
@Guy Awesome. Tuttavia, ancora una volta la "best practice" di Microsoft è fare qualcosa di diverso da quello che i loro strumenti costruiscono ...
Auspex

Questa è una buona soluzione
Timothy Macharia,

1

Uso ValueInjecternuget per iniettare il modello vincolante nell'entità database usando quanto segue:

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

Si noti l'utilizzo della convenzione personalizzata che non aggiorna le proprietà se sono nulle dal server.

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

Cerca questa risposta

Avvertimento

Non saprai se la proprietà viene intenzionalmente cancellata su null OPPURE non aveva alcun valore. In altre parole, il valore della proprietà può essere sostituito solo con un altro valore ma non cancellato.


0

Stavo cercando lo stesso e finalmente ho trovato la soluzione

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

credimi, funziona per me come un incantesimo.


0

Questo è quello che uso, usando InjectNonNull personalizzato (obj dest, obj src) lo rende completamente flessibile

[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
Questo aggiungerà una nuova riga. La domanda è come aggiornarne una esistente.
Edward Brey,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.