Skip to main content

ActiveRecord support for MultiQuery

I have submitted a patch to add MultiQuery support to ActiveRecord. The link is at http://support.castleproject.org/browse/AR-139. I added ActiveRecordMultiQuery.cs and updated ActiveRecordBaseQuery.cs to allow subclasses to be aggregated (added) to the MultiQuery. I did this to speed up initialization of a form (I know ... premature optimization is the root of all evil). If you like this patch, log in and vote for it!

Update: The patch has been applied. In the mean time, let me caution you that ActiveRecordCriteriaQuery from the svn repository (which I also wrote and contributed) and any other "query" that uses criteria will not correctly participate in a multi-query. But if we give the NHibernate team some time, I'm sure this will be resolved.

Regarding the implementation, the only thing that I really wondered about was the "root type". This basically selects the session to be created. So all queries in the multi-query need to be registered with the same database.

Why did I do this? Because I would like all of my application code (after various patches are accepted) to be relatively free of NHibernate references. If my application only refers to ActiveRecord instances, the implementors will be free to switch to any other O/R persistence engine (not that I want that!), and also my code will be more resilient to NHibernate upgrades. At the moment, this is impossible, because I use Criteria, DetachedCriteria, and various other NHibernate expressions. I have considered creating an ARSupport namespace to wrap NHibernate Criteria, and add Find, FindAll, FindFirst, FindOne, Count and Exists. One alternative would be to leverage the IQueryModifier interface, but I can't really see this working, since it is only used to set values for named or positional parameters. I am just TOO hooked on Criteria. Anyway, I do not want to have references to ISession, IQuery, Configuration, etc. sprinkled throughout my code.

As a side note, it looks like NHibernate is trying to consolidate their interfaces. I very much approve of this. An example is that ISession.CreateSQLQuery has 3 methods, but 2 are tagged as obsolete with a message explaining correct usage.


ActiveRecordBaseQuery

public abstract class ActiveRecordBaseQuery ...
...
/// <summary>
/// Add this query to a multiquery
/// </summary>
/// <param name="session">an <c>ISession</c> shared by all queries in the multiquery</param>
/// <param name="multiquery">the <c>IMultiQuery</c> that will receive the newly created query</param>
internal void AddQuery(ISession session, IMultiQuery multiquery)
{
IQuery query = CreateQuery(session);
multiquery.Add(query);
}



ActiveRecordMultiQuery

// Copyright 2004-2007 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.ActiveRecord.Queries
{
using System;
using System.Collections;
#if DOTNET2
using System.Collections.Generic;
#endif

using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using Castle.ActiveRecord.Framework.Config;

using NHibernate;
using NHibernate.Cfg;
using NHibernate.Expression;

/// <summary>
/// wrapper for an IMultiQuery that executes a collection of queries.
/// </summary>
public class ActiveRecordMultiQuery : IActiveRecordQuery
{
Type _rootType;
#if DOTNET2
List<ActiveRecordBaseQuery> _queryList = new List<ActiveRecordBaseQuery>();
#else
ArrayList _queryList = new ArrayList();
#endif

/// <summary>
/// Initializes a new instance of the <see cref="ActiveRecordMultiQuery"/> class.
/// </summary>
/// <param name="RootType">the root type for all of the queries that will be included in the <c>IMultiQuery</c></param>
public ActiveRecordMultiQuery(Type RootType)
{
_rootType = RootType;
}

/// <summary>
/// Initializes a new instance of the <see cref="ActiveRecordMultiQuery"/> class.
/// </summary>
/// <param name="RootType">the root type for all of the queries that will be included in the <c>IMultiQuery</c></param>
/// <param name="activeRecordQueries">an array of <c>IActiveRecordQuery</c></param>
public ActiveRecordMultiQuery(Type RootType, ActiveRecordBaseQuery[] activeRecordQueries)
: this(RootType)
{
_queryList.AddRange(activeRecordQueries);
}

/// <summary>
/// Add an <c>IActiveRecordQuery</c> to our <see cref="ActiveRecordMultiQuery"/>
/// </summary>
/// <param name="activeRecordQuery"><c>IActiveRecordQuery</c> to be added to the MultiQuery</param>
public void Add(ActiveRecordBaseQuery activeRecordQuery)
{
_queryList.Add(activeRecordQuery);
}

#region IActiveRecordQuery Members

/// <summary>
/// Gets the target type of this query
/// </summary>
public Type RootType
{
get { return _rootType; }
}

/// <summary>
/// Executes the specified query and return the results
/// </summary>
/// <param name="session">The session to execute the query in.</param>
/// <returns>an array of results, one for each query added</returns>
public object Execute(NHibernate.ISession session)
{
// create a multi-query
IMultiQuery multiQuery = session.CreateMultiQuery();
foreach (ActiveRecordBaseQuery arQuery in _queryList)
{
// add the executable IQuery to our multi-query
arQuery.AddQuery(session, multiQuery);
}
// execute multiquery
object resultSetArray = multiQuery.List();
return resultSetArray;
}

/// <summary>
/// (Not Implemented!)
/// Enumerates over the result of the query.
/// Note: Only use if you expect most of your values to already exist in the second level cache!
/// </summary>
public System.Collections.IEnumerable Enumerate(NHibernate.ISession session)
{
throw new Exception("The method or operation is not implemented.");
}

#endregion
}
}

Comments

Popular posts from this blog

Castle ActiveRecord with DetachedCriteria

My current development environment is Visual Studio Express C# Edition (read that as free ), Castle ActiveRecord's latest svn trunk(usually within a few days), and NHibernate svn trunk. As of NHibernate version 1.2.0, there is a very cool new class out there ... DetachedCriteria. This class lets you set all of your Castle relational attributes like BelongsTo, HasMany, etc. as lazy fetch, and over-ride this for searches, reports, or anytime you know ahead of time that you will be touching the related classes by calling detachedCriteria.SetFetchMode(..., FetchEnum.Eager). As a good netizen, I have tried to contribute to NHibernate and Castle ActiveRecord even if only in the smallest of ways . Oh yeah, I tried mapping to a SQL VIEW, and it worked GREAT! I received a comment after my last post, indicating that there is a better way, and I am sure of it, but the view guaranteed that I only have one database request for my dataset. NHibernate was wanting to re-fetch my missing as

Castle ActiveRecord with Criteria and Alias

Update May 25, 2007: ActiveRecord now supports DetachedCriteria, which eliminates the need for the SlicedFindAll that I wrote below. It is nice when a library moves to add support for such commonly needed functions. So in summary, use Detached criteria instead of the code below. It is still a nice example of using NHibernate sessions. I have a history log, where each history record "belongs to" a service record. I have to treat this as a child-to-parent join, since some children are orphans. I wanted to use the FindAll(Criteria), but I wanted the option to have optional criteria, orders and aliases. My solution was to create an ARAlias class to represent an Associated Entity and an alias, and then build an ARBusinessBase class with the following method: public static T[] SlicedFindAll(int firstResult, int maxResults, Order[] orders, ARAlias[] aliases, params ICriterion[] criteria) { IList list = null; ISessionFactoryHolder holder = ActiveRecordMediator.GetSessionF

Castle ActiveRecord calling a Stored Procedure

Update: I have contributed patch AR-156 that allows full integration of Insert, Update and Delete to ActiveRecord models . If you've been reading my blog lately, you know that I have been seriously testing the Castle ActiveRecord framework out. I really love it, but I have an existing Microsoft SQL Server database with many stored procedures in it. I have tested the ActiveRecord model out, and I am sure that I will learn enough to be able to use it for standard CRUD (create, read, update, delete aka. insert, select, update, delete) functionality. BUT ... If I really want to integrate with my existing billing procedures, etc, I will have to be able to call stored procedures. I have taken two approaches ... write the ARHelper.ExecuteNonQuery(targetType, dmlString) method that gets a connection for the supplied type, executes dmlString, and closes it. write the ARHelper.RegisterCustomMapping(targetType, xmlString) method that allows me to add mappings that refer to my auto-gener