Eigenschaft beim Aktualisieren in Entity Framework ausschließen

79

Ich habe nach einer geeigneten Möglichkeit gesucht, eine Eigenschaft zu markieren, die beim Aktualisieren eines Modells in MVC NICHT geändert werden soll.

Nehmen wir zum Beispiel dieses kleine Modell:

class Model
{
    [Key]
    public Guid Id {get; set;}
    public Guid Token {get; set;}

    //... lots of properties here ...
}

Dann sieht die von MVC erstellte Bearbeitungsmethode folgendermaßen aus:

[HttpPost]
public ActionResult Edit(Model model)
{
    if (ModelState.IsValid)
    {
        db.Entry(model).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(model);
}

Wenn meine Ansicht das Token nicht enthält, wird es durch diese Bearbeitung ungültig.

Ich suche so etwas:

db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.Token).State = PropertyState.Unmodified;
db.SaveChanges();

Der beste Weg, den ich bisher gefunden habe, ist, inklusiv zu sein und alle Eigenschaften, die ich einschließen möchte, von Hand festzulegen, aber ich möchte wirklich nur sagen, welche ausgeschlossen werden sollen.

Manuel Schweigert
quelle
Mögliches Duplikat: stackoverflow.com/questions/3809583/…
Nate-Wilkins
Ich denke nicht, dass es doppelt ist: Ich möchte immer eine bestimmte Eigenschaft von der Aktualisierung ausschließen. Der Benutzer sollte keine Möglichkeit haben, dies zu ändern.
Manuel Schweigert
2
Sie können Ansichtsmodelle verwenden und einfach zuordnen, was Sie aktualisieren möchten.
Frennky
Ich könnte. Es gibt einige Möglichkeiten, um dieses Problem zu umgehen. Aber ich möchte wissen, ob es eine gute Möglichkeit gibt, dies zu tun, und wenn es eine gibt, wie es funktioniert. Übrigens ist die kleinste "Lösung", die ich für diesen Geldautomaten habe, eine weitere Transaktion zu eröffnen: using (var db2 = new DataContext()) model.Token = db2.Models.Find(model.Id).Token;Aber ich bin auch mit dieser nicht zufrieden.
Manuel Schweigert
3
Ich erkenne an, dass dies der "richtige" Weg ist, aber es gibt Gründe, dies in diesem Fall nicht so zu tun: a) Overhead, b) nicht agil, c) nicht wartbar / fehleranfällig. Also ja, ich weigere mich, bis auf eine Eigenschaft zwei identische Klassen zu erstellen.
Manuel Schweigert

Antworten:

155

wir können so verwenden

 db.Entry(model).State = EntityState.Modified;
 db.Entry(model).Property(x => x.Token).IsModified = false;
 db.SaveChanges();

Es wird aktualisiert, jedoch ohne Token-Eigenschaft

Nitin Dominic
quelle
2
Was ist, wenn Sie verwenden AddOrUpdate? - Woher weißt du, EntityState.Modifiedoder EntityState.Added?
Jess
1
UPDATE: Damit es in EF 6 funktioniert, müssen Sie db.Model.Attach (Modell);
Maxi
6
Nur ein Hinweis für andere, dass die Reihenfolge hier wichtig ist: Wenn Sie db.Entry(model).State = EntityState.Modified;nach der Einstellung festlegen db.Entry(model).Property(x => x.Token).IsModified = false; , wird die Eigenschaft beim Speichern aktualisiert.
Akerra
1
Dies sollte auch nach dem Aktualisieren der Modellwerte geschehen. Db.Entry (model) .CurrentValues.SetValues ​​(sourceModel); Wenn nicht, wird die Eigenschaft auch beim Speichern aktualisiert.
DotNet Fan
10

Erstellen Sie ein neues Modell mit begrenzten Eigenschaften, die Sie aktualisieren möchten.

Dh wenn Ihr Entitätsmodell ist:

public class User
{
    public int Id {get;set;}
    public string Name {get;set;}
    public bool Enabled {get;set;}
}

Sie können ein benutzerdefiniertes Ansichtsmodell erstellen, mit dem Benutzer den Namen ändern können, jedoch nicht das Flag Aktiviert:

public class UserProfileModel
{
   public int Id {get;set;}
   public string Name {get;set;}
}

Wenn Sie eine Datenbankaktualisierung durchführen möchten, gehen Sie wie folgt vor:

YourUpdateMethod(UserProfileModel model)
{
    using(YourContext ctx = new YourContext())
    { 
        User user = new User { Id = model.Id } ;   /// stub model, only has Id
        ctx.Users.Attach(user); /// track your stub model
        ctx.Entry(user).CurrentValues.SetValues(model); /// reflection
        ctx.SaveChanges();
    }
}

Wenn Sie diese Methode aufrufen, aktualisieren Sie den Namen, die Enabled-Eigenschaft bleibt jedoch unverändert. Ich habe einfache Modelle verwendet, aber ich denke, Sie werden sich ein Bild davon machen, wie man es benutzt.

Bewunderer Tuzović
quelle
danke, das sieht gut aus, aber dies ist immer noch eine Whitelist, keine schwarze Liste von Eigenschaften.
Manuel Schweigert
Sie "listen" alles auf, was nicht in Ihrem Ansichtsmodell enthalten ist, und dies erfordert keine zusätzliche Codierung. Sie verwenden nur EF-Funktionen. Wenn eine Stub-Entität mit Anhängen verbunden ist, werden alle Eigenschaftswerte auf null / Standard gesetzt. Wenn Sie SetValues ​​(Modell) verwenden und Ihre Ansichtsmodelleigenschaft null ist, da sie bereits als null angehängt wurde, markiert der Änderungs-Tracker sie nicht als geändert, und daher wird diese Eigenschaft beim Speichern übersprungen. Versuch es.
Admir Tuzović
3
Ich möchte nicht mit dir streiten. Blacklisting und Whitelisting sind unterschiedliche Ansätze mit demselben Ergebnis. Ihr Ansatz ist Whitelisting. Wie ich bereits sagte, gibt es viele Möglichkeiten, aber ich habe insbesondere nach einer gefragt. Darüber hinaus funktioniert Ihre Lösung nur mit nullbaren Typen.
Manuel Schweigert
1. Hängen Sie Ihr Modell mit Anhängen an. 2. Durchlaufen Sie die Eigenschaften mit db.Entry (Modell) .Property ("Propertyname"). State = PropertyState.Modified; 3. Führen Sie SaveChanges aus.
Admir Tuzović
Sie legen fest, dass das gesamte Modell geändert werden soll, und setzen dann einige Eigenschaften auf Nicht geändert. Was ich Ihnen geschrieben habe, ist, zuerst das Modell anzuhängen (nichts wird als geändert festgelegt) und dann die Eigenschaften, die Sie aktualisieren möchten, als geändert zu markieren. Dies ist genau das, was Sie wollten => Whitelisting.
Admir Tuzović
8

Jeder, der danach sucht, wie dies auf EF Core erreicht werden kann. Es ist im Grunde das gleiche, aber Ihr IsModified muss sein, nachdem Sie das zu aktualisierende Modell hinzugefügt haben.

db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
Jesse
quelle
Ich weiß nicht warum, aber mein EF Core hatte nur eine String-Version und ich konnte kein Lambda verwenden. Ich habe stattdessen nameof verwendet, aber dies ist der richtige Weg. Danke
Cubelaster
Diese Antwort ist so "Microsofty", dass ich wusste, dass es funktionieren würde, bevor ich es teste. Im Gegensatz zu dem obigen Kommentar lieferten sowohl die String- als auch die Lambda-Version die gleichen Ergebnisse. Vielleicht verwenden wir verschiedene Versionen.
T3.0
2

Ich habe eine einfache Möglichkeit zum Bearbeiten von Eigenschaften von Entitäten erstellt, die ich mit Ihnen teilen werde. Dieser Code bearbeitet die Eigenschaften Name und Familie der Entität:

    public void EditProfileInfo(ProfileInfo profileInfo)
    {
        using (var context = new TestContext())
        {
            context.EditEntity(profileInfo, TypeOfEditEntityProperty.Take, nameof(profileInfo.Name), nameof(profileInfo.Family));
        }
    }

Dieser Code wird ignoriert, um die Eigenschaften von Name und Familie der Entität zu bearbeiten, und es werden andere Eigenschaften bearbeitet:

    public void EditProfileInfo(ProfileInfo profileInfo)
    {
        using (var context = new TestContext())
        {
            context.EditEntity(profileInfo, TypeOfEditEntityProperty.Ignore, nameof(profileInfo.Name), nameof(profileInfo.Family));
        }
    }

Verwenden Sie diese Erweiterung:

public static void EditEntity<TEntity>(this DbContext context, TEntity entity, TypeOfEditEntityProperty typeOfEditEntityProperty, params string[] properties)
   where TEntity : class
{
    var find = context.Set<TEntity>().Find(entity.GetType().GetProperty("Id").GetValue(entity, null));
    if (find == null)
        throw new Exception("id not found in database");
    if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Ignore)
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            if (properties.Contains(item.Name))
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    else if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Take)
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            if (!properties.Contains(item.Name))
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    else
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    context.SaveChanges();
}

public enum TypeOfEditEntityProperty
{
    Ignore,
    Take
}
Ali Yousefi
quelle
1

Ich denke, Sie möchten nicht, dass die Eigenschaft nur in einigen Fällen geändert wird. Wenn Sie sie nicht in Ihrer Anwendung verwenden möchten, entfernen Sie sie einfach aus Ihrem Modell.

Wenn Sie es nur in einigen Szenarien verwenden und seine "Aufhebung" im obigen Fall vermeiden möchten, können Sie Folgendes versuchen:

  • Blenden Sie den Parameter in der Ansicht mit HiddenFor aus:

    @Html.HiddenFor(m => m.Token)

Dadurch bleibt Ihr ursprünglicher Wert unverändert und wird an die Steuerung zurückgegeben.

Laden Sie Ihr Objekt erneut von Ihrem in den Controller DBSetund führen Sie diese Methode aus. Sie können sowohl eine weiße Liste als auch eine schwarze Liste von Parametern angeben, die aktualisiert werden sollen oder nicht.

Jaime
quelle
Eine gute Diskussion über TryUpdateModel finden Sie hier: Link . Wie in der validierten Antwort angegeben, ist es besser, Ansichtsmodelle zu erstellen, die genau den Eigenschaften entsprechen, die in jeder Ansicht benötigt werden.
Jaime
1
Mit @Html.HiddenForwird der Wert in den HTML-Code der Ansicht geschrieben und der Benutzer kann das Inspect-Element in seinem Browser verwenden und ändern. Danach wird es weiterhin an den Controller übergeben, jedoch mit einem anderen Wert, und aktualisiert. Ich habe es gerade getestet.
duckwizzle