NHibernate support

Discussion of open issues, suggestions and bugs regarding ADO.NET provider for SQLite
Post Reply
pleb
Posts: 37
Joined: Mon 23 Jun 2008 06:21
Location: Melbourne, Australia

NHibernate support

Post by pleb » Thu 11 Feb 2010 10:54

Hello devart,

I was wondering if there was plans to support nhibernate?

I've written my own classes to support this, but I'm having a few issues.

Namely the first is that I have to turn off hbm2ddl.keywords.

Similar issue is explained here http://orbitalcoder.wordpress.com/2009/ ... rvedwords/.

And the second is I'm also getting
NHibernate.ADOException : could not execute query
[ SELECT this_.ModelID as ModelID20_0_, this_.Version as Version20_0_, this_.Descriptor as Descriptor20_0_, this_.CreatedBy as CreatedBy20_0_, this_.CreatedOn as CreatedOn20_0_, this_.ModifiedBy as ModifiedBy20_0_, this_.ModifiedOn as ModifiedOn20_0_, this_.IsDeleted as IsDeleted20_0_, this_.AssociationOneId as Associat9_20_0_, this_.AssociationTwoId as Associa10_20_0_ FROM Models this_ WHERE this_.Descriptor in (@p0, @p1, @p2, @p3, @p4, @p5) limit 2 offset 2 ]
Positional parameters: #0>a #1>b #2>c #3>d #4>e #5>f
[SQL: SELECT this_.ModelID as ModelID20_0_, this_.Version as Version20_0_, this_.Descriptor as Descriptor20_0_, this_.CreatedBy as CreatedBy20_0_, this_.CreatedOn as CreatedOn20_0_, this_.ModifiedBy as ModifiedBy20_0_, this_.ModifiedOn as ModifiedOn20_0_, this_.IsDeleted as IsDeleted20_0_, this_.AssociationOneId as Associat9_20_0_, this_.AssociationTwoId as Associa10_20_0_ FROM Models this_ WHERE this_.Descriptor in (@p0, @p1, @p2, @p3, @p4, @p5) limit 2 offset 2]
----> Devart.Data.SQLite.SQLiteException : 2nd parameter to sqlite3_bind() out of range
bind or column index out of range
which I see was questioned here http://www.devart.com/forums/viewtopic. ... ight=range, but answered by email.

Here's my code to support nhibernate. (Note I'm using the default sqlite dialect and configuration via fluent)

Fluent support

Code: Select all

   public class DevartSqliteConfiguration : PersistenceConfiguration
    {
       public static DevartSqliteConfiguration Standard
        {
            get { return new DevartSqliteConfiguration(); }
        }

       public DevartSqliteConfiguration()
        {
            Driver();
            Dialect();
            Raw("query.substitutions", "true=1;false=0");  
        }

       public DevartSqliteConfiguration InMemory()
        {
            Raw("connection.release_mode", "on_close");
            return ConnectionString(c => c
                .Is("Data Source=:memory:;Version=3;New=True;"));
            
        }

       public DevartSqliteConfiguration UsingFile(string fileName)
        {
            return ConnectionString(c => c
                .Is(string.Format("Data Source={0};Version=3;New=True;", fileName)));
        }

       public DevartSqliteConfiguration UsingFileWithPassword(string fileName, string password)
        {
            return ConnectionString(c => c
                .Is(string.Format("Data Source={0};Version=3;New=True;Password={1};", fileName, password)));
        }        
    }
Nhibernate support

Code: Select all

    public class DevartSqliteDriver : ReflectionBasedDriver
	{
        public DevartSqliteDriver()
            : base(
            "Devart.Data.SQLite",
            "Devart.Data.SQLite.SQLiteConnection",
            "Devart.Data.SQLite.SQLiteCommand")
		{
		}

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

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

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

		public override bool SupportsMultipleOpenReaders
		{
			get { return false; }
		}
		
		public override bool SupportsMultipleQueries
		{
			get { return true; }
		}
	}
[/url]

pleb
Posts: 37
Joined: Mon 23 Jun 2008 06:21
Location: Melbourne, Australia

Post by pleb » Thu 11 Feb 2010 14:35

Do not worry (not that you would), but I've worked it out.

The hbm2ddl.keywords error was from me being a little lazy and using the built-in sqlite dialect support.

changed

Code: Select all

        public override ISet GetReservedWords()
        {
            var result = new HashedSet();

            DataTable dtReservedWords = Connection.GetSchema(DbMetaDataCollectionNames.ReservedWords);
            
            foreach (DataRow row in dtReservedWords.Rows)
            {
                result.Add(row["name"].ToString());
            }

            return result;
        }
to (notice the row[...])

Code: Select all

		public virtual ISet GetReservedWords()
		{
			var result = new HashedSet();
			DataTable dtReservedWords = connection.GetSchema(DbMetaDataCollectionNames.ReservedWords);
			foreach (DataRow row in dtReservedWords.Rows)
			{
				result.Add(row["ReservedWord"].ToString());
			}
			return result;
		}

As for the other error is was simple the way the built-in dialect handled the limit clause.. I won't go into too much detail as I've included the fully code below.


And here's the working version....

Code: Select all


// Fluent configuration helper (99% borrowed from fluent core)

namespace AI.Core.Persistence.Providers.NHibernate.Configuration.Fluent.Database
{
    public class DevartSqlite : PersistenceConfiguration
    {
        public static DevartSqlite Standard
        {
            get { return new DevartSqlite(); }
        }

        public DevartSqlite()
        {
            Driver();
            Dialect();
            Raw("query.substitutions", "true=1;false=0");  
        }

        public DevartSqlite InMemory()
        {
            Raw("connection.release_mode", "on_close");
            return ConnectionString(c => c.Is("Data Source=:memory:;Version=3;"));
            
        }

        public DevartSqlite UsingFile(string fileName)
        {
            return ConnectionString(c => c.Is(string.Format("Data Source={0};Version=3;", fileName)));
        }

        public DevartSqlite UsingFileWithPassword(string fileName, string password)
        {
            return ConnectionString(c => c.Is(string.Format("Data Source={0};Version=3;Password={1};", fileName, password)));
        }        
    }
}

// driver (99% borrowed from core)

namespace AI.Core.Persistence.Providers.NHibernate.Driver
{
    public class DevartSqlite : ReflectionBasedDriver
    {
        public DevartSqlite()
            : base(
                "Devart.Data.SQLite",
                "Devart.Data.SQLite.SQLiteConnection",
                "Devart.Data.SQLite.SQLiteCommand")
        {
        }

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

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

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

        public override bool SupportsMultipleOpenReaders
        {
            get { return false; }
        }
		
        public override bool SupportsMultipleQueries
        {
            get { return true; }
        }
    }
}

// dialect (99% borrowed from core)

namespace AI.Core.Persistence.Providers.NHibernate.Dialect
{
    public class DevartSqlite : global::NHibernate.Dialect.Dialect
    {
        public DevartSqlite()
        {
            RegisterColumnType(DbType.Binary, "BLOB");
            RegisterColumnType(DbType.Byte, "INTEGER");
            RegisterColumnType(DbType.Int16, "INTEGER");
            RegisterColumnType(DbType.Int32, "INTEGER");
            RegisterColumnType(DbType.Int64, "INTEGER");
            RegisterColumnType(DbType.SByte, "INTEGER");
            RegisterColumnType(DbType.UInt16, "INTEGER");
            RegisterColumnType(DbType.UInt32, "INTEGER");
            RegisterColumnType(DbType.UInt64, "INTEGER");
            RegisterColumnType(DbType.Currency, "NUMERIC");
            RegisterColumnType(DbType.Decimal, "NUMERIC");
            RegisterColumnType(DbType.Double, "NUMERIC");
            RegisterColumnType(DbType.Single, "NUMERIC");
            RegisterColumnType(DbType.VarNumeric, "NUMERIC");
            RegisterColumnType(DbType.AnsiString, "TEXT");
            RegisterColumnType(DbType.String, "TEXT");
            RegisterColumnType(DbType.AnsiStringFixedLength, "TEXT");
            RegisterColumnType(DbType.StringFixedLength, "TEXT");

            RegisterColumnType(DbType.Date, "DATETIME");
            RegisterColumnType(DbType.DateTime, "DATETIME");
            RegisterColumnType(DbType.Time, "DATETIME");
            RegisterColumnType(DbType.Boolean, "INTEGER");
            RegisterColumnType(DbType.Guid, "UNIQUEIDENTIFIER");

            RegisterFunction("second", new SQLFunctionTemplate(NHibernateUtil.Int32, "strftime(\"%S\", ?1)"));
            RegisterFunction("minute", new SQLFunctionTemplate(NHibernateUtil.Int32, "strftime(\"%M\", ?1)"));
            RegisterFunction("hour", new SQLFunctionTemplate(NHibernateUtil.Int32, "strftime(\"%H\", ?1)"));
            RegisterFunction("day", new SQLFunctionTemplate(NHibernateUtil.Int32, "strftime(\"%d\", ?1)"));
            RegisterFunction("month", new SQLFunctionTemplate(NHibernateUtil.Int32, "strftime(\"%m\", ?1)"));
            RegisterFunction("year", new SQLFunctionTemplate(NHibernateUtil.Int32, "strftime(\"%Y\", ?1)"));
            RegisterFunction("substring", new StandardSQLFunction("substr", NHibernateUtil.String));
        }

        public override IDataBaseSchema GetDataBaseSchema(DbConnection connection)
        {
            return new SqLiteDataBaseMetaData(connection);
        }

        public override string AddColumnString
        {
            get
            {
                return "add column";
            }
        }

        public override string IdentitySelectString
        {
            get 
            { 
                return "select last_insert_rowid()"; 
            }
        }

        public override SqlString AppendIdentitySelectToInsert(SqlString insertSql)
        {
            return insertSql.Append("; " + IdentitySelectString);
        }

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

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

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

        public override string ForUpdateString
        {
            get 
            { 
                return string.Empty; 
            }
        }

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

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

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

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

        public override string IdentityColumnString
        {
            get
            {
                // identity columns in sqlite are marked as being integer primary key
                // the primary key part will be put in at the end of the create table,
                // so just the integer part is needed here
                return "integer";
            }
        }

        public override string Qualify(string catalog, string schema, string table)
        {
            StringBuilder qualifiedName = new StringBuilder();
            bool quoted = false;

            if (!string.IsNullOrEmpty(catalog))
            {
                if (catalog.StartsWith(OpenQuote.ToString()))
                {
                    catalog = catalog.Substring(1, catalog.Length - 1);
                    quoted = true;
                }
                
                if (catalog.EndsWith(CloseQuote.ToString()))
                {
                    catalog = catalog.Substring(0, catalog.Length - 1);
                    quoted = true;
                }

                qualifiedName.Append(catalog).Append(StringHelper.Underscore);
            }

            if (!string.IsNullOrEmpty(schema))
            {
                if (schema.StartsWith(OpenQuote.ToString()))
                {
                    schema = schema.Substring(1, schema.Length - 1);
                    quoted = true;
                }

                if (schema.EndsWith(CloseQuote.ToString()))
                {
                    schema = schema.Substring(0, schema.Length - 1);
                    quoted = true;
                }
                qualifiedName.Append(schema).Append(StringHelper.Underscore);
            }

            if (table.StartsWith(OpenQuote.ToString()))
            {
                table = table.Substring(1, table.Length - 1);
                quoted = true;
            }
            if (table.EndsWith(CloseQuote.ToString()))
            {
                table = table.Substring(0, table.Length - 1);
                quoted = true;
            }

            string name = qualifiedName.Append(table).ToString();
            
            if (quoted)
            {
                return OpenQuote + name + CloseQuote;
            }

            return name;

        }

        public override string NoColumnsInsertString
        {
            get 
            { 
                return "DEFAULT VALUES"; 
            }
        }

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

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

        public override bool SupportsVariableLimit
        {
            get
            {
                return true;
            }
        }
        
        public override SqlString GetLimitString(SqlString querySqlString, int offset, int limit)
        {
            SqlStringBuilder pagingBuilder = new SqlStringBuilder();
            pagingBuilder.Add(querySqlString);
            pagingBuilder.Add(" limit ");
            pagingBuilder.Add(Parameter.Placeholder);

            if (offset > 0)
            {
                pagingBuilder.Add(" offset ");
                pagingBuilder.Add(Parameter.Placeholder);
            }

            return pagingBuilder.ToSqlString();
        }
    }
}

// dialect schema (99% borrowed from core) (not all - this replaces SQLiteDataBaseMetaData in the core)

namespace AI.Core.Persistence.Providers.NHibernate.Dialect.Schema 
{
    public class SqLiteDataBaseMetaData : AbstractDataBaseSchema
    {
        private new DbConnection Connection { get; set; }

        public SqLiteDataBaseMetaData(DbConnection connection) 
            : base(connection) 
        {
            Connection = connection;
        }

        public override ITableMetadata GetTableMetadata(DataRow rs, bool extras)
        {
            return new SQLiteTableMetaData(rs, this, extras);
        }

        public override ISet GetReservedWords()
        {
            var result = new HashedSet();

            DataTable dtReservedWords = Connection.GetSchema(DbMetaDataCollectionNames.ReservedWords);
            
            foreach (DataRow row in dtReservedWords.Rows)
            {
                result.Add(row["name"].ToString());
            }

            return result;
        }
    }
}

Hope this helps ;)

Pleb

Edited to fix bug with "InMemory" fluent helper.
Edited to fix bug with fluent helper.

StanislavK
Devart Team
Posts: 1710
Joined: Thu 03 Dec 2009 10:48

Post by StanislavK » Fri 12 Feb 2010 16:47

Thank you for sharing your knowledge. We appreciate your assistance.

We plan to add the support for NHibernate model visual editing to Devart Entity Developer, but cannot provide any timeframe at the moment.

Post Reply