web-dev-qa-db-fra.com

AddOrUpdate ne fonctionne pas comme prévu et produit des doublons

J'utilise la configuration EF5 basée sur DBContext de Code-First.

Dans DbMigrationsConfiguration.Seed j'essaie de remplir la base de données avec des données factices par défaut. Pour accomplir cette tâche, j'utilise la méthode DbSet.AddOrUpdate.

Le code le plus simple pour illustrer mon objectif:

j = 0;

var cities = new[]
    {
        "Berlin",
        "Vienna",
        "London",
        "Bristol",
        "Rome",
        "Stockholm",
        "Oslo",
        "Helsinki",
        "Amsterdam",
        "Dublin"
    };
var cityObjects = new City[cities.Length];


foreach (string c in cities)
{
    int id = r.NextDouble() > 0.5 ? 0 : 1;
    var city = new City
        {
            Id = j,
            Name = c,
            Slug = c.ToLowerInvariant(),
            Region = regions[id],
            RegionId = regions[id].Id,
            Reviewed = true
        };
    context.CitySet.AddOrUpdate(cc => cc.Id, city);
    cityObjects[j] = city;
    j++;
}

J'ai essayé d'utiliser/omettre le champ Id ainsi que d'utiliser la propriété Id/Slug comme sélecteur de mise à jour.

lorsque Update-Database est exécuté, le champ Id est ignoré et la valeur est générée automatiquement par SQL Server et la base de données est remplie de doublons; Slug selector autorise les doublons et produit des exceptions lors des exécutions ultérieures (Sequence contains more than one element).

La méthode AddOrUpdate est-elle conçue pour fonctionner de cette façon? Devrais-je effectuer l'upsert à la main?

19
berezovskyi

D'abord (pas encore de réponse), AddOrUpdate peut être appelé avec un tableau de nouveaux objets, vous pouvez donc simplement créer un tableau de type City[] et appeler une fois context.CitySet.AddOrUpdate(cc => cc.Id, cityArray);.

(édité)

Deuxièmement, AddOrUpdate utilise l'identificateur expression (cc => cc.Id) pour rechercher les villes avec le même Id que celles du tableau. Ces villes seront mises à jour. Les autres villes du tableau seront insérées, mais leurs valeurs Id seront générées par la base de données, car Id est une colonne d'identité. Il ne peut pas être défini par une instruction insert. (Sauf si vous définissez Insertion identité sur). Par conséquent, lorsque vous utilisez AddOrUpdate pour les tables avec des colonnes d'identité, vous devez trouver un autre moyen d'identifier les enregistrements, car les valeurs Id des enregistrements existants sont imprévisibles.

Dans votre cas, vous avez utilisé Slug comme identifiant pour AddOrUpdate, qui devrait être unique (selon votre commentaire). Je ne comprends pas pourquoi cela ne met pas à jour les enregistrements existants avec les noms correspondants Slugs.

J'ai mis en place un petit test: ajouter ou mettre à jour une entité avec un ID (iedntity) et un nom unique:

var n = new Product { ProductID = 999, ProductName = "Prod1", UnitPrice = 1.25 };
Products.AddOrUpdate(p => p.ProductName, n);
SaveChanges();

Lorsque "Prod1" n’est pas encore là, il est inséré (en ignorant l’ID 999).
Si c'est le cas et que UnitPrice est différent, il est mis à jour.

En regardant les requêtes émises, je constate que EF recherche un enregistrement unique par son nom:

SELECT TOP (2) 
[Extent1].[ProductID] AS [ProductID], 
[Extent1].[ProductName] AS [ProductName], 
[Extent1].[UnitPrice] AS [UnitPrice]
FROM [dbo].[Products] AS [Extent1]
WHERE N'Prod1' = [Extent1].[ProductName]

Et ensuite (lorsqu'une correspondance est trouvée et que UnitPrice est différent)

update [dbo].[Products]
set [UnitPrice] = 1.26
where ([ProductID] = 15)

Cela montre que EF a trouvé un enregistrement et utilise maintenant le champ clé pour effectuer la mise à jour.

J'espère que voir cet exemple éclaircira votre situation. Peut-être devriez-vous également surveiller les instructions SQL et voir si quelque chose d'inattendu s'y produit.

28
Gert Arnold
var paidOutType = new List<PaidOutType>
                {
                    new PaidOutType { PaidOutTypeID = 1, Code = "001", Description = "PAID OUT 1", PType = "1", Amount = 0, IsSalesSummery = true,DayFrom=1,DayTo=31 },
                    new PaidOutType { PaidOutTypeID = 2, Code = "002", Description = "PAID OUT 2", PType = "1", Amount = 0, IsSalesSummery = true,DayFrom=1,DayTo=31 },
                    new PaidOutType { PaidOutTypeID = 3, Code = "002", Description = "PAID OUT 3", PType = "1", Amount = 0, IsSalesSummery = true,DayFrom=1,DayTo=31 },
                };
                paidOutType.ForEach(u => smartPOSContext.PaidOutType.AddOrUpdate(u));
                smartPOSContext.SaveChanges();
0
RAJITHA DINESH