7

W jednokierunkowym związek wiele-do-wielu między Registration i Item, gdzie Registration, wyposażoną w ISet<Item> ItemsPurchased i Item ma odesłanie do rejestracji (nie jest to skuteczny sposób w celu zbadania wykres Object) przy patrzę na SQL generowany widzęPłynna NHibernate - Zbędne zmiana

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 

parametry przekazywane do aktualizacji są poprawne, ale nic o rzeczy uległ zmianie, więc aktualizacja nie jest potrzebna.

Mapowanie polega na automatyzacji za pomocą tego zastąpienia w miejsce dla Registration i bez przesłonięć dla Item. DB Schema wygląda całkowicie poprawnie. Usunąłem wszystkie konwencje i przetestowałem je ponownie, a zachowanie utrzymywało się, więc nie jest to żadna z moich konwencji mapowania, które to robią.

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

Dlaczego NHibernate podejmowania tego UPDATE rozmowę i co mogę zrobić, żeby go powstrzymać? To naprawdę nie rani niczego, ale sugeruje, że zrobiłem coś złego, więc chciałbym dowiedzieć się, co.

Edit: Per komentarzu poniżej, stworzyłem badanej jednostki, która tworzy Event (Item musi należeć do Event) dodaje dwa Items do niego evicts pierwszą z sesji i opróżnia sesji, a następnie pobiera pierwszy z powrotem przez jego identyfikator.

Zauważyłem coś dziwnego w wierszu wyboru elementów poniżej (2. od dołu)

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)] 

Stół jest utworzony poprawnie:

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) 
) 

W DateTimes są celowo pustych ponieważ przedmiot może nie trzeba być specyficzne dla daty (przykład czegoś, co byłoby "rejestracją wczesnego ptaka").

+0

Aby potwierdzić problem z aktualizacją fantomową, pobierz ten sam element i sprawdź, czy w opróżnieniu/zatwierdzeniu została wydana aktualizacja. Wtedy możesz wykluczyć problem kaskadowania wielu do wielu. – dotjoe

+0

Czy może to być związane z polem dziesiętnym? Google wyświetla wyniki dla Phantom Updates i dziesiętne, ale nie doszedłem do sedna tego, co wszyscy mówią. –

Odpowiedz

9

To się nazywa: Phantom Aktualizacje, to jest zwykle związane z mapowaniem obiektami

Jest to podstawowa przyczyna:

Wyobraźmy sobie, że mamy obiekt jak ten

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

i mapa:

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(); 
    } 
} 

Zauważ, że ReorderLevel zaakceptuje null

Jeśli zapiszesz ten podmiot bez podania ReorderLevel zostanie on zapisany z wartością null, ale wtedy, kiedy go załadować z powrotem z bazy danych, gdyż typ ReorderLevel jest int, A zostanie dodany, co spowoduje, że obiekt zostanie oznaczony jako zabrudzony i dlatego spowoduje aktualizację

Tego rodzaju błędy są trudne do wykrycia i śledzenia. Zalecam stosowanie typów Nullable<>, gdy naprawdę chcesz mieć wartość zerową w bazie danych

Sposób, w jaki zwykle osiągnięcia tego celu jest stworzenie konwencję, która automatycznie ustawia moje Value Types do null jeśli są one zadeklarowane z Nullable<>, w przeciwnym razie pola zostaną oznaczone jako NotNullable

Wystarczy uzupełnić, to jak moja konwencja wygląda na przykład:

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

Mam już 'ColumnNullConvention ', który zapewnia, że ​​pole oznaczone" DataAnnotations.Required "jest zadeklarowane jako nie puste. –

+0

Edytowałem oryginalne pytanie, aby wyświetlić kod SQL dla prostego cyklu tworzenia-usuwania-wypłukiwania i uwzględnienia mapowania tabel. –

+1

Problem dotyczy pola "Cena", jak widzisz, wstawiono wartość 100.42, ale potem jest ona aktualizowana o 100.42000 Myślę, że oznacza to obiekt jako brudny.Czy masz niestandardową logikę do sformatowania pola "Cena" ?, czy też możesz zaktualizować wpis mapowaniem pola "Cena" i jego jednostki? – Jupaol

1

Jak wspomniano powyżej (poniżej? kto wie. Poszukaj komentarza, który zostawiłem w drugiej odpowiedzi), zauważyłem, że różnica między testem jednostkowym CanGenerateDatabaseSchema a testem jednostki CanGetItem była taka, że ​​jedna dała mi DECIMAL (6,2), a druga dała mi DECIMAL (19,0).

Poszedłem dalej i zdałem sobie sprawę, że CanGenerateDatabaseSchema używał mojej "prawdziwej" konfiguracji (z projektu WWW), a drugi test wykorzystywał moją konfigurację "testu jednostkowego". Moje testy jednostkowe były uruchamiane przeciwko Sql Server CE ... kiedy zmieniłem moje testy jednostkowe, aby użyć tej samej konfiguracji co moja prawdziwa baza danych (Sql Server 2005), nagle aktualizacja phantom odeszła.

Więc jeśli ktokolwiek inny napotka nieoczekiwane aktualizacje Phantom z dziesiętnymi ... sprawdź, czy używasz Sql Server CE. Ponieważ test faktycznie mija (komentarz, który mówi, że to błąd jest niepoprawny, nie zawodzi, po prostu robi dodatkową pracę), myślę, że będę z tym żyć, chociaż dlaczego Sql CE ignoruje moją konfigurację to dobre pytanie, a możliwy błąd NH lub FNH.

Powiązane problemy