2012-12-08 6 views
17

Używam konfiguracji EF5 opartej na Code-First DBContext.AddOrUpdate działa niezgodnie z oczekiwaniami i produkuje duplikaty.

W DbMigrationsConfiguration.Seed Próbuję wypełnić DB domyślnymi danymi fikcyjnymi. Aby wykonać to zadanie, używam metody DbSet.AddOrUpdate.

Najprostszy kod do zilustrowania mój cel:

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++; 
} 

Próbowałem użyć/pomijania Id dziedzinie, jak również do korzystania Id/Slug nieruchomości jako selektor aktualizacji.

po uruchomieniu Update-Database, pole Id jest ignorowane, a wartość jest generowana automatycznie przez SQL Server, a DB jest wypełniane duplikatami; Slug selektor umożliwia duplikaty, a na kolejnych przebiegach tworzy wyjątki (Sequence contains more than one element).

Czy metoda ma działać w ten sposób? Czy powinienem wykonywać upsert ręcznie?

Odpowiedz

26

Po pierwsze (brak odpowiedzi), AddOrUpdate można wywołać z tablicą nowych obiektów, dzięki czemu można po prostu utworzyć tablicę typu City[] i raz wywołać context.CitySet.AddOrUpdate(cc => cc.Id, cityArray);.

(edytowane)

drugie, AddOrUpdate używa wyrażenia identyfikator (cc => cc.Id) w celu znalezienia miejscowości o tej samej Id jak te w tablicy. Te miasta zostaną zaktualizowane. Pozostałe miasta w tablicy zostaną wstawione, ale ich wartości Id zostaną wygenerowane przez bazę danych, ponieważ Id jest kolumną tożsamości. Nie można go ustawić za pomocą instrukcji insert. (O ile nie ustawisz wstawienia tożsamości na). Dlatego przy korzystaniu z AddOrUpdate dla tabel z kolumnami tożsamości należy znaleźć inny sposób identyfikacji rekordów, ponieważ wartości Id istniejących rekordów są nieprzewidywalne.

W twoim przypadku użyłeś Slug jako identyfikatora dla AddOrUpdate, który powinien być unikalny (jak na twój komentarz). Nie jest dla mnie jasne, dlaczego to nie aktualizuje istniejących rekordów z dopasowaniem Slug s.

założyłem mały test: dodać lub zaktualizować podmiot z identyfikatorem (iedntity) i unikalną nazwą:

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

Kiedy „Prod1” nie jest tam jeszcze, to jest włożona (pomijając Id 999).
Jeśli jest i UnitPrice jest inny, jest aktualizowany.

Patrząc emitowanych zapytaniami widzę, że EF poszukuje unikalnego rekordu według nazwy:

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] 

A obok (gdy zostanie znaleziony i UnitPrice różni)

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

ten pokazuje, że EF znalazł jeden rekord i teraz używa klucza do aktualizacji.

Mam nadzieję, że zobaczenie tego przykładu rzuci nieco światła na twoją sytuację. Może powinieneś również monitorować instrukcje SQL i sprawdzać, czy dzieje się tam coś nieoczekiwanego.

+0

Gert, dziękuję za pomoc! Właściwie miasto otrzymało "id = j", który zawiera się w przedziale od 0 do 9. Zostaliście po prostu oszukani przez podobne nazwy zmiennych. Następnie wspomniałem już o wpływie używania nazwy (w moim przypadku Slug, ponieważ jest naprawdę wyjątkowy) - pozwala to na powielenie w DB. Przekazywanie wszystkich obiektów jako tablicy nie przyniosło żadnego postępu. – berezovskyi

+0

Dziękujemy za zaktualizowanie odpowiedzi. Nie mogę już tego zweryfikować. Jeśli ktokolwiek ze społeczności skomentuje pod twoją odpowiedzią, że mu to pomogło, zaznaczę twoją odpowiedź jako zaakceptowaną. Przepraszam, że to trwało tak długo. – berezovskyi

+1

Jakieś pojęcie, w jaki sposób wyglądałoby wyrażenie identyfikatora, gdyby wymagało dwóch pól, aby jednoznacznie zidentyfikować rekord? Myślę, że p => p.ProductName, p.CategoryName (ale oczywiście to nie działa). – Jarvis

1
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(); 
Powiązane problemy