7

En una relación unidireccional muchos-a-muchos entre Registration y Item, donde un Registration tiene un ISet<Item> ItemsPurchased y Item tiene ninguna referencia de nuevo a registros (que no es una forma útil para explorar el gráfico de objetos), cuando miro el SQL que se genera, veoFluido NHibernate - actualización innecesaria

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 

los parámetros pasados ​​a la actualización son correctas, pero nada sobre el artículo ha cambiado, por lo que no es necesaria la actualización.

El mapeo se realiza automatizando con esta anulación en lugar de Registration y sin anulaciones para Item. DB Schema parece completamente correcto. Eliminé todas las convenciones y probé de nuevo y el comportamiento persistió, por lo que no es una de mis convenciones de mapeo lo que hace esto.

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

¿Por qué es NHibernate haciendo este UPDATE llamada y qué se puede hacer para detenerlo? Realmente no está lastimando nada, pero sugiere que hice algo mal, así que me gustaría descubrir qué.

Editar: Per comentario a continuación, he creado una prueba de unidad que crea un Event (Item deben pertenecer a una Event), añade dos Items a ella, desaloja el primero de la sesión y elimina la sesión, a continuación, obtiene el primer de vuelta por su ID.

noto algo extraño en la línea de artículos Seleccione abajo (segunda desde la parte inferior)

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

La tabla se crea correctamente:

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

Los DateTime son deliberadamente anulable porque un objeto que puede ser que necesite ser específico de la fecha (un ejemplo de algo sería el "registro anticipado").

+0

Para confirmar un problema de actualización fantasma, 'Obtenga' ese mismo elemento y compruebe si se ha emitido una actualización sobre flush/commit. Entonces puede descartar cualquier problema de cascada de muchos a muchos. – dotjoe

+0

¿Podría esto estar relacionado con tener un campo decimal? Google muestra algunos resultados para las Actualizaciones Phantom y el decimal, pero todavía no he llegado al final de lo que dicen. –

Respuesta

9

Esto se llama: Phantom Actualizaciones, por lo general se relaciona con el mapeo de los objetos

Ésta es la causa primaria:

Imaginemos que tenemos un objeto como éste

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

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

Tenga en cuenta que la ReorderLevel aceptará nulos

Si guarda esta entidad sin especificar un ReorderLevel se guardará con un valor null, pero luego cuando se carga de nuevo de la base de datos, ya que el tipo ReorderLevel es int, una Se agregará 0 que causará que la entidad se marque como sucia y por lo tanto causará una actualización

Este tipo de errores son difíciles de detectar y rastrear, le recomiendo usar los tipos Nullable<> cuando realmente desea un nulo en la base de datos

La forma en que suele lograr esto es para crear una convención que se ajusta automáticamente a mi Value Types a null si se declaran con Nullable<>, de lo contrario el campo será marcado como NotNullable

Sólo para complementar, así es como se ve mi convención como:

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

Ya tengo una 'ColumnNullConvention 'que garantiza que cualquier campo marcado con' DataAnnotations.Required' se declare como nulo. –

+0

He editado la pregunta original para mostrar el SQL para un simple ciclo de crear-desalojar-enjuagar-obtener e incluí el mapeo de tablas. –

+1

El problema es con Price 'field', como se puede ver, se inserta el valor 100.42 pero luego se actualiza con 100.42000 Creo que está marcando a la entidad como sucia.¿Tiene lógica personalizada para formatear el campo 'Precio' ?, también podría actualizar la publicación con la asignación del campo' Precio' y su entidad? – Jupaol

1

Como se menciona arriba (debajo? quién sabe. Busque el comentario que dejé en la otra respuesta), noté que la diferencia entre la prueba de unidad CanGenerateDatabaseSchema y la prueba de unidad CanGetItem fue que uno me daba DECIMAL (6,2) y el otro me daba DECIMAL (19,0).

Busqué más y me di cuenta de que CanGenerateDatabaseSchema estaba usando mi configuración "real" (del proyecto web) y la otra prueba estaba usando mi configuración de "prueba de unidad". Las pruebas de mi unidad se estaban ejecutando contra Sql Server CE ... cuando cambié mis pruebas de unidad para usar la misma configuración que mi base de datos real (Sql Server 2005), de repente la actualización fantasma desapareció.

Si alguien más se topa con actualizaciones Phantom inesperadas con decimales ... verifique si está utilizando Sql Server CE. Como la prueba en realidad está pasando (el comentario que dice que está fallando es incorrecto, no está fallando, simplemente está haciendo un trabajo extra), creo que viviré con eso, aunque por qué Sql CE está ignorando mi configuración es una buena pregunta, y una posible error NH o FNH.

Cuestiones relacionadas