Comment mettre à jour l'enregistrement en utilisant Entity Framework 6?

245

J'essaie de mettre à jour l'enregistrement en utilisant EF6. Si vous trouvez d'abord l'enregistrement, s'il existe, mettez-le à jour. Voici mon code: -

var book = new Model.Book
{
    BookNumber =  _book.BookNumber,
    BookName = _book.BookName,
    BookTitle = _book.BookTitle,
};
using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        try
        {
            db.Books.Attach(book);
            db.Entry(book).State = EntityState.Modified;
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

Chaque fois que j'essaie de mettre à jour l'enregistrement en utilisant le code ci-dessus, j'obtiens cette erreur: -

{System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: l'instruction de mise à jour, d'insertion ou de suppression de magasin a affecté un nombre inattendu de lignes (0). Les entités peuvent avoir été modifiées ou supprimées depuis le chargement des entités. Actualiser l'entrée ObjectStateManager

user1327064
la source
7
Note latérale: catch (Exception ex){throw;}est redondant et vous pouvez le supprimer complètement.
Sriram Sakthivel
essayer catch block est juste pour comprendre la raison de l'échec. Mais vous ne comprenez toujours pas pourquoi ce code échoue?
user1327064
2
Je ne suis pas expert dans ce sujet, je ne peux pas répondre à cette question. mais sans essayer catch, vous pouvez également utiliser la fonction break when exception is thrown pour casser le débogueur quand il y a une exception.
Sriram Sakthivel
1
Tu n'as rien changé. Jouer avec l'état Entité ne changera pas le fait que l'objet n'a pas été réellement modifié.
Jonathan Allen
1
Eh bien, j'ai fait la même chose que vous et je n'ai pas eu l'erreur. L'exception indique DbUpdateConcurrencyException. Comment avez-vous géré la concurrence? Avez-vous utilisé un horodatage, avez-vous cloné puis fusionné à nouveau les objets ou avez-vous utilisé des entités d'auto-suivi? (3 approches les plus utilisées). Si vous ne gérez pas la simultanéité, je suppose que c'est le problème.
El Mac

Réponses:

344

Vous essayez de mettre à jour l'enregistrement (ce qui signifie pour moi "modifier une valeur sur un enregistrement existant et le sauvegarder"). Vous devez donc récupérer l'objet, apporter une modification et l'enregistrer.

using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        result.SomeValue = "Some new value";
        db.SaveChanges();
    }
}
Craig W.
la source
16
L'affectation de la valeur ne met pas à jour la base de données, l'appel db.SaveChanges()avec des objets modifiés dans le contexte met à jour la base de données.
Craig W.
6
Cela me fascine toujours ... donc le résultat var, se connecte en fait au dbcontext ... donc cela signifie que toute variable qui est instanciée par n'importe quel membre de dbcontext aura en fait cet associaten à la base de données de sorte que tout changement soit appliqué à cette variable , est-elle également appliquée ou persistante?
WantIt
6
Étant donné que le contexte a généré l'objet, le contexte peut suivre l'objet, y compris les modifications apportées à l'objet. Lorsque vous appelez SaveChangesle contexte, il évalue tous les objets qu'il suit pour déterminer s'ils sont ajoutés, modifiés ou supprimés et envoie le code SQL approprié à la base de données connectée.
Craig W.
3
iam face au même problème - en utilisant EF6, en essayant de mettre à jour une entité. Attach + EntityState.Modified ne fonctionne pas. La seule chose qui fonctionne est - vous devez récupérer l'objet, apporter les modifications souhaitées et l'enregistrer via db.SaveChanges ();
Gurpreet Singh
7
Vous ne devriez PAS avoir à récupérer l'objet en premier afin de le mettre à jour. J'ai eu le même problème jusqu'à ce que je réalise que j'essayais de changer l'une des valeurs de clé primaire (clé composite). Tant que vous fournissez une clé primaire correcte, vous pouvez définir EntityState sur Modified et SaveChanges () fonctionnera, à condition de ne pas briser une autre contrainte d'intégrité définie sur la table.
adrianz
165

J'ai examiné le code source d'Entity Framework et trouvé un moyen de mettre à jour une entité si vous connaissez la propriété Key:

public void Update<T>(T item) where T: Entity
{
    // assume Entity base class have an Id property for all items
    var entity = _collection.Find(item.Id);
    if (entity == null)
    {
        return;
    }

    _context.Entry(entity).CurrentValues.SetValues(item);
}

Sinon, vérifiez l' implémentation AddOrUpdate pour des idées.

J'espère que cette aide!

Miguel
la source
12
Agréable! Pas besoin d'énumérer toutes les propriétés. Je suppose que l' SaveChanges()appel est requis après la définition des valeurs.
Jan Zahradník
3
Oui, les modifications seront persistantes sur SaveChanges ()
Miguel
1
Excellente réponse, il n'était pas trop clair avec IntelliSense que faire quelque chose comme ça ne fonctionnerait PAS: _context.MyObj = newObj; puis SaveChanges () ou .... _context.MyObj.Update (newObj) puis SaveChanges (); Votre solution met à jour l'objet entier sans avoir à parcourir toutes les propriétés.
Adam
7
Cela me plaint que j'essaie de modifier le champ ID
Vasily Hall
3
@VasilyHall - cela se produit si les champs ID (ou tout ce que vous avez défini comme clé primaire) sont différents entre les modèles (y compris null / 0 dans l'un des modèles). Assurez-vous que les ID correspondent entre les deux modèles et il se mettra à jour très bien.
Gavin Coates
51

Vous pouvez utiliser la AddOrUpdateméthode:

db.Books.AddOrUpdate(book); //requires using System.Data.Entity.Migrations;
db.SaveChanges();
nicedev80
la source
1
IMO meilleure solution
Norgul
112
.AddOrUpdate()est utilisé lors de la migration de la base de données, il est fortement déconseillé d'utiliser cette méthode en dehors des migrations, d'où sa raison d'être dans l' Entity.Migrationsespace de noms.
Adam Vincent
1
Comme l'a dit @AdamVincent, la AddOrUpdate()méthode est destinée aux migrations et ne convient pas aux situations où vous n'avez besoin que de mettre à jour la ligne existante. Si vous n'avez pas de livre avec une référence de recherche (c'est-à-dire un ID), cela créera une nouvelle ligne et cela peut être un problème dans les cas (par exemple, vous avez une API qui doit vous renvoyer une réponse 404-NotFound si vous essayez d'appeler la méthode PUT pour une ligne non existante).
Marko
4
Ne l'utilisez pas sauf si vous savez ce que vous faites !!!!!!!!!!!!!!!! lire: michaelgmccarthy.com/2016/08/24/…
Yusha
4
Je suis revenu là-dessus aujourd'hui, puis-je simplement vous avertir que ce n'est pas une bonne solution pour le cas d'utilisation souhaité
Yusha
23

Vous avez donc une entité qui est mise à jour, et vous souhaitez la mettre à jour dans la base de données avec le moins de code ...

La concurrence est toujours délicate, mais je suppose que vous voulez juste que vos mises à jour gagnent. Voici comment je l'ai fait pour mon même cas et modifié les noms pour imiter vos classes. En d'autres termes, changez simplement attachen add, et cela fonctionne pour moi:

public static void SaveBook(Model.Book myBook)
{
    using (var ctx = new BookDBContext())
    {
        ctx.Books.Add(myBook);
        ctx.Entry(myBook).State = System.Data.Entity.EntityState.Modified;
        ctx.SaveChanges();
    }
}
Duray Akar
la source
10

Vous devez utiliser la méthode Entry () dans le cas où vous souhaitez mettre à jour tous les champs de votre objet. Gardez également à l'esprit que vous ne pouvez pas modifier l'ID de champ (clé), définissez donc d'abord l'ID sur le même que vous modifiez.

using(var context = new ...())
{
    var EditedObj = context
        .Obj
        .Where(x => x. ....)
        .First();

    NewObj.Id = EditedObj.Id; //This is important when we first create an object (NewObj), in which the default Id = 0. We can not change an existing key.

    context.Entry(EditedObj).CurrentValues.SetValues(NewObj);

    context.SaveChanges();
}
Jarek
la source
2
Vous devriez au moins essayer de répondre à la question, pas seulement de poster le code
StaceyGirl
Veuillez expliquer la question au lieu de simplement laisser un extrait de code afin de mieux aider le demandeur de la question.
feanor07
9

Ce code est le résultat d'un test pour mettre à jour uniquement un ensemble de colonnes sans effectuer de requête pour renvoyer l'enregistrement en premier. Il utilise d'abord le code Entity Framework 7.

// This function receives an object type that can be a view model or an anonymous 
// object with the properties you want to change. 
// This is part of a repository for a Contacts object.

public int Update(object entity)
{
    var entityProperties =  entity.GetType().GetProperties();   
    Contacts con = ToType(entity, typeof(Contacts)) as Contacts;

    if (con != null)
    {
        _context.Entry(con).State = EntityState.Modified;
        _context.Contacts.Attach(con);

        foreach (var ep in entityProperties)
        {
            // If the property is named Id, don't add it in the update. 
            // It can be refactored to look in the annotations for a key 
            // or any part named Id.

            if(ep.Name != "Id")
                _context.Entry(con).Property(ep.Name).IsModified = true;
        }
    }

    return _context.SaveChanges();
}

public static object ToType<T>(object obj, T type)
{
    // Create an instance of T type object
    object tmp = Activator.CreateInstance(Type.GetType(type.ToString()));

    // Loop through the properties of the object you want to convert
    foreach (PropertyInfo pi in obj.GetType().GetProperties())
    {
        try
        {
            // Get the value of the property and try to assign it to the property of T type object
            tmp.GetType().GetProperty(pi.Name).SetValue(tmp, pi.GetValue(obj, null), null);
        }
        catch (Exception ex)
        {
            // Logging.Log.Error(ex);
        }
    }
    // Return the T type object:         
    return tmp;
}

Voici le code complet:

public interface IContactRepository
{
    IEnumerable<Contacts> GetAllContats();
    IEnumerable<Contacts> GetAllContactsWithAddress();
    int Update(object c);
}

public class ContactRepository : IContactRepository
{
    private ContactContext _context;

    public ContactRepository(ContactContext context)
    {
        _context = context;
    }

    public IEnumerable<Contacts> GetAllContats()
    {
        return _context.Contacts.OrderBy(c => c.FirstName).ToList();
    }

    public IEnumerable<Contacts> GetAllContactsWithAddress()
    {
        return _context.Contacts
            .Include(c => c.Address)
            .OrderBy(c => c.FirstName).ToList();
    }   

    //TODO Change properties to lambda expression
    public int Update(object entity)
    {
        var entityProperties = entity.GetType().GetProperties();

        Contacts con = ToType(entity, typeof(Contacts)) as Contacts;

        if (con != null)
        {
            _context.Entry(con).State = EntityState.Modified;
            _context.Contacts.Attach(con);

            foreach (var ep in entityProperties)
            {
                if(ep.Name != "Id")
                    _context.Entry(con).Property(ep.Name).IsModified = true;
            }
        }

        return _context.SaveChanges();
    }

    public static object ToType<T>(object obj, T type)
    {
        // Create an instance of T type object
        object tmp = Activator.CreateInstance(Type.GetType(type.ToString()));

        // Loop through the properties of the object you want to convert
        foreach (PropertyInfo pi in obj.GetType().GetProperties())
        {
            try
            {
                // Get the value of the property and try to assign it to the property of T type object
                tmp.GetType().GetProperty(pi.Name).SetValue(tmp, pi.GetValue(obj, null), null);
            }
            catch (Exception ex)
            {
                // Logging.Log.Error(ex);
            }
        }
        // Return the T type object
        return tmp;
    }
}    

public class Contacts
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Company { get; set; }
    public string Title { get; set; }
    public Addresses Address { get; set; }    
}

public class Addresses
{
    [Key]
    public int Id { get; set; }
    public string AddressType { get; set; }
    public string StreetAddress { get; set; }
    public string City { get; set; }
    public State State { get; set; }
    public string PostalCode { get; set; }  
}

public class ContactContext : DbContext
{
    public DbSet<Addresses> Address { get; set; } 
    public DbSet<Contacts> Contacts { get; set; } 
    public DbSet<State> States { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connString = "Server=YourServer;Database=ContactsDb;Trusted_Connection=True;MultipleActiveResultSets=true;";
        optionsBuilder.UseSqlServer(connString);
        base.OnConfiguring(optionsBuilder);
    }
}
Juan
la source
7

Pour le noyau .net

context.Customer.Add(customer);
context.Entry(customer).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
context.SaveChanges();
Chris Rosete
la source
5

Voici la meilleure solution pour ce problème: dans la vue, ajoutez tous les ID (clés). Envisagez de nommer plusieurs tables (première, deuxième et troisième)

@Html.HiddenFor(model=>model.FirstID)
@Html.HiddenFor(model=>model.SecondID)
@Html.HiddenFor(model=>model.Second.SecondID)
@Html.HiddenFor(model=>model.Second.ThirdID)
@Html.HiddenFor(model=>model.Second.Third.ThirdID)

En code C #,

[HttpPost]
public ActionResult Edit(First first)
{
  if (ModelState.Isvalid)
  {
    if (first.FirstID > 0)
    {
      datacontext.Entry(first).State = EntityState.Modified;
      datacontext.Entry(first.Second).State = EntityState.Modified;
      datacontext.Entry(first.Second.Third).State = EntityState.Modified;
    }
    else
    {
      datacontext.First.Add(first);
    }
    datacontext.SaveChanges();
    Return RedirectToAction("Index");
  }

 return View(first);
}
Kumar R
la source
5

AttachUne entité définira son état de suivi sur Unchanged. Pour mettre à jour une entité existante, il vous suffit de définir l'état de suivi sur Modified. Selon les documents EF6 :

Si vous avez une entité dont vous savez qu'elle existe déjà dans la base de données mais à laquelle des modifications peuvent avoir été apportées, vous pouvez indiquer au contexte d'attacher l'entité et de définir son état sur Modifié. Par exemple:

var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };

using (var context = new BloggingContext())
{
    context.Entry(existingBlog).State = EntityState.Modified;

    // Do some more work...  

    context.SaveChanges();
}
Bondolin
la source
4
using(var myDb = new MyDbEntities())
{

    user user = new user();
    user.username = "me";
    user.email = "[email protected]";

    myDb.Users.Add(user);
    myDb.users.Attach(user);
    myDb.Entry(user).State = EntityState.Modified;//this is for modiying/update existing entry
    myDb.SaveChanges();
}
Nikhil Dinesh
la source
4

J'ai trouvé un moyen qui fonctionne très bien.

 var Update = context.UpdateTables.Find(id);
        Update.Title = title;

        // Mark as Changed
        context.Entry(Update).State = System.Data.Entity.EntityState.Modified;
        context.SaveChanges();
Farhan
la source
3

Vous devez supprimer db.Books.Attach(book);

Renat Seyfetdinov
la source
1

Voici ma méthode de mise à jour d'entité post-RIA (pour la période Ef6):

public static void UpdateSegment(ISegment data)
{
    if (data == null) throw new ArgumentNullException("The expected Segment data is not here.");

    var context = GetContext();

    var originalData = context.Segments.SingleOrDefault(i => i.SegmentId == data.SegmentId);
    if (originalData == null) throw new NullReferenceException("The expected original Segment data is not here.");

    FrameworkTypeUtility.SetProperties(data, originalData);

    context.SaveChanges();
}

Notez que FrameworkTypeUtility.SetProperties()c'est une petite fonction utilitaire que j'ai écrite bien avant AutoMapper sur NuGet:

public static void SetProperties<TIn, TOut>(TIn input, TOut output, ICollection<string> includedProperties)
    where TIn : class
    where TOut : class
{
    if ((input == null) || (output == null)) return;
    Type inType = input.GetType();
    Type outType = output.GetType();
    foreach (PropertyInfo info in inType.GetProperties())
    {
        PropertyInfo outfo = ((info != null) && info.CanRead)
            ? outType.GetProperty(info.Name, info.PropertyType)
            : null;
        if (outfo != null && outfo.CanWrite
            && (outfo.PropertyType.Equals(info.PropertyType)))
        {
            if ((includedProperties != null) && includedProperties.Contains(info.Name))
                outfo.SetValue(output, info.GetValue(input, null), null);
            else if (includedProperties == null)
                outfo.SetValue(output, info.GetValue(input, null), null);
        }
    }
}
rasx
la source
Remarque: ne fonctionne que si vos propriétés sont exactement les mêmes dans votre modèle que votre objet ViewModel qui y est enregistré.
vapcguy
1

Comme l'a dit Renat, supprimez: db.Books.Attach(book);

Modifiez également votre requête de résultat pour utiliser "AsNoTracking", car cette requête supprime l'état du modèle du framework d'entité. Il pense que "résultat" est le livre à suivre maintenant et vous ne voulez pas cela.

var result = db.Books.AsNoTracking().SingleOrDefault(b => b.BookNumber == bookNumber);
Nez
la source
1

Essayez-le ....

UpdateModel (livre);

var book = new Model.Book
{
    BookNumber =  _book.BookNumber,
    BookName = _book.BookName,
    BookTitle = _book.BookTitle,
};
using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        try
        {
            UpdateModel(book);
            db.Books.Attach(book);
            db.Entry(book).State = EntityState.Modified;
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}
Karan
la source
1

Je sais qu'il a déjà été répondu à plusieurs reprises, mais j'aime la façon de procéder ci-dessous. J'espère que cela aidera quelqu'un.

//attach object (search for row)
TableName tn = _context.TableNames.Attach(new TableName { PK_COLUMN = YOUR_VALUE});
// set new value
tn.COLUMN_NAME_TO_UPDATE = NEW_COLUMN_VALUE;
// set column as modified
_context.Entry<TableName>(tn).Property(tnp => tnp.COLUMN_NAME_TO_UPDATE).IsModified = true;
// save change
_context.SaveChanges();
Pawel Czapski
la source
1

C'est le cas pour Entity Framework 6.2.0.

Si vous avez un DbSetélément spécifique et un élément qui doit être mis à jour ou créé:

var name = getNameFromService();

var current = _dbContext.Names.Find(name.BusinessSystemId, name.NameNo);
if (current == null)
{
    _dbContext.Names.Add(name);
}
else
{
    _dbContext.Entry(current).CurrentValues.SetValues(name);
}
_dbContext.SaveChanges();

Cependant, cela peut également être utilisé pour un générique DbSetavec une seule clé primaire ou une clé primaire composite.

var allNames = NameApiService.GetAllNames();
GenericAddOrUpdate(allNames, "BusinessSystemId", "NameNo");

public virtual void GenericAddOrUpdate<T>(IEnumerable<T> values, params string[] keyValues) where T : class
{
    foreach (var value in values)
    {
        try
        {
            var keyList = new List<object>();

            //Get key values from T entity based on keyValues property
            foreach (var keyValue in keyValues)
            {
                var propertyInfo = value.GetType().GetProperty(keyValue);
                var propertyValue = propertyInfo.GetValue(value);
                keyList.Add(propertyValue);
            }

            GenericAddOrUpdateDbSet(keyList, value);
            //Only use this when debugging to catch save exceptions
            //_dbContext.SaveChanges();
        }
        catch
        {
            throw;
        }
    }
    _dbContext.SaveChanges();
}

public virtual void GenericAddOrUpdateDbSet<T>(List<object> keyList, T value) where T : class
{
    //Get a DbSet of T type
    var someDbSet = Set(typeof(T));

    //Check if any value exists with the key values
    var current = someDbSet.Find(keyList.ToArray());
    if (current == null)
    {
        someDbSet.Add(value);
    }
    else
    {
        Entry(current).CurrentValues.SetValues(value);
    }
}
Ogglas
la source