2012-12-08 8 views
17

Ich verwende Code-First DBContext-basierte EF5-Setup.AddOrUpdate funktioniert nicht wie erwartet und erzeugt Duplikate

In DbMigrationsConfiguration.Seed Ich versuche, DB mit Standard-Dummy-Daten zu füllen. Um diese Aufgabe zu erfüllen, verwende ich DbSet.AddOrUpdate Methode.

Die einfachste Code mein Ziel zu veranschaulichen:

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

Ich habe versucht Id Feld als auch zu verwenden/auslassen als Id/Slug Eigenschaft als Update-Selektor zu verwenden.

Wenn Update-Database ausgeführt wird, wird Id Feld ignoriert und der Wert wird automatisch von SQL Server generiert und DB ist mit Duplikaten gefüllt; Slug Selektor erlaubt Duplikate und bei nachfolgenden Läufen erzeugt Ausnahmen (Sequence contains more than one element).

Ist AddOrUpdate Methode so zu arbeiten? Sollte ich Upsert von Hand durchführen?

Antwort

26

First (noch keine Antwort), AddOrUpdate kann mit einer Reihe von neuen Objekten aufgerufen werden, so dass Sie nur ein Array vom Typ City[] und context.CitySet.AddOrUpdate(cc => cc.Id, cityArray); einmal anrufen erstellen können.

(bearbeitet)

Zweitens verwendet AddOrUpdate die Kennung Ausdruck (cc => cc.Id) Städte zu finden, mit den gleichen Id wie der, die in der Anordnung. Diese Städte werden aktualisiert. Die anderen Städte im Array werden eingefügt, aber ihre Id Werte werden von der Datenbank generiert, da Id eine Identitätsspalte ist. Es kann nicht durch eine insert-Anweisung festgelegt werden. (Es sei denn, Sie legen Identity Insert fest). Wenn Sie also AddOrUpdate für Tabellen mit Identitätsspalten verwenden, sollten Sie eine andere Möglichkeit zum Identifizieren von Datensätzen finden, da die ID-Werte vorhandener Datensätze nicht vorhersehbar sind.

In Ihrem Fall haben Sie Slug als Kennung für AddOrUpdate verwendet, die eindeutig sein sollte (gemäß Ihrem Kommentar). Es ist mir nicht klar, warum das bestehende Datensätze nicht mit übereinstimmenden Slug s aktualisiert.

stelle ich einen kleinen Test auf: Hinzufügen oder eine Entität aktualisieren mit einem Id (iedntity) und einen eindeutigen Namen:

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

Wenn „Prod1“ ist noch nicht da, er eingesetzt wird (ohne Berücksichtigung von Id 999).
Wenn es ist und UnitPrice ist anders, wird es aktualisiert.

Mit Blick auf die emittierten Abfragen ich sehe, dass EF ist für einen einzigartigen Datensatz nach Namen suchen:

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] 

Und weiter (wenn eine Übereinstimmung gefunden wird und UnitPrice unterscheidet)

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

Diese zeigt, dass EF einen Datensatz gefunden hat und nun das Schlüsselfeld für die Aktualisierung verwendet.

Ich hoffe, dass dieses Beispiel ein wenig Licht auf Ihre Situation wirft. Vielleicht sollten Sie auch die SQL-Anweisungen überwachen und sehen, ob dort etwas Unerwartetes passiert.

+0

Gert, danke für den Versuch zu helfen! Tatsächlich wurde City die ID = j gegeben, die von 0 bis 9 reicht. Sie wurden gerade von ähnlichen Variablennamen ausgetrickst. Als nächstes habe ich bereits den Effekt der Verwendung von Name (in meinem Fall Slug, wie es wirklich einzigartig ist) erwähnt - es ist das Duplizieren in die DB. Das Übergeben aller Objekte als ein Array führte zu keinem Fortschritt. – berezovskyi

+0

Vielen Dank für die Aktualisierung Ihrer Antwort. Ich kann es nicht mehr verifizieren. Wenn jemand aus der Community unter Ihrer Antwort kommentiert, dass es ihm/ihr geholfen hat, werde ich Ihre Antwort als akzeptiert markieren. Entschuldigung, dass es so lange gedauert hat. – berezovskyi

+1

Gibt es eine Idee, wie der Bezeichnerausdruck aussehen würde, wenn zwei Felder benötigt würden, um den Datensatz eindeutig zu identifizieren? Ich denke p => p.ProductName, p.CategoryName (aber das funktioniert natürlich nicht). – 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();