Hibernate Criteria API with access="field" mapping - nhibernate

I have a problem when using Hibernate Criteria API:
var query = session.QueryOver<MyClass>().Where(param => param.Name == "myFilterName").List<MyClass>();
If a run this statement, a NHibernate.QueryException is thrown:
could not resolve property: Name of: MyClass
And in the StackTrace:
at NHibernate.Persister.Entity.AbstractPropertyMapping.ToType(String
propertyName)
MyClass.hbm.xml file has the property mapped of this way:
<property name="name" access="field">
<column name="NAME" length="50" not-null="true" />
</property>
I think that the problem comes because hibernate can not access the property "Name" of MyClass because is mapped with access="field", but I can not change this way to access the property due application design requirements.
The idea is create querys by using Criteria API with lambda expressions in order to avoid hardcoded string property names.
Also I´ve tried with a Expression with the same exception result:
var criterion = Expression.Where<MyClass>(param => param.Name == "myFilterName");
var result = session.CreateCriteria<MyClass>().Add(criterion).List<MyClass>();
Somebody knows how I can indicate to Criteria API that MyClass has the properties mapped as access="field"?
Help very appreciated.

Not sure of what you want to achieve, having the class code might help.
Anyway, from what you provided, I guess the mapping should be :
<property name="Name" access="field.camelcase">
see http://www.nhforge.org/doc/nh/en/#mapping-declaration-property

Related

HBM mapping both field and property

I have the following class:
public class Foo
{
//...
protected int? someData;
public int? SomeData
{
get {return someData;}
set {someData = value;}
}
//...
}
This class is mapped in HBM file:
<class name="Foo" table="Foo">
//....
<property name="someData" access="field" column="SOME_DATA" />
//....
</class>
For legacy reasons, the columns were mapped to a fields (lowercase), which are unaccessible (not public properties - uppercase).
These lowercase mappings are using multiple times in the project as part of HQL strings, criteria-based queries and so on. It is close to impossible to find all usages.
The problem is that such usage makes it impossible to use even on simple lambda:
session.Query<Foo>().Select(f=>f.SomeData)
throws error, as uppercase "SomeData" is not mapped, and
session.Query<Foo>().Select(f=>f.someData)
does not compile, as lowercase "someData" is protected.
What happens, if I will map BOTH field and property:
<property name="someData" access="field" column="SOME_DATA" />
<property name="SomeData" access="property" column="SOME_DATA" />
I tried quickly, and it seems to work, but will it have any drawback?
Is there any other simple solution, that will not require editing every criteria-based query in project?
Thanks for any help.
You can map the same column multiple times but with recent NHibernate versions only one of the properties need to be mapped as modifiable. So just add insert="false" update="false" to your additional mappings to avoid any issues in future:
<property name="someData" access="field" column="SOME_DATA" />
<property name="SomeData" access="property" column="SOME_DATA" update="false" insert="false" />

"Ambiguous column name" exception when using order-by in collection mapping

Consider this class that represents a node in a hierarchical structure:
public class Node
{
public Node()
{
Children = new List<Node>();
}
public virtual int Id { get; set; }
public virtual IList<Node> Children { get; set; }
public virtual Node Parent { get; set; }
public virtual int Position
{
get { return Parent == null ? -1 : Parent.Children.IndexOf(this); }
set { }
}
}
The mapping looks like this (as NHibernate does not support lists in bidirectional associations, I use a bag here and have the children determine their position automatically):
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="AmbiguousColumn" assembly="AmbiguousColumn" xmlns="urn:nhibernate-mapping-2.2">
<class name="Node">
<id name="Id" type="Int32">
<generator class="identity" />
</id>
<bag name="Children" inverse="true" cascade="all" order-by="Position">
<key column="Parent" />
<one-to-many class="Node" />
</bag>
<many-to-one name="Parent" />
<property name="Position" />
</class>
</hibernate-mapping>
To get all nodes with their children loaded I'd use a query like this:
var nodes = session.QueryOver<Node>()
.Fetch(x => x.Children).Eager
.List();
However, executing this results in an exception:
NHibernate.Exceptions.GenericADOException: could not execute query
[...(sql)...] ---> System.Data.SqlClient.SqlException: Ambiguous column name 'Position'.
The SQL:
SELECT
this_.Id as Id0_1_,
this_.Parent as Parent0_1_,
this_.Position as Position0_1_,
children2_.Parent as Parent3_,
children2_.Id as Id3_,
children2_.Id as Id0_0_,
children2_.Parent as Parent0_0_,
children2_.Position as Position0_0_
FROM
Node this_
left outer join
Node children2_
on this_.Id=children2_.Parent
ORDER BY
Position
I understand why this happens: NH joins the same table twice, but uses the order clause without qualifying the column name.
The question is: how can I make this scenario work? Resorting to instead of is probably difficult as I'd like to have a bidirectional relation.
There are a couple of similar question on SO, but nowhere did I find an actual solution.
Update: the error is database/driver specific. Using the Sql Server CE (e.g. SqlServerCeDriver and MsSqlCe40Dialect) I get the proper query. Using Sql Server (e.g. Sql2008ClientDriver and MsSql2012Dialect) produces the unqualified queries.
According to my own tests, this behavior still exists in the master branch on github.
A gist with a test case: https://gist.github.com/anonymous/5377535
I think I found the cause of the problem and viable workarounds:
The cause of the issue is the fact that the column is called "Position", which is a reserved word in ODBC according to http://msdn.microsoft.com/en-us/library/ms189822.aspx
This combined with the fact that the default value for NH's hbm2ddl.keywords property is set to "keywords" somehow caused NH not to qualify the order-by clause, probably because it though "Position" was a keyword, not a column.
http://nhforge.org/blogs/nhibernate/archive/2009/06/24/auto-quote-table-column-names.aspx
Ways to fix it:
1) Use a different name for the property - one that isn't a keyword. In this case, PositionInParent would have worked without any issues.
2) Quote the order by clause properly using back-ticks.
<bag name="Children" inverse="true" cascade="all" order-by="`Position`">
Or whatever it takes in your mapping API of choice, e.g. in mapping by code:
cls.Bag(x => x.Children,
map =>
{
map.Inverse(true);
map.Cascade(Cascade.All);
map.Key(key => key.Column("Parent"));
map.OrderBy("`Position`"); // note that you must not use a lambda expression in this case
},
map => map.OneToMany());
3) Disable keyword auto import, ie. set hbm2ddl.keywords to none (neither keywords nor auto-quote will work):
<property name="hbm2ddl.keywords">none</property>
Or programmatically:
config.DataBaseIntegration(db => db.KeywordsAutoImport = Hbm2DDLKeyWords.None);
You can still auto-quote reserved words by calling SchemaMetadataUpdater.QuoteTableAndColumns just before building the session factory.
SchemaMetadataUpdater.QuoteTableAndColumns(config);
I'll stick with approach 3 for now as it is the most painless so far.

nhibernate user type composition

Say I have an immutable ICompositeUserType to deal with a DateRange, or Money, and then it turns out that I have another value object (ie, immutable) that has either a DateRange or Money property value in it. For example, a Name that has an EffectivePeriod (DateRange).
The DataRangeUserType encapsulates some logic I wouldn't want duplicated.
Can I reuse my DataRangeUserType inside of a NameUserType? How?
Cheers,
Berryl
UPDATE
Below is the summary comment on ICompositeUserType taken from the NHibernate source code which suggests what I am thinking of can be done, just not sure how. Admittedly, the component strategy is easy and works great, until you think you might want to use the same compoent elsewhere
/// <summary>
/// A UserType that may be dereferenced in a query.
/// This interface allows a custom type to define "properties".
/// These need not necessarily correspond to physical .NET style properties.
///
/// A ICompositeUserType may be used in almost every way
/// that a component may be used. It may even contain many-to-one
/// associations.
///
/// ...
/// </summary>
Using component mapping
<component name="MyCompositeComponent" class="...">
<property name="Name" not-null="true" length="50"/>
<property name="Price" type="...MoneyUserType">
<column name="Amount"/>
<column name="Currency"/>
</property>
<property name="EffectivePeriod" type="...DateRangeUserType">
<column name="EffectiveStart"/>
<column name="EffectiveEnd"/>
</property>
</component>
Hibernate lets you compose value types in Components:
<class name="MyClass" table="MyTable" >
...
<component name="_namedPeriod">
<property name="_name" column="PeriodName" />
<property name="_effectivePeriod"
type="MyNamespace.DataRangeUserType, MyAssembly" >
<column name="PeriodStart" />
<column name="PeriodEnd" />
</property>
</component>
...
</class>
Classes look like this:
// entity
public class MyClass {
private NamedPeriod _namedPeriod;
}
// immutable value object
public class NamedPeriod {
private readonly String _name;
// immutable value object
private readonly DateRange _effectivePeriod;
}
The idea that you use UserTypes for primitives like DateRange and Money and Component for a larger immutable value objects. Components can also include other components.

NHibernate: how to map a Point?

I have a class which contains a collection of Points (PointF's rather).
I want to be able to persist instances of that class using NHibernate.
My class looks somewhat like this (simplified):
public class MyClass
{
public IDictionary<string, PointF> Points = new Dictionary<string, PointF>();
public void AddPoint( location, PointF position )
{
Points.Add(location, position);
}
}
The mapping of this collection looks like this (simplified):
<map name="Points" table="Locations">
<key column="MyClassId" />
<index column="LocationName" />
<composite-element class="System.Drawing.PointF, System.Drawing">
<property name="X" column="X" />
<property name="Y" column="Y" />
</composite-element>
</map>
The problem now is, that NHibernate throws an error while processing the mapping file, since PointF is not a known (mapped) entity.
How can I solve this in the most simple way ?
How can I make sure that NHibernate is able to persist my collection of locations (with their coordinates (point) ?
The problem is not that you didn't map the type PointF - because you map it as composite-element, which is correct.
When mapping such types you need to make sure
that properties are writable (which is luckily the case here)
that it has a default constructor, which is not the case here.
So how should NH create new instances when there is not default constructor? It can't.
Your options are:
implement an interceptor or NH event. I think it is possible to inject code there which creates instances of certain types, but I don't know how.
implement a NH user type (derived from ICompositeUserType), which is not too hard to do
map another type (eg. a wrapper to PointF)

NHibernate nvarchar/ntext truncation problem

I'm using nhibernate to store some user settings for an app in a SQL Server Compact Edition table.
This is an excerpt the mapping file:
<property name="Name" type="string" />
<property name="Value" type="string" />
Name is a regular string/nvarchar(50), and Value is set as ntext in the DB
I'm trying to write a large amount of xml to the "Value" property. I get an exception every time:
#p1 : String truncation: max=4000, len=35287, value='<lots of xml..../>'
I've googled it quite a bit, and tried a number of different mapping configurations:
<property name="Name" type="string" />
<property name="Value" type="string" >
<column name="Value" sql-type="StringClob" />
</property>
That's one example. Other configurations include "ntext" instead of "StringClob". Those configurations that don't throw mapping exceptions still throw the string truncation exception.
Is this a problem ("feature") with SQL CE? Is it possible to put more than 4000 characters into a SQL CE database with nhibernate? If so, can anyone tell me how?
Many thanks!
Okay, with many thanks to Artur in this thread, here's the solution:
Inherit from the SqlServerCeDriver with a new one, and override the InitializeParamter method:
using System.Data;
using System.Data.SqlServerCe;
using NHibernate.Driver;
using NHibernate.SqlTypes;
namespace MySqlServerCeDriverNamespace
{
/// <summary>
/// Overridden Nhibernate SQL CE Driver,
/// so that ntext fields are not truncated at 4000 characters
/// </summary>
public class MySqlServerCeDriver : SqlServerCeDriver
{
protected override void InitializeParameter(
IDbDataParameter dbParam,
string name,
SqlType sqlType)
{
base.InitializeParameter(dbParam, name, sqlType);
if (sqlType is StringClobSqlType)
{
var parameter = (SqlCeParameter)dbParam;
parameter.SqlDbType = SqlDbType.NText;
}
}
}
}
Then, use this driver instead of NHibernate's in your app.config
<nhibernateDriver>MySqlServerCeDriverNamespace.MySqlServerCeDriver , MySqlServerCeDriverNamespace</nhibernateDriver>
I saw a lot of other posts where people had this problem, and solved it by just changing the sql-type attribute to "StringClob" - as attempted in this thread.
I'm not sure why it wouldn't work for me, but I suspect it is the fact that I'm using SQL CE and not some other DB. But, there you have it!
<property name="Value" type="string" />
<column name="Value" sql-type="StringClob" />
</property>
I'm assuming this is a small typo, since you've closed the property tag twice. Just pointing this out, in case it wasn't a typo.
Try <property name="Value" type="string" length="4001" />
Tried:
<property name="Value" type="string" length="4001" />
and
<property name="Value" type="string" >
<column name="Value" sql-type="StringClob" length="5000"/>
</property>
Neither worked, I'm afraid... Same exception - it still says that the max value is 4000.
Why are you using the sub-element syntax?
try:
<property name='Value' type='StringClob' />
On my current deplyoment of SQL CE and NHibernate I use a length of 4001. Then NHibernate generates the stuff as NTEXT instead of NVARCHAR.
Try that.
Another thing to use with NHibernate and SQL CE is:
<session-factory>
...
<property name="connection.release_mode">on_close</property>
</session-factory>
That solves some other problems for me atleast.
After reading your post this modification got it working in my code
protected override void InitializeParameter(IDbDataParameter dbParam,string name,SqlType sqlType)
{
base.InitializeParameter(dbParam, name, sqlType);
var stringType = sqlType as StringSqlType;
if (stringType != null && stringType.LengthDefined && stringType.Length > 4000)
{
var parameter = (SqlCeParameter)dbParam;
parameter.SqlDbType = SqlDbType.NText;
}
}