I am having a great deal of trouble getting named queries to work with nHibernate. My latest problem is getting the error message "could not execute query" with no additional information. Are there any complete examples I can download from somewhere because all the tutorials and documentation examples provide code snippits but only tell half the story about getting it to work.
Here is the code that is giving me problems.
Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Model.Entities
{
public class TableInfo
{
public string TABLENAME { get; set; }
public string COLUMNNAME { get; set; }
#region Overrides
public override int GetHashCode()
{
int result = TABLENAME.GetHashCode();
result += COLUMNNAME.GetHashCode();
return result;
}
public override bool Equals(object obj)
{
if (obj == null) return false;
TableInfo dict = (TableInfo)obj;
return
dict.TABLENAME.IsEqual(this.TABLENAME) &&
dict.COLUMNNAME.IsEqual(this.COLUMNNAME);
}
#endregion
}
}
Mapping File
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Model.Entities" assembly="Model" default-lazy="false">
<class name="Model.Entities.TableInfo, Model" table="UIM_TableColumnInfo">
<composite-id>
<key-property name="TABLENAME" column="TABLENAME" type="string"></key-property>
<key-property name="COLUMNNAME" column="COLUMNNAME" type="string"></key-property>
</composite-id>
</class>
<sql-query name="GetTableInfo">
<return alias="tableInfo" class="Model.Entities.TableInfo, Model">
<return-property name="TABLENAME" column="TABLENAME"/>
<return-property name="COLUMNNAME" column="COLUMNNAME"/>
</return>
<![CDATA[
select
info.tci_table_name TABLENAME
, info.tci_column_name COLUMNNAME
from ALL_TAB_COLS c
,( select 'DATE' TYPE_NAME, 'D' data_type_ind from dual
union select 'NUMBER','N' from dual
union select 'VARCHAR2','S' from dual
) ct
, UIM_TableColumnInfo info
where c.DATA_TYPE = ct.TYPE_NAME (+)
and c.column_id is not null
and UPPER(c.TABLE_NAME) = :TableName
and UPPER(c.COLUMN_NAME) = UPPER(info.tci_column_name (+))
order by c.column_id
]]>
</sql-query>
</hibernate-mapping>
Calling Code
public List<TableInfo> GetTableInfo(string tableName)
{
return m_TableInfoRepository
.NamedQuery("GetTableInfo")
.SetString("TableName", tableName)
.List<TableInfo>() as List<TableInfo>;
}
I assume that you have tested before the SQL in your client database, so I think that maybe we should see what is happening inside, so I can recommend you this links;
Named Query Error
Using NHibernate and Log4Net in ASP.NET 2.0 applications
How do I view the SQL that is generated by nHibernate?
Hope it helps.
The inner exception should provide the actual sql that was generated and tried to run. Paste this into a database query and run it directly in the database. This will help guide you. It will be a lot easier once you know why the SQL could not be executed
Maybe I'm wrong but it seems that could be a conflict between the table "TABLENAME" and the parameter ":TableName", what happens if you try to use another parameter name?
Related
I have a one-to-one relationship between an Envelope and a Transaction. I mapped it using a primary key association as below:
<class name="Envelope" table="T_ENVELOPE">
<id name="EnvelopeId" column="ENVELOPE_ID"><generator class="identity"/></id>
<one-to-one class="Transaction" cascade="all" name="Transaction" />
</class>
<class name="Transaction" table="T_TRANSACTION">
<id name="TransactionID" column="TRANSACTION_ID" type="long"><generator class="foreign"><param name="property">Envelope</param></generator></id>
<one-to-one name="Envelope" class="Envelope" constrained="true" />
</class>
This works fine, but my case is complicated since T_TRANSACTION has 2 FKs that both map to T_ENVELOPE's PK (ENVELOPE_ID): TRANSACTION_ID (as shown above) and OLD_DEPRECATED_TXN_ID. So, I have 2 FKs in T_ENVELOPE that have the exact same value (the envelope's id). How can I make NHibernate save ENVELOPE_ID not just in TRANSACTION_ID but also in OLD_DEPRECATED_TXN_ID as well?
I wish I didn't have this requirement, but I'm dealing with legacy code schemas and code. I know I can also do something like:
envelope.Save // this saves Envelope and Transaction's transaction ID but not OLD_DEPRECATED_TXN_ID
envelope.transaction.old_deprecated_txn_id = envelope.id
envelope.Save
But, that results in one insert and one update. How do I do a one insert only?
Here's an example:
T_ENVELOPE
----------
ENVELOPE_ID
1
9
121
T_TRANSACTION
-------------
TRANSACTION_ID | OLD_DEPRECATED_TXN_ID
1|1
9|9
121|121
Depending on your requirements you could:
Write a stored procedure to perform the insert and configure NHibernate to use this procedure in the mappings. I have taken this approach when working with legacy DBs and historic fields that were not being used but had to be populated
Use an Interceptor. Something along the followings lines should work. Not tested though!
public class TransactionEntityInterceptor : EmptyInterceptor
{
public override boolean OnSave(object entity,
object id,
object[] state,
string[] propertyNames,
IType[] types)
{
if (entity is Transaction)
{
Object TransactionID;
for ( int i=0; i<propertyNames.Length; i++ )
{
if ( "TransactionID" == propertyNames[i] )
{
TransactionID = state[i];
return true
}
}
for ( int i=0; i<propertyNames.Length; i++ )
{
if ( "OLD_DEPRECATED_TXN_ID" == propertyNames[i] )
{
state[i] = TransactionID;
return true;
}
}
return false;
}
else
{
return true;
}
}
}
I'm using Fluent nHibernate for my data layer, and I have a class that is mostly populated through nHibernate/LINQ but in a few advanced usages, needs to be populated by a stored procedure.
The problem I have is the class mapping includes a Formula. When I call a nHibernate/LINQ function, the underlying variable is populated as expected; when I call the GetNamedQuery() function it throws an error:
Value cannot be null. Parameter name: fieldname
It's completely logical that for a NamedQuery, the Formula field isn't populated (obviously I want a subquery here rather than a SQL statement run for every single record returned!), but I'd like to be able to populate the Formula value from the stored procedure - or at least the query not to throw an error.
Is this possible?
Map
public class QuoteMap : ClassMap<Quote>
{
public QuoteMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
...
Map(x => x.ResponseCount).Formula("(SELECT COUNT(*) FROM QuoteResponse WHERE QuoteResponse.QuoteId = Id and QuoteResponse.Status = " + (int)QuoteResponseRepository.Status.Live + ")");
}
}
Repository
// Works fine
public ICollection<Quote> GetAllByStatus(Status status)
{
using (ISession session = NHibernateHelper.OpenSession())
{
var quoteQuery = (from quote in session.Query<Quote>()
where quote.Status == (int)status
select quote).ToList();
return quoteQuery;
}
}
// Dies horribly
public ICollection<Quote> GetListPendingByCompany(Guid companyId, Status status)
{
using (ISession session = NHibernateHelper.OpenSession())
return session.GetNamedQuery("QuoteGetListPendingByCompany")
.SetGuid("Company_Id", companyId)
.SetInt32("QuoteStatus", (int)status)
.List<Quote>();
}
SQL
CREATE PROCEDURE [dbo].[QuoteGetListPendingByCompany]
#CompanyId uniqueidentifier,
#QuoteStatus int
AS
BEGIN
SET NOCOUNT ON;
SELECT
Quote.*,
(
SELECT
COUNT(*)
FROM QuoteResponse
WHERE QuoteResponse.QuoteId = Quote.Id
) AS ResponseCount -- Needs to populate what is currently a formula field
FROM Quote
-- ... code removed
END
StoredProcsMap.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" assembly="QMP.Data" namespace="QMP.Data.Model">
<sql-query name="QuoteGetListPendingByCompany" callable="true">
<return class="QMP.Data.Model.Quote, QMP.Data" />
<![CDATA[
exec dbo.QuoteGetListPendingByCompany #CompanyId=:Company_Id,#QuoteStatus=:QuoteStatus
]]>
</sql-query>
</hibernate-mapping>
You should not put the formula in the fluent mapping, this because you have it in your namedquery:
Map(x => x.ResponseCount).Formula("(SELECT COUNT(*) FROM QuoteResponse WHERE QuoteResponse.QuoteId = Quote.Id and QuoteResponse.Status = " + (int)QuoteResponseRepository.Status.Live + ")");
Just map the field as your do with any other field and you will be fine:
Map(x => x.ResponseCount);
Ok after your edit I can now see what you are trying to do, I am not sure its possible, you want to ignore the FORMULA column if using a NamedQuery that calls a SP?
What happens if you specify a ResultTransformer?
return session.GetNamedQuery("QuoteGetListPendingByCompany")
.SetGuid("Company_Id", companyId)
.SetInt32("QuoteStatus", (int)status)
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(Quote)))
.List<Quote>();
}
Although I suspect it will still barf.
I would like to use "hilo" generator but there is no any complete example how to create "specific" table as NH documentation says, and which values pass to it.
The following code fragments taken from NH tutorial
public class Cat
{
private Int64 id;
private string name;
private char sex;
private float weight;
public Cat()
{}
public virtual Int64 Id
{
get { return id; }
set { id = value; }
}
....
}
Mapper
<hibernate-mapping ...>
<class name="Cat" table="Cat">
<id name="Id" >
<column name="CatId" sql-type="Int64" not-null="true"/>
<generator class="hilo"/>
</id>
<property name="Name">
<column name="Name" length="16" not-null="true" />
</property>
....
</class>
</hibernate-mapping>
DB table "Cat"
CatId bigint NOT NULL
Name varchar(16) NOT NULL
Sex char(1) NULL
Weight real NULL
doesn't create anything in the database by default.
Parameters in the "id" node
<param name="table">hi_value</param>
<param name="column">next_value</param>
<param name="max_lo">100</param>
gives "Invalid object name 'hi_value'" error message, without them I'm getting "Invalid object name 'hibernate_unique_key'."
Cuid.Comb that is shown in their tutorial works good but gives 99.12% of fragmentation when I added in a loop 20K cat objects.
Can somebody point me to an example of "hilo" implementation or give a tip what I'm missing?
Thanks.
This solution solved my problem. It's fairly simple, don't know why on nhibernate site there is no tiny example like that.
You may be running into NH-2687.
In my MSSQL server I have a SQL view called AllFavourite. In order to load the data into my DTO class I have the following in my hbm.xml file...
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Domain.Model.Entities" assembly="Domain.Model">
<import class="AllFavourite"/>
</hibernate-mapping>
In my code I have the following.
public IList<AllFavourite> GetFavourites(int userId)
{
var query = Session
.CreateSQLQuery("SELECT * FROM AllFavourite where UserId=:UserId")
.SetInt32("UserId", userId)
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(AllFavourite)));
return query.List<AllFavourite>();
}
This works great and produces the results that I am after, however I would like to move the SQL from code into a named query into the hbm.xml file. So my hbm.xml file now looks like this
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Domain.Model.Entities" assembly="Domain.Model">
<import class="AllFavourite"/>
<query name="GetAllFavouriteByUserId">
<![CDATA[
SELECT * FROM AllFavourite WHERE UserId=:UserId
]]>
</query>
</hibernate-mapping>
and my code now looks like this
public IList<AllFavourite> GetFavourites(int userId)
{
var query = Session
.GetNamedQuery("GetAllFavouriteByUserId")
.SetInt32("UserId", userId)
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(AllFavourite)));
return query.List<AllFavourite>();
}
However when I run this I get an error:-
Parameter UserId does not exist as a
named parameter in [SELECT * FROM
AllFavourite WHERE UserId=:UserId]
So my question is it possible to use a named query in this manner?
The query tag expects a HQL query:
<query name="GetAllFavouriteByUserId">
<![CDATA[
from AllFavourite where UserId = :UserId
]]>
</query>
If you want to write a native sql query you should use the sql-query tag:
<sql-query name="GetAllFavouriteByUserId">
<return alias="foo" class="Foo"/>
<![CDATA[
SELECT {foo.ID} as {foo.ID},
{foo}.NAME AS {foo.Name}
FROM sometable
WHERE {foo}.ID = :UserId
]]>
</sql-query>
Don't you need this?
<query-param name='UserId' type='Integer'/>
I'm using NHibernate for the DAL of my application, and in particlular NHibernate's SchemaExport function to drop/recreate my database schema before the execution of unit tests. The issue I'm having is that when I run the unit tests and execute SchemaExport one of my tables fails to drop every second time. This would indicate to me that there is some kind of foreign key issue preventing SchemaExport dropping my table - but I can't figure it out. My schema is very simple - A person table, an Address table and a PersonAddress table to support the many-to-many relationship between the two.
public class Person
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Address> Addresses {get;set;}
public Person()
{
this.Addresses = new List<Address>();
}
}
public class Address
{
public virtual int Id { get; set; }
public virtual string Street1 { get; set; }
public virtual string Street2 { get; set; }
public virtual string Postcode { get; set; }
}
and my NHibernate mapping files...
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyHibernate"
namespace="MyHibernate"
>
<class name="Person" table="Person.Person">
<id name="Id" column="Id">
<generator class="native" ></generator>
</id>
<property name="Name" column="Name" length="50"></property>
<bag name="Addresses" table="[Person].[PersonAddress]" lazy="false" cascade="all">
<key column="PersonId" foreign-key="FK_Person_Person_Id"></key>
<many-to-many class="Address" column="AddressId"></many-to-many>
</bag>
</class>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyHibernate"
namespace="MyHibernate"
>
<class name="Address" table="Person.Address">
<id name="Id" column="Id">
<generator class="native" ></generator>
</id>
<property name="Street1" column="Street1" length="50"></property>
<property name="Street2" column="Street2" length="50"></property>
<property name="Postcode" column="Postcode" length="50"></property>
</class>
and when I run `var cfg = new Configuration();
cfg.Configure();
cfg.AddAssembly(typeof(Person).Assembly);
new SchemaExport(cfg).Execute(false, true, false, false)
I get a SQL exception saying:
MyHibernate.Tests.GenerateSchemaFixture.Can_Generate_Schema:
NHibernate.HibernateException : There is already an object named 'Person' in the database.
----> System.Data.SqlClient.SqlException : There is already an object named 'Person' in the database.
Any ideas?
This has been a recurring problem for me for a long time. The problem was not solved by executing drop first or using the execute method (the drop method is a shortcut method which executes the Execute method).
After looking in the NHibernate source code I found the source of the problem. NHibernate uses hashcodes to store foreign key names in the database. The problem with hashcodes however is that they change over time, clr-version and appdomain. You can't rely on hashcodes for equality. (ref: http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx) This is why NHibernate can't always remove the foreignkeys, hence cannot drop the tables.
This is a snapshot from the NHibernate sourcecode which is used to create unique foreign key names:
public string UniqueColumnString(IEnumerable iterator, string referencedEntityName)
{
// NH Different implementation (NH-1339)
int result = 37;
if (referencedEntityName != null)
{
result ^= referencedEntityName.GetHashCode();
}
foreach (object o in iterator)
{
result ^= o.GetHashCode();
}
return (name.GetHashCode().ToString("X") + result.GetHashCode().ToString("X"));
}
So this problem isn't going to be solved by NHibernate, you have to do it yourself. I solved the problem by executing the following method before the schema is created. The method removes all foreign keys only from the tables that are mapped with NHibernate:
private static void DropAllForeignKeysFromDatabase()
{
var tableNamesFromMappings = Configuration.ClassMappings.Select(x => x.Table.Name);
var dropAllForeignKeysSql =
#"
DECLARE #cmd nvarchar(1000)
DECLARE #fk_table_name nvarchar(1000)
DECLARE #fk_name nvarchar(1000)
DECLARE cursor_fkeys CURSOR FOR
SELECT OBJECT_NAME(fk.parent_object_id) AS fk_table_name,
fk.name as fk_name
FROM sys.foreign_keys fk JOIN
sys.tables tbl ON tbl.OBJECT_ID = fk.referenced_object_id
WHERE OBJECT_NAME(fk.parent_object_id) in ('" + String.Join("','", tableNamesFromMappings) + #"')
OPEN cursor_fkeys
FETCH NEXT FROM cursor_fkeys
INTO #fk_table_name, #fk_name
WHILE ##FETCH_STATUS=0
BEGIN
SET #cmd = 'ALTER TABLE [' + #fk_table_name + '] DROP CONSTRAINT [' + #fk_name + ']'
exec dbo.sp_executesql #cmd
FETCH NEXT FROM cursor_fkeys
INTO #fk_table_name, #fk_name
END
CLOSE cursor_fkeys
DEALLOCATE cursor_fkeys
;";
using (var connection = SessionFactory.OpenSession().Connection)
{
var command = connection.CreateCommand();
command.CommandText = dropAllForeignKeysSql;
command.ExecuteNonQuery();
}
}
Works perfect for me. I hope someone else will be able to use it as well.
This is where I grabbed the sql script to drop all foreign keys: http://mafudge.mysite.syr.edu/2010/05/07/dropping-all-the-foreign-keys-in-your-sql-server-database/
Found a way around this problem. I just broke out the use of SchemaExport into two calls. The first to drop an existing schema, the second to re-create it.
var cfg = new Configuration();
cfg.Configure();
cfg.AddAssembly(typeof(Person).Assembly);
SchemaExport se = new SchemaExport(cfg);
//drop database
se.Drop(true, true);
//re-create database
se.Create(true, true);
Using the above code in the [TestFixtureSetUp] of my test class works well. I now have a clean database schema to use for integration tests.
I too received this error message; however, it was because I had removed a class that had a many-to-one relationship with another class. As such, there was nothing telling NHibernate to drop the [now orphaned, but still referenced] table on the next test run.
I simply dropped it by hand and all was well.
I had this same problem, I found that the Execute method only seems to work properly to do the full drop and recreate if you call it like so:
new SchemaExport(cfg).Execute(true, true, false, false)
I am not sure why the script parameter needs to be true, but setting it to true also solves the problem.