Why NHibernate updates DB on commit of read-only transaction

http://www.zvolkov.com/clog/2009/07/09/why-nhibernate-updates-db-on-commit-of-read-only-transaction/

Always be careful with NULLable fields whenever you deal with NHibernate. If your field is NULLable in DB, make sure corresponding .NET class uses Nullable type too. Otherwise, all kinds of weird things will happen. The symptom is usually will be that NHibernate will try to update the record in DB, even though you have not changed any fields since you read the entity from the database.

The following sequence explains why this happens:

  1. NHibernate retrieves raw entity‘s data from DB using ADO.NET
  2. NHibernate constructs the entity and sets its properties
  3. If DB field contained NULL the property will be set to the defaul value for its type:
    • properties of reference types will be set to null
    • properties of integer and floating point types will be set to 0
    • properties of boolean type will be set to false
    • properties of DateTime type will be set to DateTime.MinValue
    • etc.
  4. Now, when transaction is committed, NHibernate compares the value of
    the property to the original field value it read form DB, and since the
    field contained NULL but the property contains a non-null value,
    NHibernate considers the property dirty, and forces an update of the
    enity.

Not only this hurts performance (you get
extra round-trip to DB and extra update every time you retrieve the
entity) but it also may cause hard to troubleshoot errors with DateTime
columns. Indeed, when DateTime property is initialized to its default
value it‘s set to 1/1/0001. When this value is saved to DB, ADO.NET‘s
SqlClient can‘t convert it to a valid SqlDateTime value since the
smallest possible SqlDateTime is 1/1/1753!!! The exception it throws
looks like this:

NHibernate.Event.Default.AbstractFlushingEventListener - Could not synchronize database state with session 

NHibernate.HibernateException:
An exception occurred when executing batch queries --->
System.Data.SqlTypes.SqlTypeException: SqlDateTime overflow. Must be
between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. 

at System.Data.SqlTypes.SqlDateTime.FromTimeSpan(TimeSpan value) 

   at System.Data.SqlTypes.SqlDateTime.FromDateTime(DateTime value)

   at System.Data.SqlClient.MetaType.FromDateTime(DateTime dateTime, Byte cb)

   at
System.Data.SqlClient.TdsParser.WriteValue(Object value, MetaType type,
Byte scale, Int32 actualLength, Int32 encodingByteSize, Int32 offset,
TdsParserStateObject stateObj)

   at
System.Data.SqlClient.TdsParser.TdsExecuteRPC(_SqlRPC[] rpcArray, Int32
timeout, Boolean inSchema, SqlNotificationRequest notificationRequest,
TdsParserStateObject stateObj, Boolean isCommandProc)

   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 System.Data.SqlClient.SqlCommandSet.ExecuteNonQuery()

   at NHibernate.AdoNet.SqlClientSqlCommandSet.ExecuteNonQuery()

   --- End of inner exception stack trace ---

   at NHibernate.AdoNet.SqlClientSqlCommandSet.ExecuteNonQuery()

   at NHibernate.AdoNet.SqlClientBatchingBatcher.DoExecuteBatch(IDbCommand ps)

   at NHibernate.AdoNet.AbstractBatcher.ExecuteBatch()

   at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)

   at NHibernate.Engine.ActionQueue.ExecuteActions()

   at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)

   at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)

   at NHibernate.Impl.SessionImpl.Flush()

   at NHibernate.Transaction.AdoTransaction.Commit()

The easiest fix is to make the class
property use Nullable<T> type, in this case "DateTime?".
Alternatively, you could implement a custom type mapper by implementing
IUserType with its Equals method properly comparing DbNull.Value with
whatever default value of your value type. In our case Equals would need
to return true when comparing 1/1/0001 with DbNull.Value. Implementing a
full-functional IUserType is not really that hard but it does require
knowledge of NHibernate trivia so prepare to do some substantial
googling if you choose to go that way.

Hope this helps somebody!

时间: 2024-10-19 11:22:14

Why NHibernate updates DB on commit of read-only transaction的相关文章

ABAP:关于隐式与显式的DB Commit

转自http://blog.163.com/[email protected]/blog/static/1394892972011611111559962/#userconsent#1.显式的DB Commit 显式的DB Commit并没有对应的ABAP 语句来执行DB Commit,它是由平常的语句Commit Work来进行的.一个DB LUW中,我们是以该DB被打开,然后以DB Commit结束. 2.隐式的DB Commit 隐式的DB Commit更没有对应的ABAP语句来告诉系统(

spring.net 集成nhibernate配置文件(这里暴露了GetCurrentSession 对于 CurrentSession unbond thread这里给出了解决方法)

我这里主要分成了两个xml来进行spring.net管理实际情况中可自己根据需要进行分类 Dao2.xml <?xml version="1.0" encoding="utf-8" ?> <objects xmlns="http://www.springframework.net" xmlns:db="http://www.springframework.net/database" xmlns:tx=&quo

NHibernate could not get or update next value[SQL: ] 对象名 &#39;hibernate_unique_key&#39; 无效。

错误信息: --------------------------- --------------------------- NHibernate.Exceptions.GenericADOException: could not get or update next value[SQL: ] ---> System.Data.SqlClient.SqlException: 对象名 'hibernate_unique_key' 无效. 在 System.Data.SqlClient.SqlConn

START TRANSACTION, COMMIT, and ROLLBACK Syntax-from cyber

START TRANSACTION [WITH CONSISTENT SNAPSHOT] BEGIN [WORK] COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE] ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE] SET autocommit = {0 | 1} These statements provide control over use of transactions: START TRANSACT

WinForm下的Nhibernate+Spring.Net的框架配置文件

1.先将配置文件放到如下:<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <!--Spring配置声明--> <sectionGroup name="spring"> <section name="context" type="Spring.Context.

flask db操作

from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = False # app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True # app.config['SQLALCHEMY_ECHO'] = True # pip install mys

聊聊db和缓存一致性的5种实现方式

数据存储在数据库中,为了加快业务访问的速度,我们将数据库中的一些数据放在缓存中,那么问题来了,如何确保db和缓存中数据的一致性呢?我们列出了5种方法,大家都了解一下,然后根据业务自己选择. 方案1 获取缓存逻辑 使用过定时器,定时刷新redis中的缓存. db更新数据逻辑 更新数据不用考虑缓存中的数据,直接更新数据就可以了 存在的问题 缓存中数据和db中数据一致性可能没有那么及时,不过最终在某个时间点,数据是一致的. 方案2 获取缓存逻辑 c1:根据key在redis中获取对应的value c2

commit后数据库干的工作

用户提交commit后,数据库干的工作有: 1,oracle为用户的transaction生成一个SCN号. 2,LGWR把redo buffer中的数据写入到redo log file,同时把SCN号记录到redo log file中.这一步完成后,说明用户提 交的数据已经安全的写到磁盘 3,释放用户session占用的locks,这些locks可以在V$LOCK中查到.释放用户的lock后,那么其他在等待lock的session 就会被唤醒,继续它们的工作 4,如果在commit后,用户tr

centos7.2 安装svn服务

简介 Subversion(SVN) 是一个开源的版本控制系統, 也就是说 Subversion 管理着随时间改变的数据.这些数据放置在一个中央资料档案库(repository) 中.这个档案库很像一个普通的文件服务器, 不过它会记住每一次文件的变动.这样你就可以把档案恢复到旧的版本, 或是浏览文件的变动历史. SVN 的一些概念: repository(源代码库):源代码统一存放的地方 Checkout(提取):当你手上没有源代码的时候,你需要从repository checkout一份 Co