Wie aktualisiere ich nur ein Feld mit Entity Framework?


188

Hier ist die Tabelle

Benutzer

UserId
UserName
Password
EmailAddress

und der Code ..

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

26
Mit Passwordmeinst du gehashtes Passwort, oder? :-)
Edward Brey

Antworten:


368

Ladislavs Antwort wurde aktualisiert, um DbContext zu verwenden (eingeführt 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
Ich konnte diesen Code nur durch Hinzufügen von db.Configuration.ValidateOnSaveEnabled = false zum Laufen bringen. vor db.SaveChanges ()?
Jake Drew

3
Welcher Namespace soll verwendet werden db.Entry(user).Property(x => x.Password).IsModified = true;und nichtdb.Entry(user).Property("Password").IsModified = true;
Johan

5
Dieser Ansatz löst eine OptimisticConcurencyException aus, wenn die Tabelle ein Zeitstempelfeld enthält.
Maksim Vi.

9
Ich denke, es ist erwähnenswert, dass Sie, wenn Sie verwenden db.Configuration.ValidateOnSaveEnabled = false;, das Feld, das Sie aktualisieren, möglicherweise weiter validieren möchten:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Ziul

2
Sie müssen ValidateOnSaveEnabled auf false setzen, wenn Sie Felder in Ihrer Tabelle benötigt haben, die Sie während Ihres Updates nicht bereitstellen
Sal

54

Sie können EF auf folgende Weise mitteilen, welche Eigenschaften aktualisiert werden müssen:

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 ist für DBContext
LoxLox

16

Sie haben grundsätzlich zwei Möglichkeiten:

  • Gehen Sie den EF-Weg den ganzen Weg, in diesem Fall würden Sie
    • Laden Sie das Objekt basierend auf dem userIdbereitgestellten - das gesamte Objekt wird geladen
    • Aktualisieren Sie das passwordFeld
    • speichert das Objekt wieder im Kontext des mit .SaveChanges()Methode

In diesem Fall liegt es an EF, wie dies im Detail behandelt wird. Ich habe dies gerade getestet und für den Fall, dass ich nur ein einzelnes Feld eines Objekts ändere, ist das, was EF erstellt, so ziemlich das, was Sie auch manuell erstellen würden - so etwas wie:

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

EF ist also klug genug, um herauszufinden, welche Spalten sich tatsächlich geändert haben, und es wird eine T-SQL-Anweisung erstellt, um nur die Aktualisierungen zu verarbeiten, die tatsächlich erforderlich sind.

  • Sie definieren eine gespeicherte Prozedur, die genau das tut, was Sie benötigen, im T-SQL-Code (aktualisieren Sie einfach die PasswordSpalte für die angegebene UserIdund nichts anderes - wird im Grunde ausgeführt UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId) und erstellen einen Funktionsimport für diese gespeicherte Prozedur in Ihrem EF-Modell und rufen dies auf funktionieren, anstatt die oben beschriebenen Schritte auszuführen

1
@ marc-s Eigentlich musst du nicht das ganze Objekt laden!
Arvand

13

Gibt in Entity Framework Core Attachden Eintrag zurück. Sie benötigen also nur Folgendes:

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

12

Ich benutze dies:

Entität:

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

Zugangscode:

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
Ich erhalte Entitätsüberprüfungsfehler, wenn ich dies versuche, aber es sieht auf jeden Fall cool aus.
Devlord

Funktioniert diese Methode nicht !!!: Vielleicht müssen Sie weitere Details zur Verwendung angeben !!! - Dies ist der Fehler: "Das Anhängen einer Entität vom Typ 'Domain.Job' ist fehlgeschlagen, da eine andere Entität desselben Typs bereits denselben Primärschlüsselwert hat. Dies kann passieren, wenn Sie die Methode 'Anhängen' verwenden oder den Status einer Entität festlegen auf 'Unverändert' oder 'Geändert', wenn Entitäten im Diagramm widersprüchliche Schlüsselwerte aufweisen. Dies kann daran liegen, dass einige Entitäten neu sind und noch keine von der Datenbank generierten Schlüsselwerte erhalten haben. "
Lucian Bumb

Perfec! Überprüfen Sie meine Antwort, um einen flexiblen Ansatz für jedes Modell zu sehen!
Kryp

10

Auf der Suche nach einer Lösung für dieses Problem habe ich im Blog von Patrick Desjardins eine Variation von GONeales Antwort gefunden :

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

" Wie Sie sehen können, wird als zweiter Parameter ein Ausdruck einer Funktion verwendet. Dadurch können Sie diese Methode verwenden, indem Sie in einem Lambda-Ausdruck angeben, welche Eigenschaft aktualisiert werden soll. "

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

(Eine etwas ähnliche Lösung finden Sie auch hier: https://stackoverflow.com/a/5749469/2115384 )

Die Methode, die ich derzeit in meinem eigenen Code verwende , wurde erweitert, um auch (Linq) Typausdrücke zu verarbeiten ExpressionType.Convert. Dies war in meinem Fall beispielsweise bei Guidund anderen Objekteigenschaften erforderlich . Diese wurden in ein Convert () 'eingewickelt' und daher nicht von verarbeitet 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
Wenn ich dies verwende, wird folgende Fehlermeldung angezeigt: Der Lambda-Ausdruck kann nicht in den Typ 'Ausdruck <Func <RequestDetail, Objekt >> []' konvertiert werden, da es sich nicht um einen Delegatentyp handelt
Imran Rizvi,

@ImranRizvi, Sie müssen lediglich die Parameter aktualisieren auf: public int Update (T-Entität, Parameter Ausdruck <Func <T, Objekt >> [] Eigenschaften) HINWEIS das Schlüsselwort params vor Ausdruck
dalcam

6

Ich bin zu spät zum Spiel hier, aber so mache ich es. Ich habe eine Weile nach einer Lösung gesucht, mit der ich zufrieden war. Dies erzeugt UPDATENUR eine Anweisung für die Felder, die geändert werden, da Sie explizit definieren, was sie sind, und zwar durch ein "White List" -Konzept, das sicherer ist, um das Einfügen von Webformularen ohnehin zu verhindern.

Ein Auszug aus meinem ISession-Datenrepository:

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

Dies könnte in einem Versuch verpackt werden. Wenn Sie dies wünschen, möchte ich persönlich, dass mein Anrufer über die Ausnahmen in diesem Szenario informiert wird.

Es würde in etwa so aufgerufen (für mich über eine ASP.NET-Web-API):

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

2
Ihre bessere Lösung ist also, was Elisa? Sie sollten explizit angeben, welche Eigenschaften aktualisiert werden dürfen (genau wie die für den UpdateModelBefehl von ASP.NET MVC erforderliche Whitelist ). Auf diese Weise stellen Sie sicher, dass keine Hacker-Formularinjektion durchgeführt werden kann und keine Felder aktualisiert werden können, die nicht aktualisiert werden dürfen. Wenn aber jemand die String - Array zu irgendeiner Art von Lambda - Ausdrücke Parameter und die Arbeit mit ihm in der umwandeln kann Update<T>, groß
GONeale

3
@ Goneale - Nur auf der Durchreise. Jemand hat es mit Lambdas geknackt!
David Spence

1
@Elisa Es kann verbessert werden, indem Func <T, List <Objekt>> anstelle von Zeichenfolge [] verwendet wird
SpongeBob-Genosse

Auch später im Spiel, und vielleicht ist dies eine viel neuere Syntax, aber var entity=_context.Set<T>().Attach(item);gefolgt von entity.Property(propertyName).IsModified = true;in der Schleife sollte funktionieren.
Auspex

4

Das Entity Framework verfolgt Ihre Änderungen an Objekten, die Sie über DbContext aus der Datenbank abgefragt haben. Zum Beispiel, wenn Ihr DbContext-Instanzname dbContext ist

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

Und wie soll die Ansicht in diesem Fall aussehen?
Emanuela Colta

Dies ist falsch, da dadurch das gesamte Benutzerobjekt mit geändertem Kennwort gespeichert wird.
eigenartig

Das ist wahr, aber der Rest des Benutzerobjekts ist derselbe wie zuvor im Kontext. Das einzige, was möglicherweise anders sein wird, ist das Kennwort, sodass es im Wesentlichen nur das Kennwort aktualisiert.
Tomislav3008

3

Ich weiß, dass dies ein alter Thread ist, aber ich suchte auch nach einer ähnlichen Lösung und entschied mich für die Lösung @ Doku-so. Ich kommentiere, um die von @Imran Rizvi gestellte Frage zu beantworten. Ich folgte dem Link @ Doku-so, der eine ähnliche Implementierung zeigt. @ Imran Rizvis Frage war, dass er mit der bereitgestellten Lösung 'Lambda-Ausdruck kann nicht in Typ' Ausdruck> [] 'konvertiert werden, da es sich nicht um einen Delegatentyp handelt' einen Fehler erhalten hat. Ich wollte eine kleine Änderung anbieten, die ich an der Lösung von @ Doku-so vorgenommen habe, um diesen Fehler zu beheben, falls jemand anderes auf diesen Beitrag stößt und sich für die Verwendung der Lösung von @ Doku-so entscheidet.

Das Problem ist das zweite Argument in der Update-Methode.

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

So rufen Sie diese Methode mit der angegebenen Syntax auf ...

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

Sie müssen das Schlüsselwort 'params' als solches vor dem zweiten Arugment einfügen.

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

Wenn Sie die Methodensignatur nicht ändern möchten, müssen Sie zum Aufrufen der Update-Methode das Schlüsselwort ' new ' hinzufügen , die Größe des Arrays angeben und schließlich die Syntax für die Initialisierung des Sammlungsobjekts für jede Eigenschaft verwenden, die wie angezeigt aktualisiert werden soll unten.

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

Im Beispiel von @ Doku-so gibt er ein Array von Ausdrücken an, sodass Sie die zu aktualisierenden Eigenschaften in einem Array übergeben müssen. Aufgrund des Arrays müssen Sie auch die Größe des Arrays angeben. Um dies zu vermeiden, können Sie auch das Ausdrucksargument so ändern, dass IEnumerable anstelle eines Arrays verwendet wird.

Hier ist meine Implementierung der @ Doku-so-Lösung.

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

Verwendung:

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

@ Doku-so bot einen coolen Ansatz mit Generika. Ich habe das Konzept verwendet, um mein Problem zu lösen, aber Sie können die Lösung von @ Doku-so nicht so verwenden, wie sie ist, und sowohl in diesem Beitrag als auch im verlinkten Beitrag hat niemand die Fragen zu Verwendungsfehlern beantwortet.


Ich habe an Ihrer Lösung gearbeitet, als das Programm die Zeile übergeben hat. entityEntry.State = EntityState.Unchanged;Alle aktualisierten Werte im Parameter entityEntrywerden zurückgesetzt, sodass keine Änderungen gespeichert werden.
Können

2

In EntityFramework Core 2.x ist Folgendes nicht erforderlich Attach:

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

Versuchte dies in SQL Server und Profilierung:

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

Suchen stellt sicher, dass bereits geladene Entitäten kein SELECT auslösen, und hängt die Entität bei Bedarf automatisch an (aus den Dokumenten):

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

Ich kombiniere mehrere Vorschläge und schlage Folgendes vor:

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

angerufen von

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

Oder von

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

Oder von

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

Wie diese Methode für andere Klassen verfügbar gemacht werden kann, kann wie eine Erweiterungsmethode sein?
Velkumar

In diesem .NET CORE-Lernprogramm werden bewährte Methoden zur Verwendung (des neuen) EF Core zum Aktualisieren bestimmter Eigenschaften in MVC gezeigt. Suchen Sie nach 'TryUpdateModelAsync'.
Guy

1
@ Guy Awesome. Noch einmal, Microsofts "Best Practice" ist es, etwas anderes zu tun als das, was ihre Tools bauen ...
Auspex

Dies ist eine gute Lösung.
Timothy Macharia

1

Ich verwende ValueInjecterNuget, um das Bindungsmodell wie folgt in die Datenbankentität einzufügen:

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

Beachten Sie die Verwendung einer benutzerdefinierten Konvention, die Eigenschaften nicht aktualisiert, wenn sie vom Server null sind.

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

Verwendung:

target.InjectFrom<NoNullsInjection>(source);

Wert Injektor V2

Schlagen Sie diese Antwort nach

Vorbehalt

Sie werden nicht wissen, ob die Eigenschaft absichtlich auf null gelöscht wurde oder ob sie einfach keinen Wert hatte. Mit anderen Worten, der Eigenschaftswert kann nur durch einen anderen Wert ersetzt, aber nicht gelöscht werden.


0

Ich suchte das gleiche und fand schließlich die Lösung

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

Glauben Sie mir, es funktioniert für mich wie ein Zauber.


0

Dies ist, was ich benutze, mit benutzerdefinierten InjectNonNull (obj dest, obj src) macht es es voll flexibel

[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
Dadurch wird eine neue Zeile hinzugefügt. Die Frage ist, wie ein vorhandenes aktualisiert werden kann.
Edward Brey
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.