web-dev-qa-db-fra.com

La bonne façon d'insérer plusieurs enregistrements dans une table à l'aide de LINQ to Entities

Comme bon nombre d’entre nous, j’ai mis en place une simple boucle permettant d’ajouter plusieurs enregistrements à partir d’une base de données. Un exemple prototype serait quelque chose comme ceci:

Méthode I:

// A list of product prices
List<int> prices = new List<int> { 1, 2, 3 };

NorthwindEntities NWEntities = new NorthwindEntities();

foreach (int price in prices)
{
   Product newProduct = new Product();
   newProduct.Price = price;
   NWEntities.Products.AddObject(newProduct);
}

NWEntities.SaveChanges();

Cependant, lorsque j'ai créé la boucle pour la première fois, j’ai écrit intuitivement:

Méthode II:

Product newProduct = new Product();

foreach (int price in prices)
{
   newProduct.Price = price;
   NWEntities.Products.Add(newProduct);
}

Après une petite lecture, plusieurs personnes ont mentionné que si la Méthode II était utilisée, un seul enregistrement serait ajouté au tableau. Cela semble contre-intuitif. C'est la fonction Add () qui charge une nouvelle insertion et crée, je pense, un objet après chaque appel avec les données transmises. Déclarer mon objet Product extérieur la boucle semblerait mieux utiliser les ressources, en effet, le seul temps système consommé dans chaque appel serait la réaffectation de la propriété d'instance d'objet, et non la reconstruction de l'instance d'objet elle-même. 

Quelqu'un peut-il clarifier s'il vous plaît? Je ne pouvais pas trouver un autre post qui traite directement de cette question. S'il y en a un, veuillez le pointer. 

25
seebiscuit

Il suffit de déplacer l'instanciation du nouveau produit dans la boucle. Votre code tel qu’il est écrit ajoutera une seule instance plusieurs fois, ce qui ne produira pas ce que vous êtes après ... vous avez besoin d’une instance distincte de chaque produit contexte et le marque pour l'insertion.

foreach (int price in prices)
{
   Product newProduct = new Product();
   newProduct.Price = price;
   NWEntities.Products.Add(newProduct);
}

Pour voir ce qui se passe un peu plus explicitement, considérons ce qui suit: 

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Try to reuse same Instance:");
        using (var ctx = new AdventureWorksEntities())
        {
            List<int> ids = new List<int> {1, 2, 3}; 
            Product p1 = new Product();
            Product reference = p1;
            Product p2;
            Console.WriteLine("Start Count: {0}", ctx.Products.Count());
            foreach (var id in ids)
            {
                p1.ProductID = id;
                p2 = ctx.Products.Add(p1);
                Console.WriteLine("p1 = p2 ? {0}", p1 == p2);
                Console.WriteLine("p2 = reference? {0}", p2 == reference);
                Console.WriteLine("State: {0}", ctx.Entry(p1).State);
                var changes = ctx.ChangeTracker.Entries<Product>();
                Console.WriteLine("Change Count: {0}", changes.Count());
            }
        }
        Console.WriteLine();
        Console.WriteLine("Distinct Instances:");
        using (var ctx = new AdventureWorksEntities())
        {
            List<int> ids = new List<int> { 1, 2, 3 };
            Product p2;
            foreach (var id in ids)
            {
                var p1 = new Product {ProductID = id};
                p2 = ctx.Products.Add(p1);
                Console.WriteLine("p1 = p2 ? {0}", p1 == p2);
                Console.WriteLine("State: {0}", ctx.Entry(p1).State);
                var changes = ctx.ChangeTracker.Entries<Product>();
                Console.WriteLine("Change Count: {0}", changes.Count());
            }
        }

        Console.ReadLine();
    }
}

Dans la première boucle, vous réutilisez la même instance de produit, mais lorsque vous l'ajoutez au contexte, vous utilisez simplement la même référence à chaque fois. Vous pouvez constater que le nombre de modifications reste à 1 quel que soit le nombre de fois que la boucle est exécutée. Bien entendu, seules les dernières valeurs seraient enregistrées si vous appeliez ctx.SaveChanges ().

Dans la deuxième version, le nombre de modifications est correctement incrémenté à chaque fois et vous appelez SaveChanges pour enregistrer toutes les entités distinctes comme prévu.

23
terryt

+1 Pour la réponse de Terryt. Vous devez vous en tenir à la première méthode ou à quelque chose de similaire.

Dans Entity Framework 6 version, une nouvelle méthode permet d’ajouter un ensemble de données dans une seule instruction. C'est la méthode AddRange .

Je voudrais ajouter que je trouve la méthode AddRange élégante lorsque vous souhaitez ajouter des entités basées sur une liste existante (ou IEnumerable). 

Dans votre cas, cela pourrait se faire un peu comme ceci:

NWEntities.Products.AddRange(
    Prices.Select(priceitem =>
    new Product{price = priceitem})
)

Sémantiquement, cela devrait être similaire à votre méthode 1. Un objet Produit est instancié par prix dans la liste de prix. Il y a cependant une différence, c'est fait anonymement, donc il n'y a pas de variables de référence définies explicitement pointant vers le nouvel objet.

Si les performances sont importantes, cette question peut vous donner des informations supplémentaires: Méthode la plus rapide d’insertion dans Entity Framework

J'espère que cela vous aidera.

14
karstenols

Nous n'aurions pas besoin de l'aide de loop. Nous pouvons le faire par linq. Comme dans le code ci-dessous, les noms doivent être ajoutés à la table des employés à partir de la liste de noms avec le champ de bits IsDeleted.

db.Employee.AddRange(
   nameList.Select(name =>
      new Employee
      {
           Name = name,
           IsDeleted = false
      })
   );
1
Manoj VijayaKumar

J'avais un problème similaire. Dans mon numéro, j'avais ce code: 

        var cratelist = db.TruckContainerLoads.Where(x => x.TruckID == truckid).Select(x => x.ContainerID);
        if (!cratelist.Any())
        {
            return;
        }
        foreach (var crateid in cratelist) {
            TruckContainerLoad crInstance = new TruckContainerLoad();
            crInstance.ContainerID = crateid;
            try
            {
                db.TruckContainerLoads.Add(crInstance);
                db.SaveChanges();
            }
            catch
            {
                return;
            }
        }

Ma requête n'a ajouté que le premier enregistrement de mon foreach. Le problème était que je devais appeler mon db.SaveChanges () en dehors de la boucle foreach, après avoir ajouté plusieurs enregistrements. Pour moi, la réponse à mon problème était en fait dans la question. Je vote donc sur la question. 

0
Patrick Knott