nHibernate - Populate a mapped function with namedQuery - nhibernate

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.

Related

Can you create a Restriction for a Detached Criteria on a primitive collection?

I would like to know if there is a way to create a Restriction on a primitive collection of a Model in NHibernate.3.3.3?
Here's the details:
class Parent {
IEnumerable<string> ChildNames { get; set; }
}
I need to search like so:
private DetachedCriteria BuildQuery() {
var inNames = { "Bob", "Sam", "Dan" };
var query = DetachedCriteria.For<Parent>("parent");
query.Add(Restrictions.In("ChildNames", inNames));
return query;
}
I found this old question that says it's not possible, but given the fact that it's old and doesn't have a ton of upvotes, I'd like to confirm before I refactor.
If I can do it and I'm totally botching it, I'll take that help as well!
In this scenario, we can use Projection (something less type-safe, then mapped Property, but more flexible).
Let's expect the mapping like this:
<bag name="ChildNames" inverse="false" lazy="true" table="[dbo].[ChildNames]"
cascade="all"
batch-size="25">
<key column="ParentId" />
<element type="System.String" column="ChildName" />
</bag>
Then we can adjust the Build query method like this:
protected virtual DetachedCriteria BuildQuery()
{
var inNames = new [] { "Bob", "Sam", "Dan" };
// parent query reference
var query = DetachedCriteria.For<Parent>("parent");
// reference to child query
var child = query.CreateCriteria("ChildNames");
// let's project the column name of the Element, e.g. 'Name'
var columnNameProjection = Projections.SqlProjection(
"ChildName as name", null, new IType[] { NHibernateUtil.String }
);
// in clause
child.Add(Restrictions.In(
columnNameProjection, inNames
));
return query;
}
And this is what we will get:
SELECT ...
FROM Parent this_
inner join [dbo].[ChildNames] childNames3_
on this_.ParentId=childNames3_.ParentId
WHERE ChildName in (#p0, #p1, #p2)
...
#p0=N'Bob',#p1=N'Sam',#p2=N'Dan'
The caveat:
While this is in deed working... the ChildName is used without the Alias. That could be pretty tricky to fulfill... so be careful, if there are more columns with the name ChildName in this scenario
I ended up, like many, refactoring the collection into a strong type.

NHibernate LINQ : Why is a query using the incorrect char value if I repeat the call?

Using NHibernate 3.3.1.4000 / .Net 3.5
I have an object I'm mapping to an existing Oracle 10g database :
public MyJob : LegacyOracleDbObjects {
public virtual int JobID { get; protected set; }
public virtual char Status { get; protected set; }
}
And a mapping :
public MyJobMap()
{
Table("JOB");
Id(x => x.JobID).Column("JOB_ID");
Map(x => x.Status).Column("STATUS");
}
And a LINQ query :
char selected_status = 'W'
query = from job in query
where job.Status == selected_status
select job;
Which works fine the first time I run it, but if I then try to run the query :
char selected_status = 'S'
query = from job in query
where job.Status == selected_status
select job;
NHibernate still passes the value 'W' (i.e. the first value for status) into the database query. I've checked the selected_status value passed into the query, and it looks fine, but the SQL query always contains the original value for selected_status, unless I add a filter for ID, in which case it then uses that status for all subsequent requests regardless of what I pass in.
Any idea why this char query parameter is behaving like this?
It's a bug in NHibernate. There are a few bug entries in the NHibernate issue tracker for it, e.g.: https://nhibernate.jira.com/browse/NH-2500

NHibernate 2 FKs to same PK in one-to-one relationship

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;
}
}
}

NHibernate SchemaExport failing to drop a table .... sometimes

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.

Named queries with nHibernate

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?