Calling instance methods inside a where conditon in Nhibernate - nhibernate

I have this piece of code in a TaskRepository :
public List<Task> GetActiveTasks()
{
return SessionContainer.Session
.Query<Task>()
.Where(t => t.IsActive())
.ToList();
}
And this is the IsActive() method in the Task Class
public virtual bool IsActive()
{
return States.ToList().Max().Name == "Active";
}
My problem is that GetActiveTasks() returns NotSupportedException.
Does anyone knows whats the problem ? I think it is not possible to call instance methods in the Where predicate. If that is not possible, is there any workaround for this? I need to get all the active tasks but I dont know how to do it if it is not like this.

The lambdas given in the Query have to be translated to sql. How should NHibernate interprete a method implemented by you? it can't. However you could map IsActive as a Formula property and query that. Something like:
Map(x => x.IsActive).Formula("(SELECT ... FROM (Select s.Name FROM States s WHERE s.task_id = Id ORDER BY Id desc LIMIT 1) maxState WHERE maxState.Name == Active)")

One option is to refactor the Task class like this:
public static Expression<Func<Task, bool>> IsActiveExpr =
(task) => task.States.Max().Name == "Active";
private static Func<Task, bool> _IsActiveCompiled = IsActiveExpr.Compile();
public virtual bool IsActive()
{
return IsActiveCompiled(this);
}
Now you can use IsActiveExpr in LINQ queries.
Another options is to extend the NHibernate query provider so that it recognizes the IsActive() method directly. Do a search, or see for instance my reply to this question: Lambda string as VARCHAR. But care might need to be taken to avoid duplicating logic.

Related

Querying with linq a collection mapped as a map (IDictionary)

Using NHibernate, I have a collection of entities mapped as a dictionary.
By example, class A has a collection of B named Children, mapped as a IDictionary<int, B>. B has a property Name.
Querying class A base on some condition on B children unrelated to their dictionary indexation is quite straightforward with HQL:
from A where A.Children.Name = 'aName'
Runs flawlessly.
But for achieving the same with LINQ, this is quite less straightforward:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
Fails with message could not resolve property: Values of: B
So yes, we can trick it through
IQueryable<A> query = ...;
query.Where(a => ((ICollection<B>)a.Children).Any(b => b.Name == "aName"));
That does works and yields expected results.
But this looks to me a bit ugly, I would rather not have to do that 'invalid' cast (at least 'invalid' outside of linq2NH context).
Is there any better way for querying a children collection mapped as a IDictionary with Linq and NHibernate?
As an exercise, I have decided to extend linq-to-nhibernate for supporting Values. This give a solution to the question.
There is many ways for extending linq2NH, see this list. Here, I need to add a new 'generator', as in my answer to another question.
First you need a bunch of using:
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;
using System.Collections.Generic;
using NHibernate.Hql.Ast;
using NHibernate.Linq.Visitors;
using NHibernate.Linq.Functions;
Then, implement HQL translation for Values.
public class DictionaryValuesGenerator : BaseHqlGeneratorForProperty
{
public override HqlTreeNode BuildHql(
MemberInfo member, Expression expression,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
// Just have to skip Values, HQL does not need it.
return visitor.Visit(expression).AsExpression();
}
}
Extend the default linq2NH registry with your generator:
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public override bool TryGetGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
if (base.TryGetGenerator(property, out generator))
return true;
return TryGetDictionaryValuesGenerator(property, out generator);
}
private DictionaryValuesGenerator _dictionaryValuesGenerator =
new DictionaryValuesGenerator();
protected bool TryGetDictionaryValuesGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
generator = null;
if (property == null || property.Name != "Values")
return false;
var declaringType = property.DeclaringType;
if (declaringType.IsGenericType)
{
var genericType = declaringType.GetGenericTypeDefinition();
if (genericType != typeof(IDictionary<,>))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
if (declaringType != typeof(IDictionary))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
}
I had quite a hard time figuring out how to register a generic class property generator. There is built-in support for many cases including generic dictionaries methods through derived class of GenericDictionaryRuntimeMethodHqlGeneratorBase, but apparently no support for generic dictionaries properties. So I have ended up 'hard coding' it in the TryGetGenerator method for properties.
Now configure NH to use your new registry. With hibernate.cfg.xml, add following property node under session-factory node:
<property name="linqtohql.generatorsregistry">YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName</property>
Now this does work:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
Disclaimer: done only as an exercise, I have not even committed that in my actual code. I am currently no more using any map in my mappings. I have added some just for testing, then I have undone all.

How would I alter the SQL that Linq-to-Nhibernate generates for specific columns?

To take advantage of Full text indexing on MariaDB 10, I need to use this new "MATCH AGAINST" syntax in the sql string.
http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html#function_match
I think it would be really cool if, for certain columns only, I could override linq-to-nhibernate to change the sql it generates when I use
.Where(x => FullTextIndexedStringProperty.Contains("Some word")).ToList().
Who can give me some general directions on how to get started?
This will get you a very simple MATCH ... AGAINST clause. If you want to get more complex (more arguments, specifying the search modifier), you'll have to make some bigger changes. Hopefully this will get you started though:
Create a new dialect and register a simple MATCH (...) AGAINST (...) function:
public class CustomMySQLDialect : MySQLDialect
{
public CustomMySQLDialect()
{
this.RegisterFunction(
"matchagainst",
new SQLFunctionTemplate(
NHibernateUtil.Boolean,
"match (?1) against (?2)"));
}
}
Create a static extension method on string that you'll use in LINQ statements:
public static class LinqExtensions
{
public static bool MatchAgainst(this string source, string against)
{
throw new NotImplementedException();
}
}
Create a new LINQ to HQL generator class that associates the method with the SQL function we registered in the custom dialect:
public class MatchAgainstGenerator : BaseHqlGeneratorForMethod
{
public MatchAgainstGenerator()
{
this.SupportedMethods = new[]
{
ReflectionHelper.GetMethod(() => LinqExtensions.MatchAgainst(null, null))
};
}
public override HqlTreeNode BuildHql(
MethodInfo method,
System.Linq.Expressions.Expression targetObject,
ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor)
{
return treeBuilder.BooleanMethodCall(
"matchagainst",
arguments.Select(visitor.Visit).Cast<HqlExpression>());
}
}
Create a custom LinqToHqlGeneratorsRegistry:
public class MyLinqToHqlRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public MyLinqToHqlRegistry()
{
var generator = new MatchAgainstGenerator();
RegisterGenerator(typeof(LinqExtensions).GetMethod("MatchAgainst"), generator);
}
}
Use your custom dialect, and Linq to HQL registry either in your cfg.xml file or in code:
var cfg = new Configuration()
.DataBaseIntegration(db =>
{
db.Dialect<CustomMySQLDialect>();
})
.LinqToHqlGeneratorsRegistry<MyLinqToHqlRegistry>();
Finally, use your extension method in a LINQ-to-NHibernate query:
session.Query<Article>()
.Where(a => a.Body.MatchAgainst("configured"))
.ToList()
.Dump();
This will generate SQL that looks like this:
select
userquery_0_.Id as Id51_,
userquery_0_.Title as Title51_,
userquery_0_.Body as Body51_
from
articles userquery_0_
where
match (userquery_0_.Body) against ('configured');
Again, this won't help if you have more complicated requirements. But hopefully this is at least a good starting point.
In case anyone is curious about how to make this support more complex scenarios, here are the problems I think you'd run into:
Separating the arguments to MATCH from those to AGAINST.
Registering a custom SQL function with NHibernate that can take an arbitrary number of arguments in different places
Creating the correct HQL even after solving the two issues above.

Ignore column using mapping by code in HNibernate

I'm using mapping by code in NHibernate.
I got a class with several properties. One of them is not related to any columns in DB but still has getter and setter.
I use ConventionModelMapper not ModelMapper. The first one assumes that all properties are mapped.
How i can tell to NHibernate to ignore it?
I find it easier to just create an attribute, attach that attribute to the property, and check for it in the mapper.IsPersistentProperty method. Something like this:
class IngnoreAttribute : Attribute
{
}
class Foo
{
[Ignore]
public virtual string Bar { get; set; }
}
mapper.IsPersistentProperty((mi, declared) => mi.GetCustomAttribute<IgnoreAttribute>() == null);
This way, I don't have to keep a list of properties to be ignored at the mapping codes.
Why not map the properties you want and leave the ones not needed to be mapped
check this
You can manage the persistence of ConventionModelMapper as following:
mapper.BeforeMapProperty += (mi, propertyPath, map) =>
{
// Your code here using mi, propertyPath, and map to decide if you want to skip the property .. can check for property name and entity name if you want to ignore it
};
A better answer would be:
mapper.IsPersistentProperty((mi, declared) =>
{
if (mi.DeclaringType == typeof (YourType) && mi.Name == "PropertyNameToIgnore")
return false;
return true;
});
If you do not mention the property that should be ignored in your NHibernate mapping, NHibernate will ignore it.

Linq repository and GetTable<T>()

I'm following the fairly standard L2S repository pattern, using the following as one of the methods
public IEnumerable<T> GetAllByFilter(Func<T, bool> expression)
{
return _dataContext.GetTable<T>().Where(expression);
}
I'm a bit miffed to see that the call to GetTable appears to literally get the table, with the Where expression presumably evaluated in-memory afterwards.
So a simple call like
var order = GetAllByFilter(o => o.OrderNumber == 1);
which should only ever return one record, is fetching the entire 50000 record database.
Is Linq normally this bad? Or am I missing something?
Change:
public IEnumerable<T> GetAllByFilter(Func<T, bool> expression)
{
return _dataContext.GetTable<T>().Where(expression);
}
To:
public IQueryable<T> GetAllByFilter(Expression<Func<T, bool>> expression)
{
return _dataContext.GetTable<T>().Where(expression);
}
This will use Queryable (i.e. SQL) instead of Enumerable (i.e. local) and therefore will perform much better.

Using DetachedCriteria to return generic list of non-AR type

I'm fiddling with my repository class and attempted to execute a query with a detached criteria. However, it doesn't seem to like me setting the result transformer to a non AR-type.
public class IncidentRepository
{
public static IList<AuditReport> GetAllIncidentsToAudit()
{
DetachedCriteria dc = DetachedCriteria.For<Incident>("i")
.SetProjection(
Projections.ProjectionList()
.Add(Projections.Property("i.Id"), "IncidentId")
.Add(Projections.Property("l.Id"), "LocationId")
)
.CreateCriteria("Locations", "l")
.Add(Expression.Eq("l.PrimaryLocationFlag", "T"))
.SetResultTransformer(Transformers.AliasToBean<AuditReport>());
return ActiveRecordMediator<AuditReport>.FindAll(dc);
}
}
public class AuditReport
{
public int IncidentId { get; set; }
public int LocationId { get; set; }
}
The error I get when executing this query is:
You have accessed an ActiveRecord class that wasn't properly initialized. There are two possible explanations: that the call to ActiveRecordStarter.Initialize() didn't include castle.AuditReport class, or that castle.AuditReport class is not decorated with the [ActiveRecord] attribute.
I understand the error but how can I return a strongly typed list of a non-AR type? I've looked at what NHibernate.Transform offers but nothing stands out.
Also, is doing this bad practice?
Edit: I managed to solve it by gaining access to the underlying database session and executing my criteria from there.
ISession sess = ActiveRecordMediator.GetSessionFactoryHolder().
CreateSession(typeof(ActiveRecordBase));
ICriteria criteria = sess.CreateCriteria<Incident>("i")
.SetProjection(
Projections.ProjectionList()
.Add(Projections.Property("i.Id"), "IncidentId")
.Add(Projections.Property("l.Id"), "LocationId")
)
.CreateCriteria("Locations", "l")
.Add(Expression.Eq("l.PrimaryLocationFlag", "T"))
.SetResultTransformer(Transformers.AliasToBean<AuditReport>());
return criteria.List<AuditReport>();
Now I'm wondering, is there another of achieving this without manually creating a new session?
If you want to use a class for a transformation-result, then you might need to "import" it to ActiveRecord.
Try decorating any AR class with (or maybe the AuditReport, but it might need to be a AR-decorated class):
[Import( typeof( AuditReport ), "AuditReport" )]
That would translate to the NHibernate import attribute in xml-config.
This atleast solve it when using a class construct in HQL, like this:
select new OrderSummary(o.Foo, count(o.Foo))
from Orders o
group by o.Bar