Столбцы временных меток NHibernate и sql как версия

Я ломал голову, пытаясь заставить Nhibernate работать с байтовым массивом как отображение версии на временную метку sql. Я реализовал IUserVersionType, но Nhibernate создавал varbinary в базе данных, а не метку времени. Вдохновленный недавним сообщением в блоге Айенде о параллелизме, я изменил свое сопоставление, чтобы указать тип sql на временную метку, которая отлично работала. Однако теперь я сталкиваюсь с довольно любопытной проблемой, когда Nhibernate выполняет вставку, получает новую версию, а затем немедленно пытается выполнить обновление и пытается установить столбец версии, который является временной меткой sql, терпит неудачу.

Это мое отображение:

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

Вызов сеанса. Сохранить на физическом лице со связанным контактом приводит к следующей ошибке:

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 address_.Id=@p0; @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 contact_.Id=@p0; @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
individual_.CustomerId=@p0; @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]

Есть идеи, почему NHibernate пытается обновить столбец версии для контакта, хотя это не для адреса?


person Jimit    schedule 22.04.2009    source источник


Ответы (3)


Я обнаружил, что использование dynamic-insert = "true" в классе вместе с вызывает эту проблему. Я успешно использую следующее сопоставление:

...
    <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"/>
...
person Scott M    schedule 30.04.2009
comment
Спасибо за это, Скотт. Похоже вы писали. По какой-то причине включение динамического обновления или динамической вставки заставляет NHibernate пытаться обновить столбец версии. Их отключение устраняет проблему. К сожалению, мне нужны динамические обновления - я не хочу, чтобы NHibernate обновлял столбцы без необходимости, поскольку это портит наши контрольные журналы; необходимо обновлять только фактически измененные столбцы. Какие-либо указания на достижение этого с учетом вышеуказанного ограничения? - person Jimit; 11.05.2009
comment
Джимит, я обнаружил эту проблему, когда пытался решить проблему со столбцом версии, не пытаясь решить проблему с динамической вставкой, поэтому мне не нужно было выполнять то, что похоже на вас. Я просто позволяю NHibernate обновлять каждый столбец, как обычно, без dynamic-insert = true. Вы говорите, что это портит ваши контрольные журналы ... вы используете триггеры БД для отслеживания контрольных журналов? Если да, можете ли вы сделать их немного умнее, чтобы они регистрировали только фактические изменения данных? - person Scott M; 27.05.2009

Полагаю, в Адресе нет столбца версии.

Интересно, откуда у вас sql-тип. Почему не так?

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

Конечно, вам понадобится DateTime в сущности.

person Stefan Steinegger    schedule 22.04.2009
comment
В адресе есть столбец версии. Я не могу использовать приведенное выше сопоставление, потому что мне нужно использовать тип данных timestamp SQL Server, который является двоичным, а не datetime. - person Jimit; 25.04.2009
comment
Не знаю, не знал, что это работает. Я предлагаю вам спросить самого Айенде, либо в группе пользователей NHibernate (groups.google.com/group/nhusers) или оставьте комментарий в блоге Ayendes. - person Stefan Steinegger; 25.04.2009

http://ayende.com/Blog/archive/2009/04/15/nhibernate-mapping-concurrency.aspx

person Community    schedule 22.04.2009
comment
Я уже видел этот пост Айенде. Фактически, это сообщение, на которое я ссылался в своем вопросе выше. Сопоставление, которое я использую выше, идентично тому, которое он использовал в этом посте для сопоставления столбца версии с использованием типа данных timestamp MS SQL Server. - person Jimit; 25.04.2009