NHibernate - create criteria based on a property that is a list - nhibernate

I have a class called LoanApplication, and it has a collection property set up called Workflow. In the mapping file, I am setting the order of retrieval of the Workflow records to sort by Date, so the current workflow is always the first item in the list.
Now I want to query by the current workflow to get LoanApplications that are in a specific workflow step using the Criteria API. I'm not really sure how to do this. Here is how I am mapping the Workflow collection:
<bag name="ApplicationWorkflow" table="PreApplication.ApplicationWorkflow" generic="true" inverse="true" order-by="StartDate DESC"
cascade="all" lazy="true">
<key column="ApplicationID" />
<one-to-many class="ApplicationWorkflow" />
</bag>
Here is how I am retrieving applications (this is where I need to add the filter by Current Workflow functionality):
public IList<Model.PreApplication.Application> GetCompletedApplications()
{
IList<Model.PreApplication.Application> result = null;
using (ITransaction transaction = this.Session.BeginTransaction())
{
result = this.Session.CreateCriteria<Model.PreApplication.Application>()
.AddOrder(new Order("EnteredDate", false))
.List<Model.PreApplication.Application>();
transaction.Commit();
}
return result;
}
Thanks for any help!

So you need to list Applications whose current Workflow is at a specific step? You can use a sub-query to join only the current workflow and then restrict to a specific step.
the desired SQL...
select
...
from
Application app
inner join ApplicationWorkFlow currWorkFlow on app.id = currWorkFlow.application_id
where
currWorkFlow.id = (
select top 1 topFlow.id
from ApplicationWorkFlow topFlow
where topFlow.application_id = app.id
order by topFlow.StartedDate desc
)
and currWorkFlow.step = #step
the Criteria to get you there...
session.CreateCriteria<Application>("app")
.CreateAlias("ApplicationWorkFlow", "currWorkFlow", NHibernate.SqlCommand.JoinType.InnerJoin)
.Add(Subqueries.PropertyEq("currWorkFlow.id",
DetachedCriteria.For<ApplicationWorkFlow>("topFlow")
.SetMaxResults(1)
.SetProjection(Projections.Property("topFlow.id"))
.AddOrder(Order.Desc("topFlow.StartDate"))
.Add(Restrictions.EqProperty("app.id", "topFlow.Application.id"))))
.Add(Restrictions.Eq("currWorkFlow.step", step))
.List<Application>();

How about this sql query?
SELECT * FROM Application app
JOIN
(SELECT * FROM ApplicationWorkFlow aflo WHERE aflo.step = #step) AS aflo
WHERE aflo.application_id = app.id
Simplified query using criteria
var applications =
Session.CreateCriteria<Application>()
.CreateAlias("ApplicationWorkFlow", "appflo", JoinType.LeftOuterJoin)
.Add(Restrictions.Eq("appflo.Step", step))
.List<Application>();
Corresponding Criteria Query using detached criteria
var detached = DetachedCriteria.For<ApplicationWorkFlow>()
.SetProjection(Projections.Id())
.Add(Restrictions.Eq("Step", step));
var applications =
Session.CreateCriteria<Application>()
.CreateAlias("ApplicationWorkFlow", "appflo", JoinType.LeftOuterJoin)
.Add(Subqueries.PropertyIn("appflo.Id", detachedCriteria))
.List<Application>();
Could someone please tell me if the above two queries are the same? They are generating the same sql in my case. If they are the same, why should be use the DetachedCriteria?

I suggest to add simply reference to last/active Workflow. Criteria will be much more simpler :)
public void AddLatestWorkflow(Workflow wf)
{
this.Workflows.Add(wf);
this.LatestWorkflow = wf;
}

Related

Entity Framework problem with reducing projection

I've been working on improving performance for our .NET core API with EF 5.0.11 by reducing the projection of our queries, but I'm currently stuck with the following scenario:
I improved the projection of the queries like this:
var employeeEmailQuery = context.Employee
.Where(e => e.Active == true)
.Select(e => new EmployeeEmailView
{
Name = e.FullName,
Email = e.Email
});
This reduces the select query to just the two columns I need instead of a SELECT * on 80+ columns in the database.
In my database, I also have columns with translated descriptions. It looks like this:
What I would like to do is select the relevant translated description, based on the current culture, so I added the following code:
var culture = CultureInfo.DefaultThreadCurrentUICulture;
var employeeEmailQuery = context.Employee
.Where(e => e.Active == true)
.Select(e => new EmployeeEmailView
{
Name = e.FullName,
Email = e.Email,
this.SetDescription(e, culture);
});
The SetDescription method checks the culture and picks the correct column to set a Description property in the EmployeeEmailView. However, by adding this code, the query is now once again doing a SELECT *, which I don't want.
Does anybody have an idea on how to dynamically include a select column using EF without rewriting everything into raw SQL?
Thanks in advance.
I think the only way is to use an Interceptor to modify the query, or dynamically generate the EF IQueryable with Expressions.

How can I recreate this complex SQL Query using NHibernate QueryOver?

Imagine the following (simplified) database layout:
We have many "holiday" records that relate to going to a particular Accommodation on a certain date etc.
I would like to pull from the database the "best" holiday going to each accommodation (i.e. lowest price), given a set of search criteria (e.g. duration, departure airport etc).
There will be multiple records with the same price, so then we need to choose by offer saving (descending), then by departure date ascending.
I can write SQL to do this that looks like this (I'm not saying this is necessarily the most optimal way):
SELECT *
FROM Holiday h1 INNER JOIN (
SELECT h2.HolidayID,
h2.AccommodationID,
ROW_NUMBER() OVER (
PARTITION BY h2.AccommodationID
ORDER BY OfferSaving DESC
) AS RowNum
FROM Holiday h2 INNER JOIN (
SELECT AccommodationID,
MIN(price) as MinPrice
FROM Holiday
WHERE TradeNameID = 58001
/*** Other Criteria Here ***/
GROUP BY AccommodationID
) mp
ON mp.AccommodationID = h2.AccommodationID
AND mp.MinPrice = h2.price
WHERE TradeNameID = 58001
/*** Other Criteria Here ***/
) x on h1.HolidayID = x.HolidayID and x.RowNum = 1
As you can see, this uses a subquery within another subquery.
However, for several reasons my preference would be to achieve this same result in NHibernate.
Ideally, this would be done with QueryOver - the reason being that I build up the search criteria dynamically and this is much easier with QueryOver's fluent interface. (I had started out hoping to use NHibernate Linq, but unfortunately it's not mature enough).
After a lot of effort (being a relative newbie to NHibernate) I was able to re-create the very inner query that fetches all accommodations and their min price.
public IEnumerable<HolidaySearchDataDto> CriteriaFindAccommodationFromPricesForOffers(IEnumerable<IHolidayFilter<PackageHoliday>> filters, int skip, int take, out bool hasMore)
{
IQueryOver<PackageHoliday, PackageHoliday> queryable = NHibernateSession.CurrentFor(NHibernateSession.DefaultFactoryKey).QueryOver<PackageHoliday>();
queryable = queryable.Where(h => h.TradeNameId == website.TradeNameID);
var accommodation = Null<Accommodation>();
var accommodationUnit = Null<AccommodationUnit>();
var dto = Null<HolidaySearchDataDto>();
// Apply search criteria
foreach (var filter in filters)
queryable = filter.ApplyFilter(queryable, accommodationUnit, accommodation);
var query1 = queryable
.JoinQueryOver(h => h.AccommodationUnit, () => accommodationUnit)
.JoinQueryOver(h => h.Accommodation, () => accommodation)
.SelectList(hols => hols
.SelectGroup(() => accommodation.Id).WithAlias(() => dto.AccommodationId)
.SelectMin(h => h.Price).WithAlias(() => dto.Price)
);
var list = query1.OrderByAlias(() => dto.Price).Asc
.Skip(skip).Take(take+1)
.Cacheable().CacheMode(CacheMode.Normal).List<object[]>();
// Cacheing doesn't work this way...
/*.TransformUsing(Transformers.AliasToBean<HolidaySearchDataDto>())
.Cacheable().CacheMode(CacheMode.Normal).List<HolidaySearchDataDto>();*/
hasMore = list.Count() == take;
var dtos = list.Take(take).Select(h => new HolidaySearchDataDto
{
AccommodationId = (string)h[0],
Price = (decimal)h[1],
});
return dtos;
}
So my question is...
Any ideas on how to achieve what I want using QueryOver, or if necessary Criteria API?
I'd prefer not to use HQL but if it is necessary than I'm willing to see how it can be done with that too (it makes it harder (or more messy) to build up the search criteria though).
If this just isn't doable using NHibernate, then I could use a SQL query. In which case, my question is can the SQL be improved/optimised?
I have manage to achieve such dynamic search criterion by using Criteria API's. Problem I ran into was duplicates with inner and outer joins and especially related to sorting and pagination, and I had to resort to using 2 queries, 1st query for restriction and using the result of 1st query as 'in' clause in 2nd creteria.

NHibernate Criteria Transform Result

I have an SecurityGroup entity witch has Memebers and Application properties.
Application is a lookup.
So securityGroups is in many-to-many relationship with User table and one-to-many with LookupApplciation (FK)
Now I want to select all application linked to a specific user.
I have follow criteria:
public IList<LookupApplication> GetApplicationByUser(User user)
{
return
this.Session.CreateCriteria(typeof(SecurityGroup), "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<LookupApplication>();
}
It trows an exception
The value "Edi.Advance.Core.Model.Security.SecurityGroup" is not of type "Edi.Advance.Core.Model.Lookups.LookupApplication" and cannot be used in this generic collection.
Parameter name: value
and it is right.
How can I transform the result to IList<LookupApplication>?
Thanks
You can only return the type which you create the criteria from.
The easiest way starting from the code you have will be:
return
this.Session.CreateCriteria(typeof(SecurityGroup), "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<SecurityGroup>()
// get the lookup applications in memory
.SelectMany(x => x.LookupApplications);
This loads all SecurityGroups into memory, even if it only needs the LookupApplications. This might not be an issue when you need them anyway or when they are small.
You could also reverse the query and start from the LookupApplication
return
this.Session.CreateCriteria(typeof(LookupApplication), "la")
// requires navigation path from SecurityGroup to LookupApplication
.CreateCriteria("la.SecurityGroup", "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<LookupApplication>()
Or use HQL, which has some features not available in Criteria, items gets all the items from a collection:
select sg.LookupApplications.items
from SecurityGroup sg inner join sg.Members u
where u.Id = :userId
HQL is actually recommended when you don't have dynamic queries.
Update, from isuruceanu's comment:
Session
.CreateQuery(
#"select sg.Application
from SecurityGroup sg
inner join sg.Members u
where u.Id = :userId")
.SetParameter("userId", user.Id)
.List<LookupApplication>();
It depends on how the SecurityGroup looks like and how LookupApplication looks like.
You could use ResultTransformer like:
.SetResultTransformer(Transformers.AliasToBean<LookupApplication>())
.List<LookupApplication>();
Granted that SecurityGroup has properties matchinig LookupAppliaction, or else you have to project those properties like:
.SetProjection(NHibernate.Criterion.Projections.ProjectionList()
.Add(Projections.Property("Number"), "OrderNumber")
.Add(Projections.Property("CreatedOn"), "CreatedOn")
.Add(Projections.Property("MemeberName"), "Name"))
.SetResultTransformer(Transformers.AliasToBean<LookupApplication>())
.List<LookupApplication>();

Only get latest results using nHibernate

I have a nHibernate query like this
ICriteria query = session.CreateCriteria(typeof(MyResult))
.Add(Expression.Eq("ResultTypeId", myResult.ResultTypeId))
Problem is that users add results all the time and I want to show a table of all the latest results for all the diferent ResultTypes I have.
The MyResult class has a property ResultDate. My question is, what do I add to the query to get it to only return the latest result for the given result type. There is nothing to say that the results will be in date order in the database.
Thanks,
Mark
You can order the result by ResultDate using the AddOrder method, as below:
ICriteria query = session.CreateCriteria(typeof(MyResult))
.Add(Expression.Eq("ResultTypeId", myResult.ResultTypeId))
.AddOrder(Order.Desc("ResultDate"))
.List<MyResult>();
If you want to limit the number of MyResult instances you get back, you can use the SetMaxResults method, like so:
ICriteria query = session.CreateCriteria(typeof(MyResult))
.Add(Expression.Eq("ResultTypeId", myResult.ResultTypeId))
.AddOrder(Order.Desc("ResultDate"))
.SetMaxResults(20)
.List<MyResult>();
If I understand the question well, Mark wants to see an overview of all the last results for each type.
Which means that, for every result type, he only wants to see only one row, and that is the Result which has last been added for that type.
I think that, the easiest way to achieve this, would be to create an additional class, which we can call 'MyResultOverview' for instance:
public class MyResultOverview
{
public int ResultId {get; set;}
public int ResultTypeId {get; set;}
public DateTime ResultDate {get; set;}
}
This class should not be mapped, but NHibernate should be aware that this class exists. Therefore, we'll have to import it:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
<import class="MyResultOverview" />
</hibernate-mapping>
Then, we can create an ICriteria which will populate instances of MyResultOverview (and which will also generate the most efficient SQL Query in order to get this overview).
It should look something like this:
ICriteria criteria = session.CreateCritera (typeof(MyResult));
criteria.SetProjection (Projections.ProjectionList ()
.Add (Projections.Property("Id"), "ResultId")
.Add (Projections.Property("ResultType"), "ResultType")
.Add (Projections.Max("ResultDate"), "ResultDate"));
criteria.SetResultTransformer (Transformers.AliasToBean (typeof(MyResultOverview)));
IList<MyResultOverview> results = criteria.List<MyResultOverview>();
This should give you a list of MyResultOverview instances which represent the MyResults that you're looking for.
Then, in order to retrieve the MyResult itself, you can simply do this by retrieving the MyResult instance for that particalur ResultId that you've retrieved as well.
I haven't tested this, nor did i compile it, but this is the path that I would follow to achieve this.
Order by ResultDate (descending) and select top whatever you feel appropriate.
In HQLthis might work:
select item, tag
from MyItem item
join item.Tags tag
where tag.Id = (
select max(tag2.Id)
from MyItem item2
join item2.Tags tag2
where item2.Id = item.Id
group by item2.Id
)

NHibernate Eager Fetching Over Multiple Levels

I have a 3-leveled hierarchy of entities: Customer-Order-Line, which I would like to retrieve in entirety for a given customer, using ISession.Get(id). I have the following XML fragments:
customer.hbm.xml:
<bag name="Orders" cascade="all-delete-orphan" inverse="false" fetch="join">
<key column="CustomerID" />
<one-to-many class="Order" />
</bag>
order.hbm.xml:
<bag name="Lines" cascade="all-delete-orphan" inverse="false" fetch="join">
<key column="OrderID" />
<one-to-many class="Line" />
</bag>
I have used the fetch="join" attribute to indicate that I want to fetch the child entities for each parent, and this has constructed the correct SQL:
SELECT
customer0_.ID AS ID8_2_,
customer0_.Name AS Name8_2_,
orders1_.CustomerID AS CustomerID__4_,
orders1_.ID AS ID4_,
orders1_.ID AS ID9_0_,
orders1_.PostalAddress AS PostalAd2_9_0_,
orders1_.OrderDate AS OrderDate9_0_,
lines2_.OrderID AS OrderID__5_,
lines2_.ID AS ID5_,
lines2_.ID AS ID10_1_,
lines2_.[LineNo] AS column2_10_1_,
lines2_.Quantity AS Quantity10_1_,
lines2_.ProductID AS ProductID10_1_
FROM Customer customer0_
LEFT JOIN [Order] orders1_
ON customer0_.ID=orders1_.CustomerID
LEFT JOIN Line lines2_
ON orders1_.ID=lines2_.OrderID
WHERE customer0_.ID=1
So far, this looks good - SQL returns the correct set of records (with only one distinct orderid), but when I run a test to confirm the correct number of entities (from NH) for Orders and Lines, I get the wrong results
I should be getting (from my test data), 1xOrder and 4xLine, however, I am getting 4xOrder and 4xLine. It appears that NH is not recognising the 'repeating' group of Order information in the result set, nor correctly 'reusing' the Order entity.
I am using all integer IDs (PKs), and I've tried implementing IComparable of T and IEquatable of T using this ID, in the hope that NH will see the equality of these entities. I've also tried overridding Equals and GetHashCode to use the ID. Neither of these 'attempts' have succeeded.
Is "multiple leveled fetch" a supported operation for NH, and if so, is there an XML setting required (or some other mechanism) to support it?
NB: I used sirocco's solution with a few changes to my own code to finally solve this one. the xml needs to be changed from bag to set, for all collections, and the entitities themselves were changed to implement IComparable<>, which is a requirement of a set for uniqueness to be established.
public class BaseEntity : IComparable<BaseEntity>
{
...
private Guid _internalID { get; set; }
public virtual Guid ID { get; set; }
public BaseEntity()
{
_internalID = Guid.NewGuid();
}
#region IComparable<BaseEntity> Members
public int CompareTo( BaseEntity other )
{
if ( ID == Guid.Empty || other.ID == Guid.Empty )
return _internalID.CompareTo( other._internalID );
return ID.CompareTo( other.ID );
}
#endregion
...
}
Note the use of an InternalID field. This is required for new (transient) entities, other wise they won't have an ID initially (my model has them supplied when saved).
You're getting 4XOrder and 4XLines because the join with lines doubles the results . You can set a Transformer on the ICriteria like :
.SetResultTransformer(new DistinctRootEntityResultTransformer())
I just read Ayende's Blogpost where he used the following Example:
session.CreateCriteria(typeof(Post))
.SetFetchMode("Comments", FetchMode.Eager)
.List();
In a Criteria Query to avoid Lazy Loading on one particular Query
Maybe that can help you.
If you need to keep your one-to-manys as bags, then you can issue 2 queries, each with only 1 level of hierarchy. eg something like this:
var temp = session.CreateCriteria( typeof( Order ) )
.SetFetchMode( "Lines", NHibernate.FetchMode.Eager )
.Add( Expression.Eq( "Customer.ID", id ) )
.List();
var customer = session.CreateCriteria( typeof( Customer ) )
.SetFetchMode( "Orders", NHibernate.FetchMode.Eager )
.Add( Expression.Eq( "ID", id ) )
.UniqueResult();
Lines get loaded into the NH cache in the first query, so they won't need lazy loading when later accessing eg customer.Orders[0].Lines[0].
#Tigraine: your query only returns Post with Comments. This brings All posts with all Comments (2 levels). What Ben asking is Customer to Order To LineItem (3 level).
#Ben: to my knowledge nHibernate doesn't support eager loading upto 3 level yet. Hibernate does support it thou.
I was having the same problem. See this thread. I didn't get a solution but a hint from Fabio. Use Set instead of bag. And it worked.
So my suggestion is try to use set. You don't have to use Iesi collection use IDictonary and NH is happy
public override IEnumerable<Baseline> GetAll()
{
var baselines = Session.CreateQuery(#" from Baseline b
left join fetch b.BaselineMilestones bm
left join fetch bm.BaselineMilestonePrevious ")
.SetResultTransformer(Transformers.DistinctRootEntity)
.List<Baseline>();
return baselines;
}