【问题标题】:NHibernate and sql timestamp columns as versionNHibernate 和 sql 时间戳列作为版本
【发布时间】:2009-04-22 00:06:54
【问题描述】:

我一直在努力让 Nhibernate 使用一个字节 数组作为版本映射到 sql 时间戳。我实施了一个 IUserVersionType 但 Nhibernate 正在数据库中创建 varbinary 而不是时间戳。受到 Ayende 最近在博客上的一篇博文的启发 并发,我更改了映射以将 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>

呼叫 Session.Save 与相关联系结果的个人 在以下错误中:

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 尝试更新版本列的任何想法 联系人,即使不是地址?

【问题讨论】:

    标签: sql-server nhibernate concurrency fluent-nhibernate


    【解决方案1】:

    我发现在类上使用 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"/>
    ...
    

    【讨论】:

    • 感谢斯科特。看来你是写的。出于某种原因,启用动态更新或动态插入会导致 NHibernate 尝试更新版本列。禁用它们可以解决问题。不幸的是,我确实需要动态更新——我不希望 NHibernate 不必要地更新列,因为它会弄乱我们的审计跟踪;只有实际修改的列需要更新。鉴于上述限制,任何关于实现这一目标的指针?
    • Jimit,我在尝试解决版本列问题而不是尝试解决动态插入问题时发现了这个问题,所以我不需要完成听起来像你做的事情。我只是让 NHibernate 像通常没有动态插入 = true 那样更新每一列。您说这会弄乱您的审计跟踪...您是否使用数据库触发器来跟踪审计跟踪?如果是这样,你能让它们更聪明一点,只记录对数据的实际更改吗?
    【解决方案2】:

    地址没有我假设的版本列。

    我想知道您从哪里获得 sql 类型。为什么不这样呢?

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

    您当然需要实体中的 DateTime。

    【讨论】:

    • 地址确实有一个版本列。我不能使用上面的映射,因为我需要利用 SQL Server 的时间戳数据类型,它是二进制的,而不是日期时间。
    • 我不知道,不知道这行得通。我建议您在 NHibernate 的用户组 (groups.google.com/group/nhusers) 上询问 Ayende 本人,或者在 Ayendes 博客上发表评论。
    【解决方案3】:

    【讨论】:

    • 我已经看过 Ayende 的那篇帖子了。事实上,这就是我在上面的问题中提到的帖子。我在上面使用的映射与他在那篇文章中使用的映射相同,用于映射利用 MS SQL Server 的时间戳数据类型的版本列。
    猜你喜欢
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-17
    • 2020-06-13
    相关资源
    最近更新 更多