How to do a full text search with Nhibernate and get result ranks? - nhibernate

I have a SQL 2008 R2 database with full text indexing set up and would like to use NHibernate to get back search results with ranking. I've figured out the SQL queries usng FULLTEXTTABLE to get result rankings, but I'm struggling with how to use NHibernate to get results with the ranking value since it doesn't map to an actual column in any table.
Any pointers?

(First of all the following syntax will be a bit hazy because this is from memory, please check the api)
well you can either construct some DTO class and map that on-the-fly
for example:
public class Person
{
public virtual String Name {get;set;}
public virtual String Surname {get;set;}
}
which is properly mapped to nhibernate
and the
PersonDTO : Person
{
public int FTSRanking {get;set;}
}
which is not mapped. Note that i'm inheriting from class Person although that is not necessary and i'm only doing it for ease.
This PersonDTO class is only used on queries but there are limitations, as the following hql shows.
NHSes.CreateQuery('select p.Name, p.Surname, p.FTSAlias as FTSRanking from Person p')
.SetResultTransformer(Transformers.AliasToBean<PersonDTO>())
will return a PersonDTO which nhibernate manages to assemble because every item in the select list matches a property (in name, casing, type) in the PersonDTO class. Also you will have to manually type the select list and also, since it is not a mapped class, nhibernate cannot assemble collections.
An other option would be to use the Criteria API in which you set projections (aka extend the select list)
IList<object[]> results = NHSes.CreateCriteria(typeof(Person))
.Add(Expression.SQL(" your fts clause here "))
.SetProjection(Projections.SQL(" add your fts ranking column here",,), Projections.( here add as a projection the main entity ))
.List<object[]>();
where in the results variable each returned row is an object[] and the first element (ie results[0][0]) is the ranking and the second element (ie results[0][1]) is a properly managed Person object

Related

How to update two columns with same name from two tables in a join query

I am getting an error:
Property or Indexer cannot be assigned to "--" it is read only
when trying to update two columns with the same name in two tables in a join query. How do I get this to work? Thanks!
The anonymous object created in your projection ("select new" part) is read-only and its properties are not tracked by data context by any means.
Instead, you can try this:
//...
select new
{
p1 = p,
p2 = t
}
foreach (var row in updates)
{
row.p1.Processed = true;
row.p2.Processed = true;
}
In order to improve performance you may also want to take a look at batch update capabilities of Entity Framework Extensions (if you are using Entity Framework): https://entityframework-extensions.net/overview
Yes, that's due to anonymous type properties are read only, from documentation:
Anonymous types provide a convenient way to encapsulate a set of
read-only properties into a single object without having to explicitly
define a type first.
I suggest you to create a custom class with the two entities you need (a DTO):
public class PassengerDTO
{
public Passenger Passenger {get;set}
public PassengerItinerary PassengerItinerary {get;set}
}
And use it in your projection, You need the entity instances and not just the properties you want to modify because, when you modify the Processed property in the foreach the proxy class that represent your entity is going to change the status of you entity to Updated.

nhibernate collection query with private backing field

I have a mant-to-many relationship modeled in the database (with a bridge table) between Student and Professor (_students_selected) , in my entites i have modeled it as a one-to-many relationship i.e. a Professor has one Student.
HasManyToMany<Student>(Reveal.Member<Professor>("_students"))
.Table("_students_selected").ChildKeyColumn("student_key").ParentKeyColumn("professor_key");
public class Professor
{
private IList<Students> _students;
public virtual Student Student
{
get { return _students.FirstOrDefault(); }
}
}
The above works when getting the data however when querying over the Professors i am unable to add a where condition on the students because the actual data is mapped to the private backing field _students. How do i query this? code below does not work.
_unitOfWork.Session.QueryOver<Professor>().Where(i => i.Student.Id == 24).List();
NHibernate can't translate your C# code inside the property to SQL, it can only work with mapped properties. Either use the collection in the statement (which needs to be public/internal then of course) or filter the results in memory (but be careful with select n + 1 problems then).

NHibernate QueryOver value collection

I have a project using NH 3.1 and have been using the QueryOver syntax for everything thus far.
One aspect of this project lives in a organization-wide database that I have read-only access to and is using a completely differently DBMS (Oracle vs MSSQL). So I store references from my objects (Foos) to their objects (Bars) using a standard many-to-many table
FooBars
FooID int not null PK
BarID int not null PK
And my domain object, instead of having a Iset<Bar> instead has an ISet<int> BarIDs which is manually mapped to the FooBars table. This prevents NH from trying to do the impossible and join all the way over to the Bars table (I can use a BarRepository.Get() to retrieve the details of the Bars later, if I need them, and in this case, I wouldn't, because I just need the IDs to filter the list of objects returned).
Given IList<int> SelectedBars how can I write a QueryOver<Foo> where BarIDs contains any element in SelectedBars?
SQL something like
...FROM foos INNER JOIN foobars on foo.fooID = foobars.fooID WHERE barID IN ( ... )
It is not possible with QueryOver. Two years ago, I had a similar question about filtering value collections. (Note: QueryOver is based on Criteria API).
I'm not 100% sure, but it probably works with HQL. It is much more powerful.
You may include an SQL statement into the QueryOver criteria.
I don't really understand why you don't map it as a list of entities. There is lazy loading to avoid unnecessary loading - although there are some trade offs sometimes. You can access the ID of NH proxies without hitting the database. Mapping ids makes usually life much harder.
Try:
session.QueryOver<Foo>()
.JoinQueryOver(x => x.FooBars)
.WhereRestrictionOn(x => x.BarId).IsIn( ... )
So 3 years later, I'm back to report how I did solve this.
public class Foo :Entity {
public virtual ISet<FooBar> BarIDs { get; protected internal set; }
} ...
public class FooBar :Entity {
protected internal FooBar() { }
protected internal FooBar(Foo f, int BarID) { ... }
public virtual Foo Foo { get; protected internal set; }
public virtual int BarID { get; protected internal set; }
}
This is basically what Stefan suggested, and what's hinted at in the related post. You just have to eat the overhead of writing an extra entity and referencing it. Remember that I'm storing BarIDs instead of full Bar objects, because I'm dealing with a hard boundary between two databases: the Bars are stored in an entirely different database on a different platform than the Foos. Otherwise of course, you're far better off just telling Foo that is has an ISet<Bar>.
Finding Foos by SelectedBarIDs is then easy, much like Thilak suggested:
session.QueryOver<Foo>().JoinQueryOver<FooBar>(f => f.BarIDs).
WhereRestrictionOn(b => b.BarID).IsIn(...)...
It's an interesting problem, working across the database boundary like this. I can't say I like having to do it, but if someone else is going to take the time to maintain a list of Bars and make it available for my use, it would be a giant waste of resources for me to do the same. So a small inefficiency in having the wrapper class is a very easy cost to justify.

Keeping NHibernate from loading some fields

This is a follow on question to My earlier question on lazy loading properties. Since the application is an enhancement to a production application in a fairly major enterprise, and it currently running using NHib 1.2, upgrading to version 3 is not going to happen, so the suggested answer of using Lazy Properties in 3.0 won't work for me.
To summarize the problem, I have simple database with 2 tables. One has about a dozen simple fields, plus a one to many relation to the second table as a child table. Two of the fields are very large blobs (several megabytes each), and I want to, effectively, lazy load them. This table has about 10 records, and they populate a grid at start up, but access to the large blobs are only needed for whatever row is selected.
The object structure looks something like this:
[Serializable]
[Class(Schema = "dbo", Lazy = false)]
public class DataObject
{
[Id(-2, Name = "Identity", Column="Id")]
[Generator(-1, Class="native")]
public virtual long Identity { get; set;}
[Property]
public string FieldA { get; set;}
[Property]
public byte[] LongBlob {get; set;}
[Property]
public string VeryLongString { get; set;}
[Bag(-2, Cascade=CascadeStyle.All, Lazy= false, Inverse=true)]
[Key(-1, Column="ParentId")]
[OneToMany(0, ClassType=typeof(DataObjectChild))]
public IList<DataObjectChild> ChildObjects { get; set;}
}
Currently, the table is accessed with a simple query:
objectList = (List<DataObject>) Session.CreateQuery("from DataObject").List<DataObject>();
And that takes care of everything, including loading the child objects.
What I would like is a query to do exactly the same thing, except select a list of the properties of the DataObject that includes everything EXCEPT the two blobs. Then I can add custom property Getters that will go out and load those properties when they are accessed.
So far, I have not been successful at doing that.
Things I have tried:
a) constructing an HQL query using a SELECT list.
It looks something like this:
objectList = (List<DataObject>) Session.CreateQuery(
"SELECT new DataObject " +
"(obj.Identity, obj.FieldA) " +
"from DataObject as obj")
That works, though I have to add a constructor to the DataObject that will construct it from fields I select, which is rather cumbersome. But then it doesn't load and expand the list of child objects, and I can't figure out how to do that easily (and don't really want to - I want to tell NHib to do it.)
b) removing the [Property] attribute from the two fields in the object definition. That keeps the NHibernate.Mapping.Attributes from mapping those fields, so they don't get included in the query, but then I have no way to access them from NHib at all, including writing them out when I go to save a new or modified DataObject.
I'm thinking there has to be an easier way. Can somebody point me in the right direction?
Thanks
I think you're on the right path. However, you're going to have to do more manual work since you're using a very old version of NHibernate. I would fetch projections of your entities that contain only the columns you want to eagerly load. Then when the UI requests the large blob objects, you're going to have to write another query to get them and supply them to the UI.
Another option would be to have a second class (e.g. SmallDataObject) with the same mapping (but without the blobs) and use that for the list. When editing a list item you would load the class with the blobs using the id of the selected list item.
In any case, modifying the mapping after the creation of the SessionFactory is not possible, so you cannot get rid of the mapped properties on demand.

setfirstresult & setmaxresult in child collection

I have and entity lets call it Entity, and a Child collection Children.
I have a screen where the user has the Entity information, and a list with the Children collection, but that collection can be get very big, so i was thinking about using paging: get the first 20 elements, and lazy load the next only if the user explicitly presses the next button.
So i created in the Entity Repository a function with this signature:
IEnumerable<Child> GetChildren(Entity entity, int actualPage, int numberOfRecordsPerPage)
I need to use the setfirstresult and setmaxresult, not in the Agregate root Entity, but in the child collection. But when i use those two configurations, they allways refer to the entity type of the HQL/Criteria query.
Other alternative would be to create a HQL/Criteria query for the Child type, set the max and first result, then filter the ones who are in the Entity Children collection (by using subquery).
But i wasn't able to do this filter. If it was a bidirectional association (Child refering the parent Entity) it would be easier.
Any suggestions?
Any
One approach would be to create a query that returns results from both tables by doing a group by. This approach would allow you to apply paging on data that will come from the children collection and have a common factor (the entity's ID in each row) while you keep your starting point (the Entity object). What I mean is something like that:
public IList<object> GetData(int entityID, int actualPage, int numberOfRecordsPerPage)
{
ICriteria criteria = _repository.Session.CreateCriteria<FlowWhatIfProfile>("entity")
.CreateCriteria("Children", "children", NHibernate.SqlCommand.JoinType.InnerJoin)
.Add(Restrictions.Eq("children.EntityID", entityID));
ProjectionList pl = Projections.ProjectionList();
pl.Add(Projections.GroupProperty("children.Id"));
pl.Add(Projections.GroupProperty("children.Property1"));
pl.Add(Projections.GroupProperty("children.Property2"));
pl.Add(Projections.GroupProperty("children.Property2"));
pl.Add(Projections.GroupProperty("entity.Id"));
return criteria.SetProjection(pl)
.SetFirstResult(actualPage * numberOfRecordsPerPage)
.SetFetchSize(numberOfRecordsPerPage)
.List<object>();
}
The drawback would be that your returned data are a list of arrays (you will have to cast object to object[]) but you can overcome that by using the AliasToBean functionality that lest NHibernate project these arrays to strongly typed objects that you define.
it's simple with CreateFilter
session.CreateFilter(entity.children, "")
.SetFirstResult(0)
.SetMaxResults(20)
.List();
http://knol.google.com/k/fabio-maulo/nhibernate-chapter-16/1nr4enxv3dpeq/19#