2009-04-22 9 views
1

J'ai essayé de faire en sorte que Nhibernate fonctionne avec un tableau d'octets en tant que mappage de version vers un horodatage sql. J'avais implémenté un IUserVersionType mais Nhibernate créait varbinary dans la base de données plutôt que l'horodatage. Inspiré par un post de blog par Ayende récemment sur la concurrence, j'ai changé ma cartographie pour spécifier le type sql à timestamp qui a fonctionné parfaitement. Cependant, je suis maintenant confronté à un problème assez curieux où Nhibernate fait une insertion, obtient la nouvelle version, puis essaie immédiatement de faire une mise à jour et tente de définir la colonne version , ce qui est un timestamp sql échoue.Colonnes d'horodatage NHibernate et sql en tant que version

Ceci est mon application:

<?xml version="1.0" encoding="utf-8"?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
assembly="Core.Domain, Version=0.1.3397.31993, Culture=neutral, 
PublicKeyToken=94dc7dc697cfcfc0" namespace="Core.Domain.Entities" 
default-lazy="false"> 
<class name="Contact" table="Contacts" xmlns="urn:nhibernate- 
mapping-2.2" optimistic-lock="version" dynamic-insert="true" dynamic- 
update="true"> 
    <id name="Id" type="Int32" column="Id"> 
    <generator class="identity" /> 
    </id> 
    <version name="Version" type="BinaryBlob" generated="always" 
unsaved-value="null"> 
    <column name="Version" sql-type="timestamp" not-null="false" /> 
    </version> 
    <property name="Title" type="String"> 
    <column name="Title" length="5" /> 
    </property> 
    <property name="FirstName" type="String"> 
    <column name="FirstName" not-null="true" length="50" /> 
    </property> 
    <property name="MiddleName" type="String"> 
    <column name="MiddleName" length="50" /> 
    </property> 
    <property name="LastName" type="String"> 
    <column name="LastName" not-null="true" length="50" /> 
    </property> 
    <property name="Suffix" type="String"> 
    <column name="Suffix" length="5" /> 
    </property> 
    <property name="Email" type="String"> 
    <column name="Email" length="50" /> 
    </property> 
    <bag name="PhoneNumbers" inverse="true" cascade="all-delete- 
orphan"> 
    <key foreign-key="FK_Contacts_PhoneNumbers_ContactId" on- 
delete="cascade" column="ContactId" /> 
    <one-to-many class="Core.Domain.Entities.PhoneNumber, 
Core.Domain, Version=0.1.3397.31993, Culture=neutral, 
PublicKeyToken=94dc7dc697cfcfc0" /> 
    </bag> 
    <property name="DateCreated" type="DateTime"> 
    <column name="DateCreated" /> 
    </property> 
    <property name="DateModified" type="DateTime"> 
    <column name="DateModified" /> 
    </property> 
    <property name="LastModifiedBy" type="String"> 
    <column name="LastModifiedBy" /> 
    </property> 
</class> 
</hibernate-mapping> 
<?xml version="1.0" encoding="utf-8"?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
assembly="Core.Domain, Version=0.1.3397.31993, Culture=neutral, 
PublicKeyToken=94dc7dc697cfcfc0" namespace="Core.Domain.Entities" 
default-lazy="false"> 
<class name="Customer" table="Customers" xmlns="urn:nhibernate- 
mapping-2.2" optimistic-lock="version" dynamic-insert="true" dynamic- 
update="true"> 
    <id name="Id" type="Int32" column="Id"> 
    <generator class="identity" /> 
    </id> 
    <version name="Version" type="BinaryBlob" generated="always" 
unsaved-value="null"> 
    <column name="Version" sql-type="timestamp" not-null="false" /> 
    </version> 
    <property name="AccountNumber" access="nosetter.pascalcase- 
underscore" type="String"> 
    <column name="AccountNumber" unique="true" length="25" /> 
    </property> 
<!-- other mappings... --> 
    <property name="DateCreated" type="DateTime"> 
    <column name="DateCreated" /> 
    </property> 
    <property name="DateModified" type="DateTime"> 
    <column name="DateModified" /> 
    </property> 
    <property name="LastModifiedBy" type="String"> 
    <column name="LastModifiedBy" /> 
    </property> 
    <joined-subclass name="Core.Domain.Entities.Individual, 
Core.Domain, Version=0.1.3397.31993, Culture=neutral, 
PublicKeyToken=94dc7dc697cfcfc0" table="Individuals"> 
    <key column="CustomerId" /> 
    <many-to-one fetch="join" lazy="false" not-null="true" 
cascade="all" unique="true" not-found="exception" name="Contact" 
column="ContactID" /> 
    <bag name="Addresses" table="Addresses_Individuals"> 
     <key column="AddressId" foreign- 
key="FK_Addresses_Individuals_Addresses_AddressId" /> 
     <many-to-many column="IndividualId" 
class="Core.Domain.Entities.Address, Core.Domain, 
Version=0.1.3397.31993, Culture=neutral, 
PublicKeyToken=94dc7dc697cfcfc0" foreign- 
key="FK_Addresses_Individuals_Individuals_IndividualId" /> 
    </bag> 
    </joined-subclass> 
    <joined-subclass name="Core.Domain.Entities.Store, Core.Domain, 
Version=0.1.3397.31993, Culture=neutral, 
PublicKeyToken=94dc7dc697cfcfc0" table="Stores"> 
    <key column="CustomerId" /> 
    <many-to-one unique="true" cascade="save-update" fetch="join" 
not-null="true" not-found="exception" name="Address" 
column="AddressId" /> 
    <many-to-one lazy="proxy" not-null="true" cascade="all" not- 
found="exception" name="Client" column="ClientId" /> 
    <property name="StoreName" type="String"> 
     <column name="StoreName" not-null="true" length="50" /> 
    </property> 
    <bag name="Contacts" table="Contacts_Stores"> 
     <key column="ContactId" foreign- 
key="FK_Contacts_Stores_Contacts_ContactId" /> 
     <many-to-many column="StoreId" 
class="Core.Domain.Entities.Contact, Core.Domain, 
Version=0.1.3397.31993, Culture=neutral, 
PublicKeyToken=94dc7dc697cfcfc0" foreign- 
key="FK_Contacts_Stores_Stores_StoreId" /> 
    </bag> 
    </joined-subclass> 
</class> 
</hibernate-mapping> 

Appel Session.Save sur un individu avec contact associé résultats dans l'erreur suivante:

NHibernate: INSERT INTO Addresses (Line1, PostalCode, Country, 
DateCreated, DateModified, LastModifiedBy) VALUES (@p0, @p1, @p2, @p3, 
@p4, @p5); select SCOPE_IDENTITY(); @p0 = 'Order Address Line 1', @p1 
= 'CV31 6BW', @p2 = 'United Kingdom', @p3 = '20/04/2009 19:45:32', @p4 
= '20/04/2009 19:45:32', @p5 = '' 
NHibernate: SELECT address_.Version as Version22_ FROM Addresses 
address_ WHERE [email protected]; @p0 = '1' 
NHibernate: INSERT INTO Contacts (FirstName, LastName, DateCreated, 
DateModified, LastModifiedBy) VALUES (@p0, @p1, @p2, @p3, @p4); select 
SCOPE_IDENTITY(); @p0 = 'Joe', @p1 = 'Bloggs', @p2 = '20/04/2009 
19:45:34', @p3 = '20/04/2009 19:45:34', @p4 = '' 
NHibernate: SELECT contact_.Version as Version33_ FROM Contacts 
contact_ WHERE [email protected]; @p0 = '1' 
NHibernate: INSERT INTO Customers (AccountNumber, DateCreated, 
DateModified, LastModifiedBy) VALUES (@p0, @p1, @p2, @p3); select 
SCOPE_IDENTITY(); @p0 = '', @p1 = '20/04/2009 19:45:34', @p2 = 
'20/04/2009 19:45:34', @p3 = '' 
NHibernate: INSERT INTO Individuals (ContactID, CustomerId) VALUES 
(@p0, @p1); @p0 = '1', @p1 = '1' 
NHibernate: SELECT individual_1_.Version as Version2_ FROM Individuals 
individual_ inner join Customers individual_1_ on 
individual_.CustomerId=individual_1_.Id WHERE 
[email protected]; @p0 = '1' 
NHibernate: UPDATE Contacts SET Version = @p0 WHERE Id = @p1 AND 
Version = @p2; @p0 = 'System.Byte[]', @p1 = '1', @p2 = 'System.Byte[]' 

System.Data.SqlClient.SqlException: Cannot update a timestamp column. 
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, 
Boolean breakConnection) 
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException 
exception, Boolean breakConnection) 
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning 
(TdsParserStateObject stateObj) 
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, 
SqlCommand cmdHandler, SqlDataReader dataStream, 
BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject 
stateObj) 
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader 
ds, RunBehavior runBehavior, String resetOptionsString) 
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds 
(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean 
returnStream, Boolean async) 
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior 
cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String 
method, DbAsyncResult result) 
at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery 
(DbAsyncResult result, String methodName, Boolean sendToPipe) 
at System.Data.SqlClient.SqlCommand.ExecuteNonQuery() 
at NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd) 
in c:\CSharp\NH\nhibernate\src\NHibernate\AdoNet\AbstractBatcher.cs: 
line 203 
at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object 
id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] 
includeProperty, Int32 j, Object oldVersion, Object obj, 
SqlCommandInfo sql, ISessionImplementor session) in c:\CSharp\NH 
\nhibernate\src\NHibernate\Persister\Entity 
\AbstractEntityPersister.cs: line 2713 
NHibernate.Exceptions.GenericADOException: could not update: 
[Core.Domain.Entities.Contact#1][SQL: UPDATE Contacts SET Version = 
@p0 WHERE Id = @p1 AND Version = @p2] 

Toutes les idées pourquoi NHibernate tente de mettre à jour la version colonne pour Contact, même si ce n'est pas le cas pour l'adresse?

Répondre

2

J'ai trouvé que l'aide-insert dynamique = "true" de la classe ainsi que les causes de cette question. J'utilise le mappage suivant avec succès:

... 
    <class name="Contact" table="Contact"> 
     <id name="ID" column="ID" type="int"> 
      <generator class="identity" /> 
     </id> 
     <version name="Version" generated="always" unsaved-value="null" type="BinaryBlob"/> 
... 
+0

Merci pour ce Scott. Il semble que tu écrivais. Pour une raison quelconque, l'activation de la mise à jour dynamique ou de l'insertion dynamique entraîne NHibernate pour tenter de mettre à jour la colonne version. Leur désactivation résout le problème. Malheureusement, j'ai besoin de mises à jour dynamiques - je ne veux pas que les colonnes de mise à jour de NHibernate soient inutiles car cela salit nos pistes d'audit; seules les colonnes réellement modifiées doivent être mises à jour. Des indications sur la réalisation de ceci étant donné la limitation ci-dessus? – Jimit

+1

Jimit, j'ai trouvé ce problème en essayant de résoudre le problème de colonne de version n'essayant pas d'adresser un problème d'insertion dynamique de sorte que je n'avais pas besoin d'accomplir ce que vous semblez faire. Je laisse simplement NHibernate mettre à jour chaque colonne comme d'habitude sans dynamic-insert = true. Vous dites que cela gâche vos pistes de vérification ... utilisez-vous des déclencheurs DB pour suivre les pistes de vérification? Si oui, pouvez-vous les rendre un peu plus intelligents pour ne consigner que les changements réels dans les données? –

2

L'adresse ne possède pas de colonne de version, je suppose.

Je me demande d'où vient le type sql. Pourquoi pas de cette façon?

<version name="Version" type="Timestamp" generated="always" unsaved-value="null"> 
    <column name="Version" not-null="false" /> 
    </version> 

Vous avez bien sûr besoin d'un DateTime dans l'entité.

+0

L'adresse possède une colonne de version. Je ne peux pas utiliser le mappage ci-dessus car j'ai besoin de tirer parti du type de données d'horodatage de SQL Server, qui est binaire et non datetime. – Jimit

+0

Je ne sais pas, je ne savais pas que cela fonctionne. Je vous suggère de demander à Ayende lui-même, soit sur le groupe d'utilisateurs de NHibernate (http://groups.google.com/group/nhusers), soit de poster un commentaire sur le blog d'Ayendes. –