2009-04-22 4 views
1

Ich habe mir den Kopf zerbrochen und versucht, Nhibernate mit einem Byte array als Versionszuordnung zu einem sql timestamp zu arbeiten. Ich hatte eine IUserVersionType implementiert, aber Nhibernate erstellt Varbinary in der Datenbank anstatt Timestamp. Inspiriert von einem Blog-Post von Ayende kürzlich auf Nebenläufigkeit, änderte ich meine Zuordnung, um die sql-type zu timestamp zu spezifizieren, die perfekt funktionierte. Allerdings habe ich jetzt ein ziemlich seltsames Problem konfrontiert, wo Nhibernate eine Einfügung macht, ruft die neue Version und dann sofort versucht, ein Update zu tun und versucht, die Version Spalte, die eine sql Zeitstempel fehlschlägt.NHibernate und sql timestamp columns als Version

Das ist mein 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="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> 

Aufruf Session.Save auf einen einzelnen mit zugehörigem Kontakt in dem folgenden Fehler führt:

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] 

Irgendwelche Ideen, warum NHibernate die Version zu aktualisieren versucht, Spalte für Kontakt, obwohl es nicht für die Adresse?

Antwort

2

habe ich gefunden, dass die Verwendung von dynamisch-Insert = „true“ auf die Klasse zusammen mit den Ursachen dieses Problem. Ich verwende die folgende Zuordnung erfolgreich:

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

Danke für das Scott. Es scheint, dass du geschrieben hast. Aus irgendeinem Grund veranlasst die Aktivierung von dynamischem oder dynamischem Einfügen, dass NHibernate versucht, die Versionsspalte zu aktualisieren. Durch die Deaktivierung wird das Problem behoben. Leider benötige ich dynamische Updates - ich möchte nicht, dass NHibernate Spalten unnötig aktualisiert, da es unsere Audit-Trails durcheinander bringt; Nur tatsächlich geänderte Spalten müssen aktualisiert werden. Irgendwelche Hinweise auf das Erreichen der oben genannten Einschränkung? – Jimit

+1

Jimit, ich habe dieses Problem beim Versuch, das Versionsspalte-Problem zu lösen, nicht versucht, ein Problem mit dynamischen Einfügungen zu beheben, sodass ich nicht das tun musste, was es so klingt wie Sie. Ich lasse einfach NHibernate jede Spalte aktualisieren, wie es normalerweise ohne dynamic-insert = true tut. Sie sagen, dies vermasselt Ihre Audit-Trails ... verwenden Sie DB-Trigger, um Audit-Trails zu verfolgen? Wenn ja, können Sie sie etwas schlauer machen, um nur tatsächliche Änderungen an den Daten zu protokollieren? –

2

Die Adresse hat keine Versionsspalte, die ich annehme.

Ich frage mich, wo Sie den SQL-Typ haben. Warum nicht so?

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

Sie benötigen natürlich eine DateTime in der Entität.

+0

Adresse hat eine Versionsspalte. Ich kann die obige Zuordnung nicht verwenden, da ich den Zeitstempel-Datentyp von SQL Server verwenden muss, der binär ist, nicht datetime. – Jimit

+0

Ich weiß es nicht, wusste nicht, dass das funktioniert. Ich empfehle Ihnen, Ayende selbst zu fragen, entweder in der Benutzergruppe von NHibernate (http://groups.google.com/group/nhusers) oder einen Kommentar im Ayendes-Blog zu schreiben. –