上一章主要介绍了配置测试环境,以及设置一些重要的断点以方便我们在调试的时候去理解NHibernagte在以Sql-query方式查询数据的代码结构。当我们调试到下面这一断点时,应该能明白为什么NHibernate只能返回映射好的实体类型或object数组了。
Loader.cs NHibernate=>Loader=>Loader.cs
断点位置:417 line
private IList DoQuery(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies) { RowSelection selection = queryParameters.RowSelection; int maxRows = HasMaxRows(selection) ? selection.MaxRows : int.MaxValue; int entitySpan = EntityPersisters.Length; List<object> hydratedObjects = entitySpan == 0 ? null : new List<object>(entitySpan * 10); IDbCommand st = PrepareQueryCommand(queryParameters, false, session); IDataReader rs = GetResultSet(st, queryParameters.HasAutoDiscoverScalarTypes, queryParameters.Callable, selection, session); ...... }
它是采用DataReader来读取数据的,因此无法返回类似于DataSet的东西。考虑到这一点,也许我们对怎么扩展就有一点想法了-也许我们可以采用DataAdapter来返回一个DataSet对象。
从上面的跟踪调试过程中我们也大致了解了NHibernate 的Naemd Query的工作方式。接下来我们就可以基于NHibernate的源码定制我们
的利用sql-query返回DataSet的方法。大致思路如下:根据我们的调试过程,模仿在上诉运行中被调用的方法,最终在Loader.cs中
通过command和dataadapter返回dataset。
我们打算采用下面的方式调用来返回DataSet:
var res = query.QueryDataSet();
因此 需要给IQuery 接口增加一个方法:DataSet QueryDataSet()。
然后将实现了IQuery 的接口的子类都实现这个方法。这里我们只要为两个类AbstractQueryImpl和SqlQueryImpl实现该方法就可以了。
AbstractQueryImpl:
public virtual System.Data.DataSet QueryDataSet() { throw new NotImplementedException(); }
上面方法并没有实现如何去获取DataSet对象,只是实现和定义了一个QueryDataSet虚方法。如果有方法不小心调用了这个方法,仅仅做个抛出异常提示就行了。
而在我们所要完成目标中:
var res = query.QueryDataSet();
query其实是一个实现了IQuery 接口的SqlQueryImpl类,该类继承于AbstractQueryImpl。因此必须要在SqlQueryImpl重写上面的方法。仿照上面调试过程中返回IList的方法,我们按如下方式覆盖QueryDataSet。
public override System.Data.DataSet QueryDataSet() { VerifyParameters(); IDictionary<string, TypedValue> namedParams = NamedParams; NativeSQLQuerySpecification spec = GenerateQuerySpecification(namedParams); QueryParameters qp = GetQueryParameters(namedParams); Before(); try { return Session.QueryDataSet(spec, qp); } finally { After(); } //return base.QueryDataSet(); }
这样我们需要为Session对象添加QueryDataSet方法。因为上面的Session对象实现了ISessionImplementor接口。首先我们给ISessionImplementor添加一个方法声明:
DataSet QueryDataSet(NativeSQLQuerySpecification spec, QueryParameters queryParameters);
我们之所以不直接给它的子类直接添加这个方法而是先在接口上增加这个方法,更多的是为了保持NHibernate的原有结构,这样也利于我们扩展,比如说这次我是以Oracle数据库为测试数据库,那如果是下次你要用到Sql Server呢。我们都知道NHibernate 中Oracle和Sql Server是不同的dialet。针对不同的数据库,NHibernate 大量采用了工厂方法来达到比较好的移植性。因此我们在扩展NHibernate的功能的时候尽量保持原有架构的特点。为ISessionImplementor的子类添加该方法,这里主要是AbstractSessionImpl和SessionImpl。
为AbstractSessionImpl实现该方法:
public virtual DataSet QueryDataSet(NativeSQLQuerySpecification spec, QueryParameters queryParameters) { throw new NotImplementedException(); }
同理在SessionImpl类中覆盖该方法(仿照SessionImpl类中返回IList的方法):
public override DataSet QueryDataSet(NativeSQLQuerySpecification spec, QueryParameters queryParameters) { using (new SessionIdLoggingContext(SessionId)) { DataSet results = new DataSet(); SQLCustomQuery query = new SQLCustomQuery( spec.SqlQueryReturns, spec.QueryString, spec.QuerySpaces, Factory); DataSetCustomQuery(query, queryParameters,ref results); return results; } }
上面代码中我们添加了一个DataSetCustomQuery方法,这个方法用于返回真正的DataSet。
public void DataSetCustomQuery(SQLCustomQuery customQuery, QueryParameters queryParameters,ref DataSet results) { using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); CustomLoader loader = new CustomLoader(customQuery, Factory); AutoFlushIfRequired(loader.QuerySpaces); bool success = false; dontFlushFromFind++; try { //ArrayHelper.AddAll(results, loader.List(this, queryParameters)); results = loader.QueryDataSet(this, queryParameters); success = true; } finally { dontFlushFromFind--; AfterOperation(success); } } }
上面代码中注释的部分为先前返回IList方法中获得返回数据的调用。在此我们也是模拟该方法的调用,只是直接通过执行CustomLoader方法QueryDataSet来返回数据。我们为CustomLoader添加一个通过调用基类Loader中的方法QueryDataSet的,属于CustomLoader的QueryDataSet方法(仿照List方法)。
internal DataSet QueryDataSet(Impl.SessionImpl sessionImpl, QueryParameters queryParameters) { return base.QueryDataSet(sessionImpl, queryParameters, querySpaces); }
然后为Loader添加一QueryDataSet方法(模仿List方法):
protected DataSet QueryDataSet(Impl.SessionImpl sessionImpl, QueryParameters queryParameters, ISet<string> querySpaces) { IPersistenceContext persistenceContext = sessionImpl.PersistenceContext; bool defaultReadOnlyOrig = persistenceContext.DefaultReadOnly; if (queryParameters.IsReadOnlyInitialized) persistenceContext.DefaultReadOnly = queryParameters.ReadOnly; else queryParameters.ReadOnly = persistenceContext.DefaultReadOnly; persistenceContext.BeforeLoad(); DataSet result; try { try { result = DoDataSetQuery(sessionImpl, queryParameters); } finally { persistenceContext.AfterLoad(); } persistenceContext.InitializeNonLazyCollections(); } finally { persistenceContext.DefaultReadOnly = defaultReadOnlyOrig; } return result; }
上诉代码中DoDataSetQuery方法用于返回查询结果。这是仿照Loader类中DoList,DoQuery等方法定义的。
private DataSet DoDataSetQuery(ISessionImplementor session, QueryParameters queryParameters) { DataSet ds = new DataSet(); using (IDbCommand st = PrepareQueryCommand(queryParameters, false, session)) { IDbConnection connection = session.Connection; st.Connection = connection; IDbDataAdapter adapter = session.Batcher.CreateAdapter(st); if (connection.State == ConnectionState.Closed) { connection.Open(); } using (IDbTransaction tran = connection.BeginTransaction()) { adapter.Fill(ds); tran.Commit(); } return ds; }
上述代码就是最终执行查询获得DataSet的方法。这里我们要注意两个地方。一个是IDbCommand对象的获得,另外一个是IDbDataAdapter对象的获得。NHibernate提供了IDbCommand对箱的获取方法(参考DoQuery方法),但是并没有提供获取IDbDataAdapter对象的方法。因此我们的问题集中在如何获取IDbDataAdapter对象。NHibernate是通过工厂方法的方式来获得各种针对不同数据库的IDbCommand对象的,因此我们也必须基于这种方式去实现。我们可以查看Loader类中PrepareQueryCommand方法的定义。其中最主要的一句代码:
IDbCommand command = session.Batcher.PrepareQueryCommand(CommandType.Text, sqlString, sqlCommand.ParameterTypes);
该代码中session事实上就是SessionImpl,而Battcher属性是一个实现了IBatcher接口的类。因此我们能否仿照现有的获取IDbCommand对象的方式去获取一个IDbDataAdapter呢,试一试。
首先为IBatcher增加一个 IDbDataAdapter方法:
CreateAdapter(IDbCommand command)
接着我们为IBatcher的子类实现上面的方法,这里我们只有为AbstractBatcher类实现即可,其它类都是继承于AbstractBatcher抽象类。
public IDbDataAdapter CreateAdapter(IDbCommand command) { IDbDataAdapter dataAdapter = _factory.ConnectionProvider.Driver.CreateAdapter(command); return dataAdapter; }
上述方法是模仿AbstractBatcher中Generate方法而来的。我们查看Generate方法,它是通过下面这段代码来获得IDbCommand对象的。
IDbCommand cmd = _factory.ConnectionProvider.Driver.GenerateCommand(type, sql, parameterTypes)
因此我们也希望能通过Driver来创建返回一个dataAdapter对象。我们为IDriver对象添加一CreateAdapter方法,然后再为其子类实现该方法。
IDbDataAdapter CreateAdapter(IDbCommand command)
其子类主要是两抽象类:DriverBase,ReflectionBasedDriver。
DriverBase:
public virtual IDbDataAdapter CreateAdapter(IDbCommand command) { throw new NotImplementedException("Customized method is not implemented by ChenKeFang"); }
ReflectionBasedDriver(模仿创建Command对象的方法):
public override IDbDataAdapter CreateAdapter(IDbCommand command) { return connectionCommandProvider.CreateDataAdapter(command); }
保存编译报错,因为connectionCommandProvider中并没有定义CreateDataAdapter方法。connectionCommandProvider是实现了IDriveConnectionCommandProvider的类。我们同样给这个IDriveConnectionCommandProvider接口增加一个这样的声明。
public interface IDriveConnectionCommandProvider { IDbConnection CreateConnection(); IDbCommand CreateCommand(); IDbDataAdapter CreateDataAdapter(IDbCommand command); }
为子类实现上述方法:
DbProviderFactoryDriveConnectionCommandProvider:
public IDbDataAdapter CreateDataAdapter(IDbCommand command) { throw new NotImplementedException("Customized method is not implemented by ChenKeFang"); }
ReflectionDriveConnectionCommandProvider:
public IDbDataAdapter CreateDataAdapter(IDbCommand command) { if (adapterType==null) { throw new ArgumentNullException("The Data Adapter type is not passed!"); } return (IDbDataAdapter)Environment.BytecodeProvider.ObjectsFactory.CreateInstance(adapterType,command); }
比较上面两个子类的实现,两者有很大区别。之所以这样,是我在调试中发现我调用的实际上是ReflectionDriveConnectionCommandProvider对象。因此没有在DbProviderFactoryDriveConnectionCommandProvider类中实现。DriverBase也是基于此并没有真正实现。
查看ReflectionDriveConnectionCommandProvider类,我们发现Command类型和Connectoin类型都是基于构造函数来获得的。因此我们也修改下该构造函数:
private readonly System.Type adapterType; public ReflectionDriveConnectionCommandProvider(System.Type connectionType, System.Type commandType, System.Type adapterType=null) { if (connectionType == null) { throw new ArgumentNullException("connectionType"); } if (commandType == null) { throw new ArgumentNullException("commandType"); } this.connectionType = connectionType; this.commandType = commandType; this.adapterType = adapterType; }
好了写好上述代码后,我们开始测试,还是无法获的一个DataAdapter对象,返回一个null对象。当我们调试到AbstractBatcher中CreateAdapter时候我们发现这个Driver属性其实是一个NHibernate.Driver.OracleDataClientDriver对象(测试数据库为Oracle)。我们在该类的构造函数处设置断点调试。当我们打开NHibernate.Driver.OracleDataClientDriver查看其代码的时候也许就知道为什么仍然无法获得DataAdapter对象。NHibernate是采用工厂方式,针对多种数据库定义了多种类型的Driver,在各子类中定义了几个变量如driverAssemblyName,commandTypeName等。然后通过这些字符串变量,结合反射技术去获得各自相应的需要类型。由此我们也可以试试模仿这种方式,在OracleDataClientDriver增加一常量:
private const string adapterName = "Oracle.DataAccess.Client.OracleDataAdapter" ; public OracleDataClientDriver() : base( "Oracle.DataAccess.Client", driverAssemblyName, connectionTypeName, commandTypeName, adapterName)
修改其基类构造函数ReflectionBasedDriver:
protected ReflectionBasedDriver(string driverAssemblyName, string connectionTypeName, string commandTypeName, string adapterName=null) : this(null, driverAssemblyName, connectionTypeName, commandTypeName, adapterName) { } protected ReflectionBasedDriver(string providerInvariantName, string driverAssemblyName, string connectionTypeName, string commandTypeName, string adapterName = null) { // Try to get the types from an already loaded assembly var connectionType = ReflectHelper.TypeFromAssembly(connectionTypeName, driverAssemblyName, false); var commandType = ReflectHelper.TypeFromAssembly(commandTypeName, driverAssemblyName, false); var adapterType = ReflectHelper.TypeFromAssembly(adapterName, driverAssemblyName, false); if (connectionType == null || commandType == null) { if (string.IsNullOrEmpty(providerInvariantName)) { throw new HibernateException(string.Format(ReflectionTypedProviderExceptionMessageTemplate, driverAssemblyName)); } var factory = DbProviderFactories.GetFactory(providerInvariantName); connectionCommandProvider = new DbProviderFactoryDriveConnectionCommandProvider(factory); } else { connectionCommandProvider = new ReflectionDriveConnectionCommandProvider(connectionType, commandType, adapterType); } }
注意上面代码中这一句:
connectionCommandProvider = new ReflectionDriveConnectionCommandProvider(connectionType, commandType, adapterType);
终于我们将要创建的DataAdapter类型传入ReflectionDriveConnectionCommandProvider,然后在ReflectionDriveConnectionCommandProvider中利用反射,动态生成一个DataAdapter对象。
到此基本上整个流程算是走通了。一个基于Sql-Query和Oracle的返回DataSet的方法算是创建完成了。修改上一节中的DriverInfo.hbm.xml映射文件下sql-query结点中配置以测试我们扩展出来的方法。
<sql-query name="GetDataSetTest"> select * from Fleet_DriverInfo where rownum<2 </sql-query>
以及UnitTest1.cs中的TestReaderWay方法:
public void TestReaderWay() { ISessionFactory factory=SessionFactory.SessionFac; ISession session = factory.OpenSession(); IQuery query = session.GetNamedQuery("GetDataSetTest"); var res = query.QueryDataSet(); }
下图是本次扩展涉及到的需要修改的文件图。
下面链接包含本次扩展需要修改的文件,仅供参考。
https://files.cnblogs.com/wuwei_chen/nhibernate.zip
下面链接是NHibernate扩展后编译生成的类库,仅供学习参考。本例是基于ORACLE数据库而做的扩展,有兴趣的同仁可以试试扩展基于其它数据库的QueryDataSet方法。