I’m stuck in a simple problem, I’d like to add a new custom restriction in NHibernate. I want to write a simple QueryOver with a fulltext index, an example with a Projections is here How to use Full Text Search for any property with QueryOver API
But I need a more flexibility so I’d like something like
criteria = criteria.WhereRestrictionOn(() => table.COLUMN_WITHFULLTEXTINDEX).Contains(valueToCheck);
Is it possible? I’m trying in the latest to days surfing over the NHibernate source code but I could’t get anything usefull.
Thanks
Advertisement
Answer
Since I must manage two different dbs dialect (SQL SERVER and ORACLE) I made the following.
A class that has all the custom criterions, now only one for full text
/// <summary> /// Full custom criterions /// </summary> public static class CustomCriterions { static CustomCriterions() { ExpressionProcessor.RegisterCustomMethodCall(() => FullTextSearch(null, ""), ProcessFullTextSearch); } /// <summary> /// Only a dummy method to force the static constructor <see cref="CustomCriterions"/> /// </summary> public static void Register() { } public static bool FullTextSearch(this string objectProperty, string valueToCheck) { throw new Exception("Not to be used directly - use inside QueryOver expression"); } private static ICriterion ProcessFullTextSearch(MethodCallExpression mce) { var arg0 = ExpressionProcessor.FindMemberProjection(mce.Arguments[0]).AsProjection(); var arg1 = ExpressionProcessor.FindMemberProjection(mce.Arguments[1]).AsProjection(); var projection = Projections.SqlFunction("contains", NHibernateUtil.Boolean, arg0, arg1); return new FullTextCriterion(projection); } }
Then a class who have to manage custom criterion
/// <summary> /// Custom criterion to have a full text search /// </summary> public class FullTextCriterion : AbstractCriterion { private readonly IProjection _projection; public FullTextCriterion(IProjection projection) { _projection = projection; } public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) { var typedValues = new List<TypedValue>(); if (_projection != null) { typedValues.AddRange(_projection.GetTypedValues(criteria, criteriaQuery)); } typedValues.Add(GetParameterTypedValue(criteria, criteriaQuery)); return typedValues.ToArray(); } private TypedValue GetParameterTypedValue(ICriteria criteria, ICriteriaQuery criteriaQuery) { return CriterionUtil.GetTypedValues(criteriaQuery, criteria, _projection, null).Single(); } public override IProjection[] GetProjections() { return new[] { _projection }; } public override string ToString() { return _projection.ToString(); } public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { var columnNames = CriterionUtil.GetColumnNamesForSimpleExpression( null, _projection, criteriaQuery, criteria, this, string.Empty); return DbUtil.GetFullText(columnNames); } }
FullTextCriterion isn’t strictly necessary but the ORACLE syntax is
CONTAINS (a, b)>0
So I must to add the “>0”.
DbUtil build the syntax by the dialect, e.g. in ORACLE
public SqlString GetFullText(SqlString[] columnNames) {
var sqlBuilder = new SqlStringBuilder(4 * columnNames.Length); sqlBuilder.Add(columnNames[0]); sqlBuilder.Add("> 0"); return sqlBuilder.ToSqlString(); }
Without using FullTextCriterion for ORACLE dialect I could use more simplest solutions that use custom projection instead custom criterion: NHibernate QueryOver Coalesce a property to another property
In addition, another simplest solution is to call the template as explained here http://www.andrewwhitaker.com/blog/2014/08/15/queryover-series-part-7-using-sql-functions/ and call it directly in ProcessFullTextSearch method. In this another solution it can write only a dummy ProjectionAsCriterion class which get only
public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { var columnNames = CriterionUtil.GetColumnNamesForSimpleExpression( null, _projection, criteriaQuery, criteria, this, string.Empty); return columnNames[0]; }
The template it could be written into the dialect class as
RegisterFunction("fulltextsearch", new SQLFunctionTemplate(NHibernateUtil.Boolean, "contains(?1, ?2) > 0"));