2

I am working on a legacy SQLBase database and am trying to set up a new project with NHibernate.

A big problem is the format SQLBase expects the bind variables in the SQL to be. The format has to be explicitly

INSERT INTO ... VALUES (:1,:2,:3); 
SELECT ... FROM TABLE WHERE ID=:1 AND NAME=:2;

Now NHibernate uses a ":p0,:p1,..." format for bind variables which gives a "invalid program bind variable" SQL exception in SQLBase.

The problem is the "p" before the number, and also that the parameters start at 0 - SQLBase must have parameters starting with 1.

Is there any way I can configure/change NHibernate to drop the "p" in the bind variables, and also start with 1 instead of 0? I was able to change the format of the parameters for INSERTs, UPDATEs and DELETEs with sql-insert, sql-update and sql-delete mappings in my class mappings, but the only way I found to change the SELECT parameters was to write lots of named queries into my mappings.

Obviously this isn't the preferred way. Is there any better way to change how the bind variables are generated? I already made my own Driver (inherited from OleDbDriver) and Dialect (from GenericDialect) and changed a few things to make SQLBase work int the first place.

I also tried setting

private static string ToParameterName(int index)
{
  return "p" + index;
}

to return String.Empty, but to no avail. But even if that'd remove the "p" prefix from the parameters I'd still have the problem of them starting at 0 instead of 1.

Any chance to change this behaviour or NHibernate?

EDIT: I now also tried changing a few other parameter functions:

string ISqlParameterFormatter.GetParameterName(int index)
        {
            int ret = index + 1;
            return (NamedPrefix + ret);
        }

        private void SetCommandParameters(IDbCommand cmd, SqlType[] sqlTypes)
        {
            for (int i = 0; i < sqlTypes.Length; i++)
            {
                int ret = i + 1;
                string paramName = ret.ToString();
                IDbDataParameter dbParam = GenerateParameter(cmd, paramName, sqlTypes[i]);
                cmd.Parameters.Add(dbParam);
            }
        }

        protected override void InitializeParameter(IDbDataParameter dbParam, string name, SqlType sqlType)
        {
            if (sqlType == null)
            {
                throw new QueryException(String.Format("No type assigned to parameter '{0}'", name));
            }
            name = name.Remove(0, 1);
            dbParam.ParameterName = (Int32.Parse(name) + 1).ToString();
            dbParam.DbType = sqlType.DbType;
        }

With these changes the SQL parameters are ":1, :2" and so on, but it also broke the bindings - now the parameters aren't appended to the queries at all :(

Edit2: Here's the complete driver and dialect code:

namespace NHSQLBase
{
    public class SQLBaseDriver : OleDbDriver, ISqlParameterFormatter
    {
        public override bool UseNamedPrefixInSql
        {
            get
            {
                return true;
            }
        }

        public override bool UseNamedPrefixInParameter
        {
            get
            {
                return false;
            }
        }

        public override string NamedPrefix
        {
            get
            {
                return ":";
            }
        }

        private static string ToParameterName(int index)
        {
            return (index + 1).ToString();
        }


        string ISqlParameterFormatter.GetParameterName(int index)
        {
            int ret = index + 1;
            return (NamedPrefix + ret);
        }

        private void SetCommandParameters(IDbCommand cmd, SqlType[] sqlTypes)
        {
            for (int i = 0; i < sqlTypes.Length; i++)
            {
                int ret = i + 1;
                string paramName = ret.ToString();
                IDbDataParameter dbParam = GenerateParameter(cmd, paramName, sqlTypes[i]);
                cmd.Parameters.Add(dbParam);
            }
        }

        protected override void InitializeParameter(IDbDataParameter dbParam, string name, SqlType sqlType)
        {
            if (sqlType == null)
            {
                throw new QueryException(String.Format("No type assigned to parameter '{0}'", name));
            }
            name = name.Remove(0, 1);
            dbParam.ParameterName = (Int32.Parse(name) + 1).ToString();
            dbParam.DbType = sqlType.DbType;
        }
    }

    public class SQLBaseDialect : GenericDialect
    {
        public override string ForUpdateString
        {
            get
            {
                return " ";
            }
        }

        public override bool ForUpdateOfColumns
        {
            get
            {
                return true;
            }
        }

        public override string GetForUpdateString(string aliases)
        {
            return " for update of " + aliases;
        }

        public override bool SupportsOuterJoinForUpdate
        {
            get
            {
                return false;
            }
        }

        public override bool SupportsParametersInInsertSelect
        {
            get
            {
                return false;
            }
        }
    }
}
Aether McLoud
  • 732
  • 1
  • 7
  • 20
  • might be a stupid comment, but have you tried return (index+1).ToString() for your ToParameterName ? – jbl Mar 20 '13 at 10:22
  • 1
    Alas trying to change the ToParameterName string doesn't change anything. Even with your suggestion the parameters are still named "p0, p1" and so on :( – Aether McLoud Mar 20 '13 at 10:23
  • Is there any chance you can post your dialect source code? – mickfold Mar 20 '13 at 10:27
  • I added both the driver and dialect code to the question. – Aether McLoud Mar 20 '13 at 10:30
  • One thing to note is that you can't override a static method. This is why your first attempt by change `ToParameterName` failed. – mickfold Mar 20 '13 at 10:34
  • @penfold I was going to make the same comment ;-) I guess the only option is to take DriverBase code to write a brand new driver which does not inherit from DriverBase, with a brand new ToParameter method – jbl Mar 20 '13 at 10:46
  • 1
    @jbl that looks like the only way to do this. I don't know why `ToParameterName` is a static method though. It would be better if it was a virtual method for situations such as this. – mickfold Mar 20 '13 at 11:03
  • 1
    In case you haven't already, please see https://groups.google.com/forum/?fromgroups=#!topic/nhibernate-development/wE5gabvX-VE and consider submitting a pull request. – Oskar Berggren Mar 22 '13 at 17:01

1 Answers1

1

After a bit of trial and error and think I have found the root of your issue. The main issue seems to be caused by SQLBaseDriver inheriting from OleDbDriver. Once this was changed this to ReflectionBasedDriver and the constructor was correctly populated I was able to perform inserts with no problems.

Please see below for working version of both driver and dialect.

One thing to note, the .Net data provider dll for SQLBase, Gupta.SQLBase.Data.dll, has to be in the same folder as the NHibernate.dll for this to work.

public class SQLBaseDriver : NHibernate.Driver.ReflectionBasedDriver
{
    public SQLBaseDriver()
        : base("Gupta.SQLBase.Data",
               "Gupta.SQLBase.Data.SQLBaseConnection",
               "Gupta.SQLBase.Data.SQLBaseCommand")
    {

    }
    public override bool UseNamedPrefixInSql
    {
        get { return true; }
    }

    public override bool UseNamedPrefixInParameter
    {
        get { return false; }
    }

    public override string NamedPrefix
    {
        get { return ":"; }
    }
}

Dialect code:

public class SQLBaseDialect : GenericDialect
{
    public override string ForUpdateString
    {
        get { return " "; }
    }

    public override bool ForUpdateOfColumns
    {
        get { return true; }
    }

    public override string GetForUpdateString(string aliases)
    {
        return " for update of " + aliases;
    }

    public override bool SupportsOuterJoinForUpdate
    {
        get { return false; }
    }

    public override bool SupportsParametersInInsertSelect
    {
        get { return false; }
    }
}
mickfold
  • 2,003
  • 1
  • 14
  • 20
  • I now tried to implement this instead of my hacked solution (that worked to some degree). Strangely I get an InvalidCastException from the driver constructor: System.InvalidCastException was caught Message=Object of type "Gupta.SQLBase.Data.SQLBaseConnection" cannot be converted to "System.Data.Common.DbConnection". – Aether McLoud Mar 28 '13 at 10:29
  • What version of SQLBase are you using? I used the latest release, 11.7, in my tests. – mickfold Mar 28 '13 at 10:42
  • I have uploaded my working test project [here](http://wikisend.com/download/292674/NHibernateSQLBaseTest.zip). Can you check if the 11.5 version of Gupta.SQLBase.Data.SQLBaseConnection inherits from DbConnection like it does in the 11.7 version? – mickfold Mar 28 '13 at 12:53
  • Alas, it inherits from Component and only implements IDbConnection as an interface :( – Aether McLoud Mar 28 '13 at 13:10
  • It should still work. The reflection based driver looks like it only uses `IDbConnection`. Can you update the question with the full stack trace for the exception? – mickfold Mar 28 '13 at 13:53