7

In einer unidirektionalen many-to-many-Beziehung zwischen Registration und Item, wo ein Registration hat eine ISet<Item> ItemsPurchased und Item keinen Rückbezug auf Registrierungen haben (es ist kein brauchbarer Weg, um die Objektgraphen zu erkunden), wenn ich schaue auf die SQL erzeugt wird, sehe ichFluent NHibernate - Unnötige Update

INSERT INTO Registrations_Items (RegistrationId, ItemId) VALUES (@p0, @p1);@p0 = 1 [Type: Int32 (0)], @p1 = 1 [Type: Int32 (0)] 
UPDATE Items SET Price = @p0, Name = @p1, [...], ListIndex = @p5, EventId = @p6 WHERE ItemId = @p7 

die Parameter für das Update vergangen sind korrekt, aber nichts über den Punkt hat sich geändert, so dass das Update nicht erforderlich ist.

Die Zuordnung erfolgt durch automatisches Überschreiben mit dieser Überschreibung anstelle von Registration und keine Überschreibungen für Item. DB-Schema sieht vollständig korrekt aus. Ich entfernte alle Konventionen und testete erneut und das Verhalten blieb bestehen, so dass es keine meiner Zuordnungskonventionen ist, die dies tun.

mapping.HasManyToMany(e => e.ItemsPurchased).AsSet().Cascade.All().Not.Inverse();

Warum macht NHibernate diese UPDATE Anruf und was kann ich tun, um es zu stoppen? Es tut nicht wirklich weh, aber es deutet darauf hin, dass ich etwas falsch gemacht habe, also würde ich gerne herausfinden, was.

Edit: Per Kommentar unten, habe ich einen Komponententest, der ein Event erzeugt (Item zu einem Event gehören müssen), fügt zwei Items es, die erste von Sitzung evicts und Sitzungs spült, dann erhält der erste zurück durch seine ID.

Ich bemerke etwas Merkwürdiges in der Zeile SELECT Elemente unten (2. von unten)

INSERT INTO Events (blah blah blah...) 
select @@IDENTITY 
INSERT INTO Items (Price, Name, StartDate, EndDate, ExternalID, ListIndex, EventId) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);@p0 = 100.42 [Type: Decimal (0)], @p1 = 'Item 1' [Type: String (0)], @p2 = NULL [Type: DateTime (0)], @p3 = NULL [Type: DateTime (0)], @p4 = '123' [Type: String (0)], @p5 = 0 [Type: Int32 (0)], @p6 = 1 [Type: Int32 (0)] 
select @@IDENTITY 
SELECT blah blah blah FROM Events event0_ WHERE [email protected];@p0 = 1 [Type: Int32 (0)] 
SELECT itemsforsa0_.EventId as EventId1_, itemsforsa0_.ItemId as ItemId1_, itemsforsa0_.ListIndex as ListIndex1_, itemsforsa0_.ItemId as ItemId3_0_, itemsforsa0_.Price as Price3_0_, itemsforsa0_.Name as Name3_0_, itemsforsa0_.StartDate as StartDate3_0_, itemsforsa0_.EndDate as EndDate3_0_, itemsforsa0_.ExternalID as ExternalID3_0_, itemsforsa0_.ListIndex as ListIndex3_0_, itemsforsa0_.EventId as EventId3_0_ FROM Items itemsforsa0_ WHERE [email protected];@p0 = 1 [Type: Int32 (0)] 
UPDATE Items SET Price = @p0, Name = @p1, StartDate = @p2, EndDate = @p3, ExternalID = @p4, ListIndex = @p5, EventId = @p6 WHERE ItemId = @p7;@p0 = 100.42000 [Type: Decimal (0)], @p1 = 'Item 1' [Type: String (0)], @p2 = NULL [Type: DateTime (0)], @p3 = NULL [Type: DateTime (0)], @p4 = '123' [Type: String (0)], @p5 = 0 [Type: Int32 (0)], @p6 = 1 [Type: Int32 (0)], @p7 = 1 [Type: Int32 (0)] 

Die Tabelle korrekt erstellt:

create table Items (
    ItemId INT IDENTITY NOT NULL, 
    Price NUMERIC(19,5) not null, 
    Name NVARCHAR(255) not null, 
    StartDate DATETIME null, 
    EndDate DATETIME null, 
    ExternalID NVARCHAR(255) not null, 
    ListIndex INT not null, 
    EventId INT not null, 
    primary key (ItemId) 
) 

Die Datetime sind bewusst auf NULL festlegbare, weil ein Artikel nicht benötigen datumsspezifisch sein (ein Beispiel für etwas, das "Frühbucherregistrierung" wäre).

+0

Um ein Phantom-Update-Problem zu bestätigen, "Get" das gleiche Element und prüfen, ob eine Aktualisierung auf Flush/Commit ausgegeben wird. Dann können Sie jedes kaskadierende Problem von vielen zu vielen ausschließen. – dotjoe

+0

Könnte dies mit einem Dezimalfeld zusammenhängen? Google zeigt einige Ergebnisse für Phantom Updates und Dezimalzahlen an, aber ich bin noch nicht auf den Grund gekommen, was sie alle sagen. –

Antwort

9

Das heißt: Das ist die primäre Ursache Phantom Updates, ist es in der Regel mit der Abbildung Ihrer Objekte

bezogen werden:

Stellen haben wir ein Objekt wie dieses

public class Product 
{ 
    public Guid Id { get; set; } 
    public int ReorderLevel { get; set; } 
    public decimal UnitPrice { get; set; } 
} 

und eine Karte:

public class ProductMap : ClassMap<Product> 
{ 
    public ProductMap() 
    { 
     Not.LazyLoad(); 
     Id(x => x.Id).GeneratedBy.GuidComb(); 
     Map(x => x.ReorderLevel); 
     Map(x => x.UnitPrice).Not.Nullable(); 
    } 
} 

Beachten Sie, dass die ReorderLevel nulls

annehmen Wenn Sie diese Einheit speichern, ohne ein ReorderLevel Angabe wird es mit einem null Wert gespeichert werden, aber dann, wenn Sie es wieder aus der Datenbank geladen werden, da der ReorderLevel Typ int ist, ein wird hinzugefügt, die bewirkt, dass auf das Unternehmen als schmutzig markiert werden und deshalb wird es dazu führen, Update

Diese Art von Fehler nur schwer zu erkennen und zu verfolgen, empfehle ich Ihnen Nullable<> Arten zu verwenden, wenn Sie wirklich eine null wollen in der Datenbank

Die Art, wie ich dies in der Regel zu tun ist eine Konvention zu erstellen, die automatisch meine Value Types zu null gesetzt werden, wenn sie mit Nullable<> deklariert werden, sonst wird das Feld als NotNullable markiert

einfach zu ergänzen, ist dies, wie meine Konvention sieht wie:

mapper.BeforeMapProperty += (ins, memb, cust) => 
    { 
     var type = memb.LocalMember.GetPropertyOrFieldType(); 

     if (type.IsValueType) 
     { 
      if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) 
      { 
       cust.Column(x => { x.NotNullable(notnull: false); }); 
      } 
      else 
      { 
       cust.Column(x => { x.NotNullable(notnull: true); }); 
      } 
     } 
    } 
+0

Ich habe bereits eine 'ColumnNullConvention ', die sicherstellt, dass jedes Feld, das mit' DataAnnotations.Required' markiert ist, nicht null ist. –

+0

Ich habe die ursprüngliche Frage bearbeitet, um die SQL für einen einfachen Create-Evict-Flush-Get-Zyklus zu zeigen und die Tabellenzuordnung eingeschlossen. –

+1

Das Problem ist mit dem Preis 'Feld', wie Sie sehen können, es ist der Wert 100.42 eingefügt, aber dann wird es mit 100.42000 aktualisiert. Ich denke, das markiert die Entität als schmutzig.Verfügen Sie über benutzerdefinierte Logik zum Formatieren des Felds "Preis"? Könnten Sie den Post auch mit der Zuordnung des Felds "Preis" und seiner Entität aktualisieren? – Jupaol

1

Wie oben erwähnt (unten? Wer weiß. Suchen Sie nach dem Kommentar, den ich auf der anderen Antwort hinterlassen habe), ich bemerkte, dass der Unterschied zwischen dem CanGenerateDatabaseSchema Komponententest und dem CanGetItem Komponententest war, dass mir jemand DECIMAL (6,2) gab und der andere mir DECIMAL (19,0) gab.

Ich stocherte um mehr und erkannte, dass CanGenerateDatabaseSchema meine "echte" Konfiguration (aus dem Web-Projekt) und der andere Test meine "Unit-Test" -Konfig verwendete. Meine Komponententests wurden gegen Sql Server CE ausgeführt ... als ich meine Komponententests änderte, um die gleiche Konfiguration wie meine echte Datenbank (Sql Server 2005) zu verwenden, verschwand plötzlich das Phantomupdate.

Also, wenn jemand unerwartete Phantom Updates mit Dezimalzahlen läuft ... überprüfen Sie, ob Sie Sql Server CE verwenden. Da der Test tatsächlich vorbei ist (der Kommentar, der sagt, dass er versagt, ist falsch, er versagt nicht, macht nur zusätzliche Arbeit), ich denke, ich werde damit leben, obwohl Sql CE meine Konfiguration ignoriert, ist eine gute Frage, und a Möglicher NH- oder FNH-Fehler.