Quelqu'un a-t-il des suggestions sur la manière la plus efficace d'implémenter la logique «mise à jour de la ligne s'il existe, sinon insérer» à l'aide d'Entity Framework?
Quelqu'un a-t-il des suggestions sur la manière la plus efficace d'implémenter la logique «mise à jour de la ligne s'il existe, sinon insérer» à l'aide d'Entity Framework?
Réponses:
Si vous travaillez avec un objet attaché (objet chargé à partir de la même instance du contexte), vous pouvez simplement utiliser:
if (context.ObjectStateManager.GetObjectStateEntry(myEntity).State == EntityState.Detached)
{
context.MyEntities.AddObject(myEntity);
}
// Attached object tracks modifications automatically
context.SaveChanges();
Si vous pouvez utiliser des connaissances sur la clé de l'objet, vous pouvez utiliser quelque chose comme ceci:
if (myEntity.Id != 0)
{
context.MyEntities.Attach(myEntity);
context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
context.MyEntities.AddObject(myEntity);
}
context.SaveChanges();
Si vous ne pouvez pas décider de l'existence de l'objet par son identifiant, vous devez exécuter une requête de recherche:
var id = myEntity.Id;
if (context.MyEntities.Any(e => e.Id == id))
{
context.MyEntities.Attach(myEntity);
context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
context.MyEntities.AddObject(myEntity);
}
context.SaveChanges();
using
bloc. Est-il acceptable de laisser le contexte en mémoire pendant un moment? Par exemple, pendant la durée de vie d'un formulaire Windows? J'essaie normalement de nettoyer les objets de base de données pour assurer une charge minimale sur la base de données. N'y a-t-il aucun problème à attendre de détruire mon contexte EF?
Depuis Entity Framework 4.3, il existe une AddOrUpdate
méthode dans l'espace de noms System.Data.Entity.Migrations
:
public static void AddOrUpdate<TEntity>(
this IDbSet<TEntity> set,
params TEntity[] entities
)
where TEntity : class
qui par le doc :
Ajoute ou met à jour des entités par clé lorsque SaveChanges est appelé. Équivaut à une opération "upsert" à partir de la terminologie de la base de données. Cette méthode peut être utile lors de l'amorçage de données à l'aide de Migrations.
Pour répondre au commentaire de @ Smashing1978 , je vais coller les parties pertinentes du lien fourni par @Colin
Le travail d'AddOrUpdate est de s'assurer que vous ne créez pas de doublons lorsque vous amorcez des données pendant le développement.
Tout d'abord, il exécutera une requête dans votre base de données à la recherche d'un enregistrement où tout ce que vous avez fourni comme clé (premier paramètre) correspond à la ou aux valeurs de colonne mappée fournies dans AddOrUpdate. C'est donc un peu lâche pour la correspondance, mais parfaitement adapté pour l'ensemencement des données de conception.
Plus important encore, si une correspondance est trouvée, la mise à jour mettra à jour tout et annulera tout ce qui ne figurait pas dans votre AddOrUpdate.
Cela dit, j'ai une situation dans laquelle je tire des données d'un service externe et j'insère ou mets à jour des valeurs existantes par clé primaire (et mes données locales pour les consommateurs sont en lecture seule) - j'utilise AddOrUpdate
en production depuis plus de 6 mois maintenant et donc loin aucun problème.
La magie se produit lors de l'appel SaveChanges()
et dépend du courant EntityState
. Si l'entité a un EntityState.Added
, elle sera ajoutée à la base de données, si elle en a un EntityState.Modified
, elle sera mise à jour dans la base de données. Vous pouvez donc implémenter une InsertOrUpdate()
méthode comme suit:
public void InsertOrUpdate(Blog blog)
{
using (var context = new BloggingContext())
{
context.Entry(blog).State = blog.BlogId == 0 ?
EntityState.Added :
EntityState.Modified;
context.SaveChanges();
}
}
En savoir plus sur EntityState
Si vous ne pouvez pas vérifier Id = 0
s'il s'agit d'une nouvelle entité ou non, vérifiez la réponse de Ladislav Mrnka .
Si vous savez que vous utilisez le même contexte et que vous ne détachez aucune entité, vous pouvez créer une version générique comme celle-ci:
public void InsertOrUpdate<T>(T entity, DbContext db) where T : class
{
if (db.Entry(entity).State == EntityState.Detached)
db.Set<T>().Add(entity);
// If an immediate save is needed, can be slow though
// if iterating through many entities:
db.SaveChanges();
}
db
peut bien sûr être un champ de classe, ou la méthode peut être rendue statique et une extension, mais ce sont les bases.
La réponse de Ladislav était proche mais j'ai dû faire quelques modifications pour que cela fonctionne dans EF6 (base de données d'abord). J'ai étendu mon contexte de données avec ma méthode on AddOrUpdate et jusqu'à présent, cela semble bien fonctionner avec les objets détachés:
using System.Data.Entity;
[....]
public partial class MyDBEntities {
public void AddOrUpdate(MyDBEntities ctx, DbSet set, Object obj, long ID) {
if (ID != 0) {
set.Attach(obj);
ctx.Entry(obj).State = EntityState.Modified;
}
else {
set.Add(obj);
}
}
[....]
À mon avis, il vaut la peine de dire qu'avec le tout nouveau EntityGraphOperations pour Entity Framework Code First, vous pouvez vous éviter d'écrire des codes répétitifs pour définir les états de toutes les entités du graphique. Je suis l'auteur de ce produit. Et je l'ai publié dans le github , code-project ( comprend une démonstration étape par étape et un exemple de projet est prêt à être téléchargé) et nuget .
Il définira automatiquement l'état des entités sur Added
ou Modified
. Et vous choisirez manuellement quelles entités doivent être supprimées si elles n'existent plus.
L'exemple:
Disons que j'ai un Person
objet. Person
pourrait avoir plusieurs téléphones, un document et pourrait avoir un conjoint.
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public int Age { get; set; }
public int DocumentId {get; set;}
public virtual ICollection<Phone> Phones { get; set; }
public virtual Document Document { get; set; }
public virtual PersonSpouse PersonSpouse { get; set; }
}
Je souhaite déterminer l'état de toutes les entités incluses dans le graphique.
context.InsertOrUpdateGraph(person)
.After(entity =>
{
// Delete missing phones.
entity.HasCollection(p => p.Phones)
.DeleteMissingEntities();
// Delete if spouse is not exist anymore.
entity.HasNavigationalProperty(m => m.PersonSpouse)
.DeleteIfNull();
});
De plus, comme vous le savez, les propriétés de clé uniques pourraient jouer un rôle lors de la définition de l'état de l'entité Phone. Pour ces fins spéciales, nous avons la ExtendedEntityTypeConfiguration<>
classe, qui hérite de EntityTypeConfiguration<>
. Si nous voulons utiliser de telles configurations spéciales, nous devons hériter de nos classes de mappage ExtendedEntityTypeConfiguration<>
plutôt que de EntityTypeConfiguration<>
. Par exemple:
public class PhoneMap: ExtendedEntityTypeConfiguration<Phone>
{
public PhoneMap()
{
// Primary Key
this.HasKey(m => m.Id);
…
// Unique keys
this.HasUniqueKey(m => new { m.Prefix, m.Digits });
}
}
C'est tout.
Insérer autre mettre à jour les deux
public void InsertUpdateData()
{
//Here TestEntities is the class which is given from "Save entity connection setting in web.config"
TestEntities context = new TestEntities();
var query = from data in context.Employee
orderby data.name
select data;
foreach (Employee details in query)
{
if (details.id == 1)
{
//Assign the new values to name whose id is 1
details.name = "Sanjay";
details. Surname="Desai";
details.address=" Desiwadi";
}
else if(query==null)
{
details.name="Sharad";
details.surname=" Chougale ";
details.address=" Gargoti";
}
}
//Save the changes back to database.
context.SaveChanges();
}
Vérifiez la ligne existante avec Any.
public static void insertOrUpdateCustomer(Customer customer)
{
using (var db = getDb())
{
db.Entry(customer).State = !db.Customer.Any(f => f.CustomerId == customer.CustomerId) ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
}
}
Alternative pour la réponse @LadislavMrnka. Cela vaut 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 DbSet
avec 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);
}
}
Corrigée
public static void InsertOrUpdateRange<T, T2>(this T entity, List<T2> updateEntity)
where T : class
where T2 : class
{
foreach(var e in updateEntity)
{
context.Set<T2>().InsertOrUpdate(e);
}
}
public static void InsertOrUpdate<T, T2>(this T entity, T2 updateEntity)
where T : class
where T2 : class
{
if (context.Entry(updateEntity).State == EntityState.Detached)
{
if (context.Set<T2>().Any(t => t == updateEntity))
{
context.Set<T2>().Update(updateEntity);
}
else
{
context.Set<T2>().Add(updateEntity);
}
}
context.SaveChanges();
}