web-dev-qa-db-fra.com

Comment puis-je empêcher Entity Framework d'essayer de sauvegarder / insérer des objets enfants?

Lorsque je sauvegarde une entité avec une structure d'entité, j'ai naturellement supposé que cela ne ferait que tenter de sauvegarder l'entité spécifiée. Cependant, il essaie également de sauvegarder les entités enfants de cette entité. Cela cause toutes sortes de problèmes d'intégrité. Comment forcer EF à ne sauvegarder que l'entité que je veux sauvegarder et donc à ignorer tous les objets enfants?

Si je définis manuellement les propriétés sur null, j'obtiens une erreur "L'opération a échoué: la relation n'a pas pu être modifiée car une ou plusieurs propriétés de clé étrangère ne sont pas nullables." Ceci est extrêmement contre-productif puisque j’ai défini l’objet enfant sur null de manière à ce que EF le laisse tranquille.

Pourquoi ne veux-je pas sauvegarder/insérer les objets enfants?

Comme ceci est discuté dans les commentaires, je vais donner une justification de la raison pour laquelle je veux que mes objets enfants soient laissés seuls.

Dans l'application que je suis en train de construire, le modèle d'objet EF n'est pas chargé à partir de la base de données, mais utilisé en tant qu'objets de données que je remplis lors de l'analyse d'un fichier à plat. Dans le cas des objets enfants, bon nombre d'entre eux font référence à des tables de recherche définissant diverses propriétés de la table parent. Par exemple, l'emplacement géographique de l'entité principale.

Puisque j'ai peuplé ces objets moi-même, EF suppose que ce sont de nouveaux objets et doivent être insérés avec l'objet parent. Cependant, ces définitions existent déjà et je ne souhaite pas créer de doublons dans la base de données. J'utilise uniquement l'objet EF pour effectuer une recherche et remplir la clé étrangère dans mon entité de table principale.

Même avec les objets enfants qui sont de vraies données, je dois d'abord enregistrer le parent et obtenir une clé primaire ou EF semble tout gâcher. J'espère que cela donne quelques explications.

77
Mark Micallef

Autant que je sache, vous avez deux options.

Option 1)

Null tous les objets enfants, cela garantira que EF ne rien ajouter. Cela ne supprimera rien non plus de votre base de données.

Option 2)

Définissez les objets enfants comme étant détachés du contexte à l'aide du code suivant

 context.Entry(yourObject).State = EntityState.Detached

Notez que vous ne pouvez pas détacher un List/Collection. Vous devrez parcourir votre liste et détacher chaque élément de votre liste comme

foreach (var item in properties)
{
     db.Entry(item).State = EntityState.Detached;
}
48
Johan

Longue histoire: utilisez la clé étrangère pour économiser votre journée.

Supposons que vous avez une entité Ecole et une entité Ville, et il s'agit d'une relation plusieurs-à-un où une ville a plusieurs écoles et une école appartient à une ville. Et supposons que les villes existent déjà dans la table de recherche, vous ne souhaitez donc PAS qu’elles soient réinsérées lors de l’insertion d’une nouvelle école.

Initialement, vous pourriez définir vos entités comme ceci:

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

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

    [Required]
    public City City { get; set; }
}

Et vous pourriez faire l’insertion Ecole comme ceci (supposons que vous ayez déjà la propriété Ville affectée à la newItem):

public School Insert(School newItem)
{
    using (var context = new DatabaseContext())
    {
        context.Set<School>().Add(newItem);
        // use the following statement so that City won't be inserted
        context.Entry(newItem.City).State = EntityState.Unchanged;
        context.SaveChanges();
        return newItem;
    }
}

L'approche ci-dessus peut parfaitement fonctionner dans ce cas, cependant, je préfère l'approche clé étrangère qui est pour moi plus claire et flexible. Voir la solution mise à jour ci-dessous:

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

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

    [ForeignKey("City_Id")]
    public City City { get; set; }

    [Required]
    public int City_Id { get; set; }
}

De cette manière, vous définissez explicitement que School a une clé étrangère City_Id et se réfère à City. Ainsi, s’agissant de l’insertion de Ecole, vous pouvez faire:

    public School Insert(School newItem, int cityId)
    {
        if(cityId <= 0)
        {
            throw new Exception("City ID no provided");
        }

        newItem.City = null;
        newItem.City_Id = cityId;

        using (var context = new DatabaseContext())
        {
            context.Set<School>().Add(newItem);
            context.SaveChanges();
            return newItem;
        }
    }

Dans ce cas, vous spécifiez explicitement le City_Id du nouvel enregistrement et supprimez le Ville du graphique afin que EF ne se soucie pas de l'ajouter au contexte avec - école.

Bien que, à la première impression, l’approche de clé étrangère semble plus compliquée, mais croyez-moi, cette mentalité vous fera gagner beaucoup de temps lorsqu’il s’agit d’insérer une relation plusieurs-à-plusieurs (imaginer que vous avez une relation école-étudiant a une propriété de la ville) et ainsi de suite.

J'espère que ça te sera utile.

22
tooatui

Si vous souhaitez simplement stocker les modifications apportées à un objet parent et éviter de stocker les modifications apportées à l'un de ses objets enfant, pourquoi ne pas simplement procéder comme suit:

using (var ctx = new MyContext())
{
    ctx.Parents.Attach(parent);
    ctx.Entry(parent).State = EntityState.Added;  // or EntityState.Modified
    ctx.SaveChanges();
}

La première ligne attache l'objet parent et le graphique entier de ses objets enfants dépendants au contexte dans l'état Unchanged.

La deuxième ligne modifie l'état de l'objet parent uniquement, laissant ses enfants dans l'état Unchanged.

Notez que j'utilise un contexte nouvellement créé, cela évite donc d'enregistrer d'autres modifications dans la base de données.

15
keykey76

L'une des solutions suggérées consiste à affecter la propriété de navigation à partir du même contexte de base de données. Dans cette solution, la propriété de navigation attribuée en dehors du contexte de la base de données serait remplacée. Veuillez vous reporter à l'exemple suivant pour l'illustration.

class Company{
    public int Id{get;set;}
    public Virtual Department department{get; set;}
}
class Department{
    public int Id{get; set;}
    public String Name{get; set;}
}

Enregistrement dans la base de données:

 Company company = new Company();
 company.department = new Department(){Id = 45}; 
 //an Department object with Id = 45 exists in database.    

 using(CompanyContext db = new CompanyContext()){
      Department department = db.Departments.Find(company.department.Id);
      company.department = department;
      db.Companies.Add(company);
      db.SaveChanges();
  }

Microsoft en fait une fonctionnalité, mais je trouve cela agaçant. Si l'objet département associé à l'objet société a un identifiant qui existe déjà dans la base de données, pourquoi EF n'associe-t-il pas simplement l'objet société à l'objet base de données? Pourquoi devrions-nous avoir besoin de prendre soin de l'association par nous-mêmes? Prendre soin de la propriété de navigation lors de l'ajout d'un nouvel objet revient à déplacer les opérations de la base de données de SQL en C #, ce qui est fastidieux pour les développeurs.

12

Tout d'abord, vous devez savoir qu'il existe deux manières de mettre à jour une entité dans EF.

  • Objets attachés

    Lorsque vous modifiez la relation des objets attachés au contexte de l'objet à l'aide de l'une des méthodes décrites ci-dessus, Entity Framework doit conserver les clés étrangères, les références et les collections synchronisées.

  • Objets déconnectés

    Si vous travaillez avec des objets déconnectés, vous devez gérer manuellement la synchronisation.

Dans l'application que je suis en train de construire, le modèle d'objet EF n'est pas chargé à partir de la base de données, mais utilisé en tant qu'objets de données que je remplis lors de l'analyse d'un fichier plat.

Cela signifie que vous travaillez avec un objet déconnecté, mais il n'est pas clair si vous utilisez association indépendante ou association de clé étrangère .

  • Ajouter

    Lors de l'ajout d'une nouvelle entité avec un objet enfant existant (objet existant dans la base de données), si l'objet enfant n'est pas suivi par EF, l'objet enfant sera réinséré. Sauf si vous attachez d'abord l'objet enfant manuellement.

    db.Entity(entity.ChildObject).State = EntityState.Modified;
    db.Entity(entity).State = EntityState.Added;
    
  • Mise à jour

    Vous pouvez simplement marquer l'entité comme modifiée, puis toutes les propriétés scalaires seront mises à jour et les propriétés de navigation seront simplement ignorées.

    db.Entity(entity).State = EntityState.Modified;
    

Graph Diff

Si vous voulez simplifier le code lorsque vous travaillez avec un objet déconnecté, vous pouvez essayer librairie de diff. Graphes .

Voici l'introduction, Introduction à GraphDiff pour Entity Framework Code First - Autorisation de mises à jour automatisées d'un graphique d'entités détachées .

Exemple de code

  • Insérer une entité si elle n'existe pas, sinon mettre à jour.

    db.UpdateGraph(entity);
    
  • Insérer une entité si elle n'existe pas, sinon mettre à jour [~ # ~] et [~ # ~] insérer un objet enfant s'il n'existe pas, sinon mettre à jour.

    db.UpdateGraph(entity, map => map.OwnedEntity(x => x.ChildObject));
    
8
Yuliam Chandra

La meilleure façon de le faire est de remplacer la fonction SaveChanges dans votre contexte de données.

    public override int SaveChanges()
    {
        var added = this.ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Added);

        // Do your thing, like changing the state to detached
        return base.SaveChanges();
    }
3
Wouter Schut

Cela a fonctionné pour moi:

// temporarily 'detach' the child entity/collection to have EF not attempting to handle them
var temp = entity.ChildCollection;
entity.ChildCollection = new HashSet<collectionType>();

.... do other stuff

context.SaveChanges();

entity.ChildCollection = temp;
1
Klaus

Je sais que c'est un vieil article, mais si vous utilisez une approche code-premier, vous pouvez obtenir le résultat souhaité en utilisant le code suivant dans votre fichier de mappage.

Ignore(parentObject => parentObject.ChildObjectOrCollection);

Cela va essentiellement dire à EF d’exclure la propriété "ChildObjectOrCollection" du modèle afin qu’elle ne soit pas mappée à la base de données.

1
Syed Danish

J'ai le même problème lorsque j'essaie de sauvegarder un profil, je présente déjà une salutation et une nouvelle pour créer un profil. Lorsque j'insère le profil, il l'insère également dans la salutation. J'ai donc essayé comme ça avant savechanges ().

db.Entry (Profile.Salutation) .State = EntityState.Unchanged;

1
ZweHtatNaing

Avant d’ajouter le parent au dbset, déconnectez les collections enfants du parent, en veillant à transférer les collections existantes vers d’autres variables afin de pouvoir les utiliser ultérieurement, puis en remplaçant les collections enfants actuelles par de nouvelles collections vides. Définir les collections enfants sur null/rien ne semblait échouer pour nous. Ensuite, ajoutez le parent au dbset. De cette façon, les enfants ne sont pas ajoutés tant que vous ne le souhaitez pas.

0
Shaggie