Search code examples
sql-servernhibernatefluent-nhibernatenhibernate-mapping

Controlling NHIbernate search query output regarding parameters


When you use NHibernate to "fetch" a mapped object, it outputs a SELECT query to the database. It outputs this using parameters; so if I query a list of cars based on tenant ID and name, I get:

select Name, Location from Car where tenantID=@p0 and Name=@p1

This has the nice benefit of our database creating (and caching) a query plan based on this query and the result, so when it is run again, the query is much faster as it can load the plan from the cache.

The problem with this is that we are a multi-tenant database, and almost all of our indexes are partition aligned. Our tenants have vastly different data sets; one tenant could have 5 cars, while another could have 50,000. And so because NHibernate does this, it has the net effect of our database creating and caching a plan for the FIRST tenant that runs it. This plan is likely not efficient for subsequent tenants who run the query.

What I WANT to do is force NHibernate NOT to parameterize certain parameters; namely, the tenant ID. So I'd want the query to read:

select Name, Location from Car where tenantID=55 and Name=@p0

I can't figure out how to do this in the HBM.XML mapping. How can I dictate to NHibernate how to use parameters? Or can I just turn parameters off altogether?


Solution

  • OK everyone, I figured it out.

    The way I did it was overriding the SqlClientDriver with my own custom driver that looks like this:

     public class CustomSqlClientDriver : SqlClientDriver
    {
        private static Regex _partitionKeyReplacer = new Regex(@".PartitionKey=(@p0)", RegexOptions.Compiled);
        public override void AdjustCommand(IDbCommand command)
        {
            var m = _tenantIDReplacer.Match(command.CommandText);
    
            if (!m.Success)
                return;
    
            //  replace the first parameter with the actual partition key
            var parameterName = m.Groups[1].Value;
    
            // find the parameter value
            var tenantID = (IDbDataParameter ) command.Parameters[parameterName];
            var valueOfTenantID = tenantID.Value;
    
            // now replace the string
            command.CommandText = _tenantIDReplacer.Replace(command.CommandText, ".TenantID=" + valueOfTenantID);
        }
    } }
    

    I override the AdjustCommand method and use a Regex to replace the tenantID. This works; not sure if there's a better way, but I really didn't want to have to open up NHibernate and start messing with core code.

    You'll have to register this custom driver in the connection.driver_class property of the SessionFactory upon initialization.

    Hope this helps somebody!